DEV: Use pick-files-button in composer-editor and clean up (#16375)

A while ago in 27b97e4 the
pick-files-input was added but only used once for data-explorer. This commit uses it
for the composer-editor, and cleans it up to be usable either via uppy
handling the uploads or with this component handling the uploads.

This can then be used in other places in the app and also for plugins.
This commit is contained in:
Martin Brennan 2022-04-06 12:48:13 +10:00 committed by GitHub
parent 1598e6b489
commit bf3260faea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 168 deletions

View File

@ -1,8 +1,4 @@
import { import { authorizesOneOrMoreImageExtensions } from "discourse/lib/uploads";
authorizedExtensions,
authorizesAllExtensions,
authorizesOneOrMoreImageExtensions,
} from "discourse/lib/uploads";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import { BasePlugin } from "@uppy/core"; import { BasePlugin } from "@uppy/core";
import { resolveAllShortUrls } from "pretty-text/upload-short-url"; import { resolveAllShortUrls } from "pretty-text/upload-short-url";
@ -201,24 +197,6 @@ export default Component.extend(ComposerUploadUppy, {
}); });
}, },
@discourseComputed()
acceptsAllFormats() {
return (
this.capabilities.isIOS ||
authorizesAllExtensions(this.currentUser.staff, this.siteSettings)
);
},
@discourseComputed()
acceptedFormats() {
const extensions = authorizedExtensions(
this.currentUser.staff,
this.siteSettings
);
return extensions.map((ext) => `.${ext}`).join();
},
@bind @bind
_afterMentionComplete(value) { _afterMentionComplete(value) {
this.composer.set("reply", value); this.composer.set("reply", value);

View File

@ -1,25 +1,46 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
import computed, { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
import bootbox from "bootbox"; import bootbox from "bootbox";
import { isBlank } from "@ember/utils";
import {
authorizedExtensions,
authorizesAllExtensions,
} from "discourse/lib/uploads";
import { action } from "@ember/object";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
// This picker is intended to be used with UppyUploadMixin or with
// ComposerUploadUppy, which is why there are no change events registered
// for the input. They are handled by the uppy mixins directly.
//
// However, if you provide an onFilesPicked action to this component, the change
// binding will still be added, and the file type will be validated here. This
// is sometimes useful if you need to do something outside the uppy upload with
// the file, such as directly using JSON or CSV data from a file in JS.
export default Component.extend({ export default Component.extend({
fileInputId: null,
fileInputClass: null,
classNames: ["pick-files-button"], classNames: ["pick-files-button"],
acceptedFileTypes: null, acceptedFormatsOverride: null,
acceptAnyFile: empty("acceptedFileTypes"), allowMultiple: false,
showButton: false,
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
if (this.onFilesPicked) {
const fileInput = this.element.querySelector("input"); const fileInput = this.element.querySelector("input");
this.set("fileInput", fileInput); this.set("fileInput", fileInput);
fileInput.addEventListener("change", this.onChange, false); fileInput.addEventListener("change", this.onChange, false);
}
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
if (this.onFilesPicked) {
this.fileInput.removeEventListener("change", this.onChange); this.fileInput.removeEventListener("change", this.onChange);
}
}, },
@bind @bind
@ -28,33 +49,27 @@ export default Component.extend({
this._filesPicked(files); this._filesPicked(files);
}, },
@computed @discourseComputed()
acceptedFileTypesString() { acceptsAllFormats() {
if (!this.acceptedFileTypes) { return (
return null; this.capabilities.isIOS ||
} authorizesAllExtensions(this.currentUser.staff, this.siteSettings)
);
return this.acceptedFileTypes.join(",");
}, },
@computed @discourseComputed()
acceptedExtensions() { acceptedFormats() {
if (!this.acceptedFileTypes) { // the acceptedFormatsOverride can be a list of extensions or mime types
return null; if (!isBlank(this.acceptedFormatsOverride)) {
return this.acceptedFormatsOverride;
} }
return this.acceptedFileTypes const extensions = authorizedExtensions(
.filter((type) => type.startsWith(".")) this.currentUser.staff,
.map((type) => type.substring(1)); this.siteSettings
}, );
@computed return extensions.map((ext) => `.${ext}`).join();
acceptedMimeTypes() {
if (!this.acceptedFileTypes) {
return null;
}
return this.acceptedFileTypes.filter((type) => !type.startsWith("."));
}, },
@action @action
@ -79,25 +94,18 @@ export default Component.extend({
_haveAcceptedTypes(files) { _haveAcceptedTypes(files) {
for (const file of files) { for (const file of files) {
if ( if (!this._hasAcceptedExtensionOrType(file)) {
!(this._hasAcceptedExtension(file) || this._hasAcceptedMimeType(file))
) {
return false; return false;
} }
} }
return true; return true;
}, },
_hasAcceptedExtension(file) { _hasAcceptedExtensionOrType(file) {
const extension = this._fileExtension(file.name); const extension = this._fileExtension(file.name);
return ( return (
!this.acceptedExtensions || this.acceptedExtensions.includes(extension) this.acceptedFormats.includes(`.${extension}`) ||
); this.acceptedFormats.includes(file.type)
},
_hasAcceptedMimeType(file) {
return (
!this.acceptedMimeTypes || this.acceptedMimeTypes.includes(file.type)
); );
}, },

View File

@ -23,9 +23,5 @@
{{/d-editor}} {{/d-editor}}
{{#if allowUpload}} {{#if allowUpload}}
{{#if acceptsAllFormats}} {{pick-files-button fileInputId="file-uploader" allowMultiple=true}}
<input type="file" id="file-uploader" multiple>
{{else}}
<input type="file" id="file-uploader" accept={{acceptedFormats}} multiple>
{{/if}}
{{/if}} {{/if}}

View File

@ -1,6 +1,8 @@
{{d-button action=(action "openSystemFilePicker") label=label icon=icon}} {{#if showButton}}
{{#if acceptAnyFile}} {{d-button action=(action "openSystemFilePicker") label=label icon=icon}}
<input type="file"> {{/if}}
{{else}} {{#if acceptsAllFormats}}
<input type="file" accept={{acceptedFileTypesString}}> <input type="file" id={{fileInputId}} class={{fileInputClass}} multiple={{allowMultiple}}>
{{else}}
<input type="file" id={{fileInputId}} class={{fileInputClass}} accept={{acceptedFormats}} multiple={{allowMultiple}}>
{{/if}} {{/if}}

View File

@ -1,92 +0,0 @@
import componentTest, {
setupRenderingTest,
} from "discourse/tests/helpers/component-test";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import { triggerEvent } from "@ember/test-helpers";
import sinon from "sinon";
import bootbox from "bootbox";
function createBlob(mimeType, extension) {
const blob = new Blob(["content"], {
type: mimeType,
});
blob.name = `filename${extension}`;
return blob;
}
discourseModule(
"Integration | Component | pick-files-button",
function (hooks) {
const expectedExtension = ".json";
const expectedMimeType = "text/json";
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.set("acceptedFileTypes", [expectedExtension, expectedMimeType]);
this.set("onFilesPicked", () => {});
});
componentTest("it doesn't show alert if a file has a supported MIME type", {
skip: true,
template: hbs`
{{pick-files-button
acceptedFileTypes=this.acceptedFileTypes
onFilesPicked=this.onFilesPicked}}`,
async test(assert) {
sinon.stub(bootbox, "alert");
const wrongExtension = ".txt";
const file = createBlob(expectedMimeType, wrongExtension);
await triggerEvent("input[type='file']", "change", { files: [file] });
assert.ok(bootbox.alert.notCalled);
},
});
componentTest("it doesn't show alert if a file has a supported extension", {
skip: true,
template: hbs`
{{pick-files-button
acceptedFileTypes=this.acceptedFileTypes
onFilesPicked=this.onFilesPicked}}`,
async test(assert) {
sinon.stub(bootbox, "alert");
const wrongMimeType = "text/plain";
const file = createBlob(wrongMimeType, expectedExtension);
await triggerEvent("input[type='file']", "change", { files: [file] });
assert.ok(bootbox.alert.notCalled);
},
});
componentTest(
"it shows alert if a file has an unsupported extension and unsupported MIME type",
{
skip: true,
template: hbs`
{{pick-files-button
acceptedFileTypes=this.acceptedFileTypes
onFilesPicked=this.onFilesPicked}}`,
async test(assert) {
sinon.stub(bootbox, "alert");
const wrongExtension = ".txt";
const wrongMimeType = "text/plain";
const file = createBlob(wrongMimeType, wrongExtension);
await triggerEvent("input[type='file']", "change", { files: [file] });
assert.ok(bootbox.alert.calledOnce);
},
}
);
}
);