أنواع ملفات صور زخرفة الصورة الرمزية المسموح بها

لست متأكدًا مما إذا كان هذا مقصودًا، ولكنه يبدو خطأً بسيطًا.

عند تحميل صورة لتمييز الصورة الرمزية، يسمح بتحديد أي نوع ملف صورة. ومع ذلك، إذا حددت النوع الخاطئ، فسوف تعلمك Discourse بخطأ.

هذا لأن Discourse يستخدم حرف بدل في السمة accept:
<input class="hidden-upload-field" accept="image/*" type="file">

ومع ذلك، فإن عددًا قليلاً فقط من أنواع الملفات مسموح بتحميلها بالفعل. لقد قمت بتحميل صورة SVG، وهي غير مسموح بها. يمكنك رؤية ما هو مسموح بتحديده وما تسمح به Discourse بالفعل:

من الناحية المثالية، يجب أن تحدد السمة accept جميع أنواع الملفات المسموح بها بدلاً من حرف بدل.

Discourse 2.9.0.beta3 (03ad88f2c2)

5 إعجابات

بعد سنوات… هذا في الواقع أمر صعب بعض الشيء. يمكننا القيام بشيء مثل هذا.

commit ea5927e3ff15616fc7df91ddcd361e6379a12582
Author: Sam Saffron <sam.saffron@gmail.com>
Date:   Thu Oct 30 13:51:02 2025 +1100

    FEATURE: filter uploads correctly in uppy uploader and image  / avatar uploader

diff --git a/frontend/discourse/admin/components/images-uploader.gjs b/frontend/discourse/admin/components/images-uploader.gjs
index 18a857d581..978edaac30 100644
--- a/frontend/discourse/admin/components/images-uploader.gjs
+++ b/frontend/discourse/admin/components/images-uploader.gjs
@@ -2,13 +2,18 @@
 import Component from "@ember/component";
 import { getOwner } from "@ember/owner";
 import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { service } from "@ember/service";
 import { tagName } from "@ember-decorators/component";
 import icon from "discourse/helpers/d-icon";
+import { acceptedImageFormats } from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";

 @tagName("span")
 export default class ImagesUploader extends Component {
+  @service currentUser;
+  @service siteSettings;
+
   uppyUpload = new UppyUpload(getOwner(this), {
     id: "images-uploader",
     type: "avatar",
@@ -28,6 +33,10 @@ export default class ImagesUploader extends Component {
     return this.uploadingOrProcessing ? i18n("uploading") : i18n("upload");
   }

+  get acceptedFormats() {
+    return acceptedImageFormats(this.currentUser?.staff, this.siteSettings);
+  }
+
   get template() {
     return `
       <label
@@ -40,7 +49,7 @@ export default class ImagesUploader extends Component {
         class="hidden-upload-field"
         disabled={{this.uppyUpload.uploading}}
         type="file"
-        accept="image/*"
+        accept={{this.acceptedFormats}}
         multiple
       />
     </label>
diff --git a/frontend/discourse/app/components/avatar-uploader.gjs b/frontend/discourse/app/components/avatar-uploader.gjs
index d3bf8fc341..0f77c9e06a 100644
--- a/frontend/discourse/app/components/avatar-uploader.gjs
+++ b/frontend/discourse/app/components/avatar-uploader.gjs
@@ -3,15 +3,20 @@ import Component from "@ember/component";
 import { action } from "@ember/object";
 import { getOwner } from "@ember/owner";
 import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { service } from "@ember/service";
 import { isBlank } from "@ember/utils";
 import { tagName } from "@ember-decorators/component";
 import DButton from "discourse/components/d-button";
 import discourseComputed from "discourse/lib/decorators";
+import { acceptedImageFormats } from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";

 @tagName("span")
 export default class AvatarUploader extends Component {
+  @service currentUser;
+  @service siteSettings;
+
   uppyUpload = new UppyUpload(getOwner(this), {
     id: "avatar-uploader",
     type: "avatar",
@@ -34,6 +39,11 @@ export default class AvatarUploader extends Component {

   imageIsNotASquare = false;

+  @discourseComputed()
+  acceptedFormats() {
+    return acceptedImageFormats(this.currentUser?.staff, this.siteSettings);
+  }
+
   @discourseComputed("uppyUpload.uploading", "uploadedAvatarId")
   customAvatarUploaded() {
     return !this.uppyUpload.uploading && !isBlank(this.uploadedAvatarId);
@@ -58,7 +68,7 @@ export default class AvatarUploader extends Component {
       class="hidden-upload-field"
       disabled={{this.uploading}}
       type="file"
-      accept="image/*"
+      accept={{this.acceptedFormats}}
       aria-hidden="true"
     />
     <DButton
diff --git a/frontend/discourse/app/components/uppy-image-uploader.gjs b/frontend/discourse/app/components/uppy-image-uploader.gjs
index 6a94ae6cb8..28bf264e8a 100644
--- a/frontend/discourse/app/components/uppy-image-uploader.gjs
+++ b/frontend/discourse/app/components/uppy-image-uploader.gjs
@@ -15,7 +15,12 @@ import concatClass from "discourse/helpers/concat-class";
 import icon from "discourse/helpers/d-icon";
 import { getURLWithCDN } from "discourse/lib/get-url";
 import lightbox from "discourse/lib/lightbox";
-import { authorizesOneOrMoreExtensions, isVideo } from "discourse/lib/uploads";
+import {
+  acceptedImageFormats,
+  acceptedVideoFormats,
+  authorizesOneOrMoreExtensions,
+  isVideo,
+} from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";

@@ -138,7 +143,20 @@ export default class UppyImageUploader extends Component {
   }

   get acceptedFormats() {
-    return this.args.allowVideo ? "image/*,video/*" : "image/*";
+    const imageFormats = acceptedImageFormats(
+      this.currentUser?.staff,
+      this.siteSettings
+    );
+
+    if (this.args.allowVideo) {
+      const videoFormats = acceptedVideoFormats(
+        this.currentUser?.staff,
+        this.siteSettings
+      );
+      return videoFormats ? `${imageFormats},${videoFormats}` : imageFormats;
+    }
+
+    return imageFormats;
   }

   get isVideoFile() {
diff --git a/frontend/discourse/app/lib/uploads.js b/frontend/discourse/app/lib/uploads.js
index a49005f6a8..da38f2abf4 100644
--- a/frontend/discourse/app/lib/uploads.js
+++ b/frontend/discourse/app/lib/uploads.js
@@ -178,6 +178,17 @@ function imagesExtensions(staff, siteSettings) {
   return exts;
 }

+function videosExtensions(staff, siteSettings) {
+  let exts = extensions(siteSettings).filter((ext) => isVideo(`.${ext}`));
+  if (staff) {
+    const staffExts = staffExtensions(siteSettings).filter((ext) =>
+      isVideo(`.${ext}`)
+    );
+    exts = exts.concat(staffExts);
+  }
+  return exts;
+}
+
 function isAuthorizedFile(fileName, staff, siteSettings) {
   if (
     staff &&
@@ -215,6 +226,16 @@ function authorizedImagesExtensions(staff, siteSettings) {
     : imagesExtensions(staff, siteSettings).join(", ");
 }

+export function acceptedImageFormats(staff, siteSettings) {
+  const exts = imagesExtensions(staff, siteSettings);
+  return exts.map((ext) => `.${ext}`).join(",");
+}
+
+export function acceptedVideoFormats(staff, siteSettings) {
+  const exts = videosExtensions(staff, siteSettings);
+  return exts.map((ext) => `.${ext}`).join(",");
+}
+
 export function authorizesAllExtensions(staff, siteSettings) {
   return (
     siteSettings.authorized_extensions.includes("*") ||

أحد العيوب الطفيفة هو أن ضغط الصور من جانب العميل لدينا يمكنه أحيانًا التعامل مع تحويل التنسيق. لذلك من المحتمل أن يؤدي هذا إلى حظر بعض أنواع الصور التي لا نريد حظرها.

أنا متردد بشأن ما إذا كنا نريد إصلاح هذا أم لا، ولكني أرى أن هذا له تأثير كبير إذا قمت بتكوين موقعك (على سبيل المثال) للسماح بصيغة png فقط.
م.م. @martin

من نظرة سريعة، يبدو هذا جيدًا بالنسبة لي، هل يمكنك فتح طلب سحب (PR) وسأقوم بمراجعته؟