允许的头像风格图片文件类型

多年以后……这确实是一个棘手的问题。我们可以做一些类似的事情

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);
+  }
+
   	<template>
     	<label
        class="btn"
@@ -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.uppyUpload.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,那么我认为这会产生很大的影响。
cc @martin