DEV: Move composer-editor upload functions into mixin (#13923)

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

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:

@discourseComputed
composerComponent() {
  return this.siteSettings.enable_experimental_composer_uploader
    ? "composer-editor-uppy"
    : "composer-editor";
},
{{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:

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
diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index aa8cb1d..c9a1a2d 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -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, {

[... diff too long, it was truncated ...]

GitHub sha: 8eabbdae5c8582654d315b71b665b2dc12d284c7

This commit appears in #13923 which was approved by lis2. It was merged by martin.