mirror of
https://github.com/discourse/discourse.git
synced 2025-02-21 03:19:10 +00:00
DEV: Move composer-editor upload functions into mixin (#13923)
This PR moves all the upload related functions into a new ComposerUpload mixin that is extended by the composer-editor component. This is being done so I can introduce a ComposerUploadUppy mixin that overrides functions in the regular ComposerUpload mixin, via a new composer-editor-uppy component that inherits from ComposerEditor. The proposed structure, which will be in the next PR, looks like this: composer-editor-uppy ```javascript import ComposerEditor from "discourse/components/composer-editor" import ComposerUploadUppy from "discourse/mixins/composer-upload-uppy" export default ComposerEditor.extend(ComposerUploadUppy, { layoutName: "components/composer-editor" }); ``` This way the new composer-editor is a dumb component purely used for testing uppy safely, and within the template for composer.hbs we do this: ```javascript @discourseComputed composerComponent() { return this.siteSettings.enable_experimental_composer_uploader ? "composer-editor-uppy" : "composer-editor"; }, ``` ```handlebars {{component composerComponent ...}} ``` This is the only way I can think to do it, because it is not possible to access the site settings when the component is first declared I can't do something like: ```javascript const uploaderMixin = this.siteSettings.use_experimental_uploader? ComposerUploaderUppy : ComposerUploader; Component.extend(uploaderMixin, {}); ``` An additional change in this PR is explicitly passing in these four plugin data structures to the composer-editor Component, rather than relying on JS closures which the mixin cannot do: * uploadMarkdownResolvers * uploadProcessorActions * uploadProcessorQueue * uploadHandlers
This commit is contained in:
parent
4ec2c1e9a9
commit
8eabbdae5c
@ -2,17 +2,10 @@ import {
|
||||
authorizedExtensions,
|
||||
authorizesAllExtensions,
|
||||
authorizesOneOrMoreImageExtensions,
|
||||
displayErrorForUpload,
|
||||
getUploadMarkdown,
|
||||
validateUploadedFiles,
|
||||
} from "discourse/lib/uploads";
|
||||
import {
|
||||
cacheShortUploadUrl,
|
||||
resolveAllShortUrls,
|
||||
} from "pretty-text/upload-short-url";
|
||||
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
|
||||
import {
|
||||
caretPosition,
|
||||
clipboardHelpers,
|
||||
formatUsername,
|
||||
inCodeBlock,
|
||||
tinyAvatar,
|
||||
@ -29,16 +22,15 @@ import {
|
||||
fetchUnseenMentions,
|
||||
linkSeenMentions,
|
||||
} from "discourse/lib/link-mentions";
|
||||
import { later, next, run, schedule, throttle } from "@ember/runloop";
|
||||
import { later, next, schedule, throttle } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import Composer from "discourse/models/composer";
|
||||
import ComposerUpload from "discourse/mixins/composer-upload";
|
||||
import EmberObject from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import bootbox from "bootbox";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||
@ -77,31 +69,16 @@ export function cleanUpComposerUploadMarkdownResolver() {
|
||||
uploadMarkdownResolvers = [];
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
export default Component.extend(ComposerUpload, {
|
||||
classNameBindings: ["showToolbar:toolbar-visible", ":wmd-controls"],
|
||||
|
||||
uploadProgress: 0,
|
||||
_xhr: null,
|
||||
shouldBuildScrollMap: true,
|
||||
scrollMap: null,
|
||||
uploadFilenamePlaceholder: null,
|
||||
uploadProcessingFilename: null,
|
||||
uploadProcessingPlaceholdersAdded: false,
|
||||
|
||||
@discourseComputed("uploadFilenamePlaceholder")
|
||||
uploadPlaceholder(uploadFilenamePlaceholder) {
|
||||
const clipboard = I18n.t("clipboard");
|
||||
const filename = uploadFilenamePlaceholder
|
||||
? uploadFilenamePlaceholder
|
||||
: clipboard;
|
||||
|
||||
let placeholder = `[${I18n.t("uploading_filename", { filename })}]()\n`;
|
||||
if (!this._cursorIsOnEmptyLine()) {
|
||||
placeholder = `\n${placeholder}`;
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
},
|
||||
uploadMarkdownResolvers,
|
||||
uploadProcessorActions,
|
||||
uploadProcessorQueue,
|
||||
uploadHandlers,
|
||||
|
||||
@discourseComputed("composer.requiredCategoryMissing")
|
||||
replyPlaceholder(requiredCategoryMissing) {
|
||||
@ -130,20 +107,6 @@ export default Component.extend({
|
||||
return requiredCategoryMissing && replyLength === 0;
|
||||
},
|
||||
|
||||
@observes("composer.uploadCancelled")
|
||||
_cancelUpload() {
|
||||
if (!this.get("composer.uploadCancelled")) {
|
||||
return;
|
||||
}
|
||||
this.set("composer.uploadCancelled", false);
|
||||
|
||||
if (this._xhr) {
|
||||
this._xhr._userCancelled = true;
|
||||
this._xhr.abort();
|
||||
}
|
||||
this._resetUpload(true);
|
||||
},
|
||||
|
||||
@observes("focusTarget")
|
||||
setFocus() {
|
||||
if (this.focusTarget === "editor") {
|
||||
@ -305,54 +268,6 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_setUploadPlaceholderSend(data) {
|
||||
const filename = this._filenamePlaceholder(data);
|
||||
this.set("uploadFilenamePlaceholder", filename);
|
||||
|
||||
// when adding two separate files with the same filename search for matching
|
||||
// placeholder already existing in the editor ie [Uploading: test.png...]
|
||||
// and add order nr to the next one: [Uploading: test.png(1)...]
|
||||
const escapedFilename = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regexString = `\\[${I18n.t("uploading_filename", {
|
||||
filename: escapedFilename + "(?:\\()?([0-9])?(?:\\))?",
|
||||
})}\\]\\(\\)`;
|
||||
const globalRegex = new RegExp(regexString, "g");
|
||||
const matchingPlaceholder = this.get("composer.reply").match(globalRegex);
|
||||
if (matchingPlaceholder) {
|
||||
// get last matching placeholder and its consecutive nr in regex
|
||||
// capturing group and apply +1 to the placeholder
|
||||
const lastMatch = matchingPlaceholder[matchingPlaceholder.length - 1];
|
||||
const regex = new RegExp(regexString);
|
||||
const orderNr = regex.exec(lastMatch)[1]
|
||||
? parseInt(regex.exec(lastMatch)[1], 10) + 1
|
||||
: 1;
|
||||
data.orderNr = orderNr;
|
||||
const filenameWithOrderNr = `${filename}(${orderNr})`;
|
||||
this.set("uploadFilenamePlaceholder", filenameWithOrderNr);
|
||||
}
|
||||
},
|
||||
|
||||
_setUploadPlaceholderDone(data) {
|
||||
const filename = this._filenamePlaceholder(data);
|
||||
const filenameWithSize = `${filename} (${data.total})`;
|
||||
this.set("uploadFilenamePlaceholder", filenameWithSize);
|
||||
|
||||
if (data.orderNr) {
|
||||
const filenameWithOrderNr = `${filename}(${data.orderNr})`;
|
||||
this.set("uploadFilenamePlaceholder", filenameWithOrderNr);
|
||||
} else {
|
||||
this.set("uploadFilenamePlaceholder", filename);
|
||||
}
|
||||
},
|
||||
|
||||
_filenamePlaceholder(data) {
|
||||
return data.files[0].name.replace(/\u200B-\u200D\uFEFF]/g, "");
|
||||
},
|
||||
|
||||
_resetUploadFilenamePlaceholder() {
|
||||
this.set("uploadFilenamePlaceholder", null);
|
||||
},
|
||||
|
||||
_enableAdvancedEditorPreviewSync() {
|
||||
return this.siteSettings.enable_advanced_editor_preview_sync;
|
||||
},
|
||||
@ -645,237 +560,6 @@ export default Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
_resetUpload(removePlaceholder) {
|
||||
next(() => {
|
||||
if (this._validUploads > 0) {
|
||||
this._validUploads--;
|
||||
}
|
||||
if (this._validUploads === 0) {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: false,
|
||||
isCancellable: false,
|
||||
});
|
||||
}
|
||||
if (removePlaceholder) {
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
this.uploadPlaceholder,
|
||||
""
|
||||
);
|
||||
}
|
||||
this._resetUploadFilenamePlaceholder();
|
||||
});
|
||||
},
|
||||
|
||||
_bindUploadTarget() {
|
||||
this._unbindUploadTarget(); // in case it's still bound, let's clean it up first
|
||||
this._pasted = false;
|
||||
|
||||
const $element = $(this.element);
|
||||
|
||||
$.blueimp.fileupload.prototype.processActions = uploadProcessorActions;
|
||||
|
||||
$element.fileupload({
|
||||
url: getURL(`/uploads.json?client_id=${this.messageBus.clientId}`),
|
||||
dataType: "json",
|
||||
pasteZone: $element,
|
||||
processQueue: uploadProcessorQueue,
|
||||
});
|
||||
|
||||
$element
|
||||
.on("fileuploadprocessstart", () => {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: true,
|
||||
isProcessingUpload: true,
|
||||
isCancellable: false,
|
||||
});
|
||||
})
|
||||
.on("fileuploadprocess", (e, data) => {
|
||||
if (!this.uploadProcessingPlaceholdersAdded) {
|
||||
data.originalFiles
|
||||
.map((f) => f.name)
|
||||
.forEach((f) => {
|
||||
this.appEvents.trigger(
|
||||
"composer:insert-text",
|
||||
`[${I18n.t("processing_filename", {
|
||||
filename: f,
|
||||
})}]()\n`
|
||||
);
|
||||
});
|
||||
this.uploadProcessingPlaceholdersAdded = true;
|
||||
}
|
||||
this.uploadProcessingFilename = data.files[data.index].name;
|
||||
})
|
||||
.on("fileuploadprocessstop", () => {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: false,
|
||||
isProcessingUpload: false,
|
||||
isCancellable: false,
|
||||
});
|
||||
this.uploadProcessingPlaceholdersAdded = false;
|
||||
});
|
||||
|
||||
$element.on("fileuploadpaste", (e) => {
|
||||
this._pasted = true;
|
||||
|
||||
if (!$(".d-editor-input").is(":focus")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { canUpload, canPasteHtml, types } = clipboardHelpers(e, {
|
||||
siteSettings: this.siteSettings,
|
||||
canUpload: true,
|
||||
});
|
||||
|
||||
if (!canUpload || canPasteHtml || types.includes("text/plain")) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$element.on("fileuploadsubmit", (e, data) => {
|
||||
const max = this.siteSettings.simultaneous_uploads;
|
||||
|
||||
// Limit the number of simultaneous uploads
|
||||
if (max > 0 && data.files.length > max) {
|
||||
bootbox.alert(
|
||||
I18n.t("post.errors.too_many_dragged_and_dropped_files", {
|
||||
count: max,
|
||||
})
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for a matching file upload handler contributed from a plugin
|
||||
const matcher = (handler) => {
|
||||
const ext = handler.extensions.join("|");
|
||||
const regex = new RegExp(`\\.(${ext})$`, "i");
|
||||
return regex.test(data.files[0].name);
|
||||
};
|
||||
|
||||
const matchingHandler = uploadHandlers.find(matcher);
|
||||
if (data.files.length === 1 && matchingHandler) {
|
||||
if (!matchingHandler.method(data.files[0], this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no plugin, continue as normal
|
||||
const isPrivateMessage = this.get("composer.privateMessage");
|
||||
|
||||
data.formData = { type: "composer" };
|
||||
if (isPrivateMessage) {
|
||||
data.formData.for_private_message = true;
|
||||
}
|
||||
if (this._pasted) {
|
||||
data.formData.pasted = true;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
user: this.currentUser,
|
||||
siteSettings: this.siteSettings,
|
||||
isPrivateMessage,
|
||||
allowStaffToUploadAnyFileInPm: this.siteSettings
|
||||
.allow_staff_to_upload_any_file_in_pm,
|
||||
};
|
||||
|
||||
const isUploading = validateUploadedFiles(data.files, opts);
|
||||
|
||||
run(() => {
|
||||
this.setProperties({ uploadProgress: 0, isUploading });
|
||||
});
|
||||
|
||||
return isUploading;
|
||||
});
|
||||
|
||||
$element.on("fileuploadprogressall", (e, data) => {
|
||||
run(() => {
|
||||
this.set(
|
||||
"uploadProgress",
|
||||
parseInt((data.loaded / data.total) * 100, 10)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploadsend", (e, data) => {
|
||||
run(() => {
|
||||
this._pasted = false;
|
||||
this._validUploads++;
|
||||
|
||||
this._setUploadPlaceholderSend(data);
|
||||
|
||||
if (this.uploadProcessingFilename) {
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
`[${I18n.t("processing_filename", {
|
||||
filename: this.uploadProcessingFilename,
|
||||
})}]()`,
|
||||
this.uploadPlaceholder.trim()
|
||||
);
|
||||
this.uploadProcessingFilename = null;
|
||||
} else {
|
||||
this.appEvents.trigger(
|
||||
"composer:insert-text",
|
||||
this.uploadPlaceholder
|
||||
);
|
||||
}
|
||||
|
||||
if (data.xhr && data.originalFiles.length === 1) {
|
||||
this.set("isCancellable", true);
|
||||
this._xhr = data.xhr();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploaddone", (e, data) => {
|
||||
run(() => {
|
||||
let upload = data.result;
|
||||
this._setUploadPlaceholderDone(data);
|
||||
if (!this._xhr || !this._xhr._userCancelled) {
|
||||
const markdown = uploadMarkdownResolvers.reduce(
|
||||
(md, resolver) => resolver(upload) || md,
|
||||
getUploadMarkdown(upload)
|
||||
);
|
||||
|
||||
cacheShortUploadUrl(upload.short_url, upload);
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
this.uploadPlaceholder.trim(),
|
||||
markdown
|
||||
);
|
||||
this._resetUpload(false);
|
||||
} else {
|
||||
this._resetUpload(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploadfail", (e, data) => {
|
||||
run(() => {
|
||||
this._setUploadPlaceholderDone(data);
|
||||
this._resetUpload(true);
|
||||
|
||||
const userCancelled = this._xhr && this._xhr._userCancelled;
|
||||
this._xhr = null;
|
||||
|
||||
if (!userCancelled) {
|
||||
displayErrorForUpload(data, this.siteSettings, data.files[0].name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (this.site.mobileView) {
|
||||
const uploadButton = document.getElementById("mobile-file-upload");
|
||||
uploadButton.addEventListener(
|
||||
"click",
|
||||
() => document.getElementById("file-uploader").click(),
|
||||
false
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_registerImageScaleButtonClick($preview) {
|
||||
// original string ``
|
||||
// group 1 `image|foo=bar`
|
||||
@ -920,20 +604,6 @@ export default Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_unbindUploadTarget() {
|
||||
this._validUploads = 0;
|
||||
$("#reply-control .mobile-file-upload").off("click.uploader");
|
||||
this.messageBus.unsubscribe("/uploads/composer");
|
||||
const $uploadTarget = $(this.element);
|
||||
try {
|
||||
$uploadTarget.fileupload("destroy");
|
||||
} catch (e) {
|
||||
/* wasn't initialized yet */
|
||||
}
|
||||
$uploadTarget.off();
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_composerClosed() {
|
||||
this.appEvents.trigger("composer:will-close");
|
||||
@ -950,10 +620,6 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
showUploadSelector(toolbarEvent) {
|
||||
this.send("showUploadSelector", toolbarEvent);
|
||||
},
|
||||
|
||||
onExpandPopupMenuOptions(toolbarEvent) {
|
||||
const selected = toolbarEvent.selected;
|
||||
toolbarEvent.selectText(selected.start, selected.end - selected.start);
|
||||
|
355
app/assets/javascripts/discourse/app/mixins/composer-upload.js
Normal file
355
app/assets/javascripts/discourse/app/mixins/composer-upload.js
Normal file
@ -0,0 +1,355 @@
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import I18n from "I18n";
|
||||
import { next, run } from "@ember/runloop";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { clipboardHelpers } from "discourse/lib/utilities";
|
||||
import discourseComputed, {
|
||||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import {
|
||||
displayErrorForUpload,
|
||||
getUploadMarkdown,
|
||||
validateUploadedFiles,
|
||||
} from "discourse/lib/uploads";
|
||||
import { cacheShortUploadUrl } from "pretty-text/upload-short-url";
|
||||
|
||||
export default Mixin.create({
|
||||
_xhr: null,
|
||||
uploadProgress: 0,
|
||||
uploadFilenamePlaceholder: null,
|
||||
uploadProcessingFilename: null,
|
||||
uploadProcessingPlaceholdersAdded: false,
|
||||
|
||||
@discourseComputed("uploadFilenamePlaceholder")
|
||||
uploadPlaceholder(uploadFilenamePlaceholder) {
|
||||
const clipboard = I18n.t("clipboard");
|
||||
const filename = uploadFilenamePlaceholder
|
||||
? uploadFilenamePlaceholder
|
||||
: clipboard;
|
||||
|
||||
let placeholder = `[${I18n.t("uploading_filename", { filename })}]()\n`;
|
||||
if (!this._cursorIsOnEmptyLine()) {
|
||||
placeholder = `\n${placeholder}`;
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
},
|
||||
|
||||
@observes("composer.uploadCancelled")
|
||||
_cancelUpload() {
|
||||
if (!this.get("composer.uploadCancelled")) {
|
||||
return;
|
||||
}
|
||||
this.set("composer.uploadCancelled", false);
|
||||
|
||||
if (this._xhr) {
|
||||
this._xhr._userCancelled = true;
|
||||
this._xhr.abort();
|
||||
}
|
||||
this._resetUpload(true);
|
||||
},
|
||||
|
||||
_setUploadPlaceholderSend(data) {
|
||||
const filename = this._filenamePlaceholder(data);
|
||||
this.set("uploadFilenamePlaceholder", filename);
|
||||
|
||||
// when adding two separate files with the same filename search for matching
|
||||
// placeholder already existing in the editor ie [Uploading: test.png...]
|
||||
// and add order nr to the next one: [Uploading: test.png(1)...]
|
||||
const escapedFilename = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regexString = `\\[${I18n.t("uploading_filename", {
|
||||
filename: escapedFilename + "(?:\\()?([0-9])?(?:\\))?",
|
||||
})}\\]\\(\\)`;
|
||||
const globalRegex = new RegExp(regexString, "g");
|
||||
const matchingPlaceholder = this.get("composer.reply").match(globalRegex);
|
||||
if (matchingPlaceholder) {
|
||||
// get last matching placeholder and its consecutive nr in regex
|
||||
// capturing group and apply +1 to the placeholder
|
||||
const lastMatch = matchingPlaceholder[matchingPlaceholder.length - 1];
|
||||
const regex = new RegExp(regexString);
|
||||
const orderNr = regex.exec(lastMatch)[1]
|
||||
? parseInt(regex.exec(lastMatch)[1], 10) + 1
|
||||
: 1;
|
||||
data.orderNr = orderNr;
|
||||
const filenameWithOrderNr = `${filename}(${orderNr})`;
|
||||
this.set("uploadFilenamePlaceholder", filenameWithOrderNr);
|
||||
}
|
||||
},
|
||||
|
||||
_setUploadPlaceholderDone(data) {
|
||||
const filename = this._filenamePlaceholder(data);
|
||||
|
||||
if (data.orderNr) {
|
||||
const filenameWithOrderNr = `${filename}(${data.orderNr})`;
|
||||
this.set("uploadFilenamePlaceholder", filenameWithOrderNr);
|
||||
} else {
|
||||
this.set("uploadFilenamePlaceholder", filename);
|
||||
}
|
||||
},
|
||||
|
||||
_filenamePlaceholder(data) {
|
||||
if (data.files) {
|
||||
return data.files[0].name.replace(/\u200B-\u200D\uFEFF]/g, "");
|
||||
} else {
|
||||
return data.name.replace(/\u200B-\u200D\uFEFF]/g, "");
|
||||
}
|
||||
},
|
||||
|
||||
_resetUploadFilenamePlaceholder() {
|
||||
this.set("uploadFilenamePlaceholder", null);
|
||||
},
|
||||
|
||||
_resetUpload(removePlaceholder) {
|
||||
next(() => {
|
||||
if (this._validUploads > 0) {
|
||||
this._validUploads--;
|
||||
}
|
||||
if (this._validUploads === 0) {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: false,
|
||||
isCancellable: false,
|
||||
});
|
||||
}
|
||||
if (removePlaceholder) {
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
this.uploadPlaceholder,
|
||||
""
|
||||
);
|
||||
}
|
||||
this._resetUploadFilenamePlaceholder();
|
||||
});
|
||||
},
|
||||
|
||||
_bindUploadTarget() {
|
||||
this._unbindUploadTarget(); // in case it's still bound, let's clean it up first
|
||||
this._pasted = false;
|
||||
|
||||
const $element = $(this.element);
|
||||
|
||||
$.blueimp.fileupload.prototype.processActions = this.uploadProcessorActions;
|
||||
|
||||
$element.fileupload({
|
||||
url: getURL(`/uploads.json?client_id=${this.messageBus.clientId}`),
|
||||
dataType: "json",
|
||||
pasteZone: $element,
|
||||
processQueue: this.uploadProcessorQueue,
|
||||
});
|
||||
|
||||
$element
|
||||
.on("fileuploadprocessstart", () => {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: true,
|
||||
isProcessingUpload: true,
|
||||
isCancellable: false,
|
||||
});
|
||||
})
|
||||
.on("fileuploadprocess", (e, data) => {
|
||||
if (!this.uploadProcessingPlaceholdersAdded) {
|
||||
data.originalFiles
|
||||
.map((f) => f.name)
|
||||
.forEach((f) => {
|
||||
this.appEvents.trigger(
|
||||
"composer:insert-text",
|
||||
`[${I18n.t("processing_filename", {
|
||||
filename: f,
|
||||
})}]()\n`
|
||||
);
|
||||
});
|
||||
this.uploadProcessingPlaceholdersAdded = true;
|
||||
}
|
||||
this.uploadProcessingFilename = data.files[data.index].name;
|
||||
})
|
||||
.on("fileuploadprocessstop", () => {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: false,
|
||||
isProcessingUpload: false,
|
||||
isCancellable: false,
|
||||
});
|
||||
this.uploadProcessingPlaceholdersAdded = false;
|
||||
});
|
||||
|
||||
$element.on("fileuploadpaste", (e) => {
|
||||
this._pasted = true;
|
||||
|
||||
if (!$(".d-editor-input").is(":focus")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { canUpload, canPasteHtml, types } = clipboardHelpers(e, {
|
||||
siteSettings: this.siteSettings,
|
||||
canUpload: true,
|
||||
});
|
||||
|
||||
if (!canUpload || canPasteHtml || types.includes("text/plain")) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$element.on("fileuploadsubmit", (e, data) => {
|
||||
const max = this.siteSettings.simultaneous_uploads;
|
||||
|
||||
// Limit the number of simultaneous uploads
|
||||
if (max > 0 && data.files.length > max) {
|
||||
bootbox.alert(
|
||||
I18n.t("post.errors.too_many_dragged_and_dropped_files", {
|
||||
count: max,
|
||||
})
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for a matching file upload handler contributed from a plugin
|
||||
const matcher = (handler) => {
|
||||
const ext = handler.extensions.join("|");
|
||||
const regex = new RegExp(`\\.(${ext})$`, "i");
|
||||
return regex.test(data.files[0].name);
|
||||
};
|
||||
|
||||
const matchingHandler = this.uploadHandlers.find(matcher);
|
||||
if (data.files.length === 1 && matchingHandler) {
|
||||
if (!matchingHandler.method(data.files[0], this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no plugin, continue as normal
|
||||
const isPrivateMessage = this.get("composer.privateMessage");
|
||||
|
||||
data.formData = { type: "composer" };
|
||||
if (isPrivateMessage) {
|
||||
data.formData.for_private_message = true;
|
||||
}
|
||||
if (this._pasted) {
|
||||
data.formData.pasted = true;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
user: this.currentUser,
|
||||
siteSettings: this.siteSettings,
|
||||
isPrivateMessage,
|
||||
allowStaffToUploadAnyFileInPm: this.siteSettings
|
||||
.allow_staff_to_upload_any_file_in_pm,
|
||||
};
|
||||
|
||||
const isUploading = validateUploadedFiles(data.files, opts);
|
||||
|
||||
run(() => {
|
||||
this.setProperties({ uploadProgress: 0, isUploading });
|
||||
});
|
||||
|
||||
return isUploading;
|
||||
});
|
||||
|
||||
$element.on("fileuploadprogressall", (e, data) => {
|
||||
run(() => {
|
||||
this.set(
|
||||
"uploadProgress",
|
||||
parseInt((data.loaded / data.total) * 100, 10)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploadsend", (e, data) => {
|
||||
run(() => {
|
||||
this._pasted = false;
|
||||
this._validUploads++;
|
||||
|
||||
this._setUploadPlaceholderSend(data);
|
||||
|
||||
if (this.uploadProcessingFilename) {
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
`[${I18n.t("processing_filename", {
|
||||
filename: this.uploadProcessingFilename,
|
||||
})}]()`,
|
||||
this.uploadPlaceholder.trim()
|
||||
);
|
||||
this.uploadProcessingFilename = null;
|
||||
} else {
|
||||
this.appEvents.trigger(
|
||||
"composer:insert-text",
|
||||
this.uploadPlaceholder
|
||||
);
|
||||
}
|
||||
|
||||
if (data.xhr && data.originalFiles.length === 1) {
|
||||
this.set("isCancellable", true);
|
||||
this._xhr = data.xhr();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploaddone", (e, data) => {
|
||||
run(() => {
|
||||
let upload = data.result;
|
||||
this._setUploadPlaceholderDone(data);
|
||||
if (!this._xhr || !this._xhr._userCancelled) {
|
||||
const markdown = this.uploadMarkdownResolvers.reduce(
|
||||
(md, resolver) => resolver(upload) || md,
|
||||
getUploadMarkdown(upload)
|
||||
);
|
||||
|
||||
cacheShortUploadUrl(upload.short_url, upload);
|
||||
this.appEvents.trigger(
|
||||
"composer:replace-text",
|
||||
this.uploadPlaceholder.trim(),
|
||||
markdown
|
||||
);
|
||||
this._resetUpload(false);
|
||||
} else {
|
||||
this._resetUpload(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$element.on("fileuploadfail", (e, data) => {
|
||||
run(() => {
|
||||
this._setUploadPlaceholderDone(data);
|
||||
this._resetUpload(true);
|
||||
|
||||
const userCancelled = this._xhr && this._xhr._userCancelled;
|
||||
this._xhr = null;
|
||||
|
||||
if (!userCancelled) {
|
||||
displayErrorForUpload(data, this.siteSettings, data.files[0].name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._bindMobileUploadButton();
|
||||
},
|
||||
|
||||
_bindMobileUploadButton() {
|
||||
if (this.site.mobileView) {
|
||||
const uploadButton = document.getElementById("mobile-file-upload");
|
||||
uploadButton.addEventListener(
|
||||
"click",
|
||||
() => document.getElementById("file-uploader").click(),
|
||||
false
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_unbindUploadTarget() {
|
||||
this._validUploads = 0;
|
||||
$("#reply-control .mobile-file-upload").off("click.uploader");
|
||||
this.messageBus.unsubscribe("/uploads/composer");
|
||||
const $uploadTarget = $(this.element);
|
||||
try {
|
||||
$uploadTarget.fileupload("destroy");
|
||||
} catch (e) {
|
||||
/* wasn't initialized yet */
|
||||
}
|
||||
$uploadTarget.off();
|
||||
},
|
||||
|
||||
showUploadSelector(toolbarEvent) {
|
||||
this.send("showUploadSelector", toolbarEvent);
|
||||
},
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user