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:
parent
1598e6b489
commit
bf3260faea
|
@ -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);
|
||||||
|
|
|
@ -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)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
{{#if showButton}}
|
||||||
{{d-button action=(action "openSystemFilePicker") label=label icon=icon}}
|
{{d-button action=(action "openSystemFilePicker") label=label icon=icon}}
|
||||||
{{#if acceptAnyFile}}
|
{{/if}}
|
||||||
<input type="file">
|
{{#if acceptsAllFormats}}
|
||||||
{{else}}
|
<input type="file" id={{fileInputId}} class={{fileInputClass}} multiple={{allowMultiple}}>
|
||||||
<input type="file" accept={{acceptedFileTypesString}}>
|
{{else}}
|
||||||
|
<input type="file" id={{fileInputId}} class={{fileInputClass}} accept={{acceptedFormats}} multiple={{allowMultiple}}>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -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);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
Loading…
Reference in New Issue