2020-10-01 07:43:29 +02:00
|
|
|
import ComposerEditor from "discourse/components/composer-editor";
|
2020-10-06 11:30:24 +02:00
|
|
|
import { default as discourseComputed, on } from "discourse-common/utils/decorators";
|
2020-05-28 05:06:24 +02:00
|
|
|
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
2019-11-20 13:08:04 +01:00
|
|
|
import { throttle } from "@ember/runloop";
|
2020-10-02 06:40:10 +02:00
|
|
|
import { scheduleOnce, next } from "@ember/runloop";
|
2020-10-02 03:38:32 +02:00
|
|
|
import {
|
|
|
|
safariHacksDisabled,
|
|
|
|
caretPosition,
|
|
|
|
inCodeBlock,
|
|
|
|
} from "discourse/lib/utilities";
|
2020-10-01 07:43:29 +02:00
|
|
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
|
|
|
import { getToken } from "wizard/lib/ajax";
|
2020-10-02 06:40:10 +02:00
|
|
|
import {
|
|
|
|
validateUploadedFiles,
|
|
|
|
getUploadMarkdown
|
|
|
|
} from "discourse/lib/uploads";
|
2020-10-06 11:30:24 +02:00
|
|
|
import { cacheShortUploadUrl } from "pretty-text/upload-short-url";
|
|
|
|
import { alias } from "@ember/object/computed";
|
2020-10-06 12:05:24 +02:00
|
|
|
import { uploadIcon } from "discourse/lib/uploads";
|
2020-10-02 06:40:10 +02:00
|
|
|
|
|
|
|
const uploadMarkdownResolvers = [];
|
2019-11-20 13:08:04 +01:00
|
|
|
|
2020-10-01 07:43:29 +02:00
|
|
|
const uploadHandlers = [];
|
2019-11-20 13:08:04 +01:00
|
|
|
export default ComposerEditor.extend({
|
2020-10-01 07:43:29 +02:00
|
|
|
classNameBindings: ["fieldClass"],
|
|
|
|
allowUpload: true,
|
2019-11-20 13:08:04 +01:00
|
|
|
showLink: false,
|
2020-10-01 15:48:20 +02:00
|
|
|
showHyperlinkBox: false,
|
2019-11-20 13:08:04 +01:00
|
|
|
topic: null,
|
|
|
|
showToolbar: true,
|
|
|
|
focusTarget: "reply",
|
|
|
|
canWhisper: false,
|
2020-10-01 07:43:29 +02:00
|
|
|
lastValidatedAt: "lastValidatedAt",
|
2019-11-20 13:08:04 +01:00
|
|
|
popupMenuOptions: [],
|
2020-10-01 07:43:29 +02:00
|
|
|
draftStatus: "null",
|
2020-10-06 11:30:24 +02:00
|
|
|
replyPlaceholder: alias("field.placeholder"),
|
2020-10-01 07:43:29 +02:00
|
|
|
|
2019-11-20 13:08:04 +01:00
|
|
|
@on("didInsertElement")
|
|
|
|
_composerEditorInit() {
|
|
|
|
const $input = $(this.element.querySelector(".d-editor-input"));
|
|
|
|
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
|
2020-10-01 07:43:29 +02:00
|
|
|
|
2019-11-20 13:08:04 +01:00
|
|
|
if (this.siteSettings.enable_mentions) {
|
|
|
|
$input.autocomplete({
|
|
|
|
template: findRawTemplate("user-selector-autocomplete"),
|
2020-10-01 07:43:29 +02:00
|
|
|
dataSource: (term) => this.userSearchTerm.call(this, term),
|
2019-11-20 13:08:04 +01:00
|
|
|
key: "@",
|
2020-10-01 07:43:29 +02:00
|
|
|
transformComplete: (v) => v.username || v.name,
|
2020-10-02 03:38:32 +02:00
|
|
|
afterComplete: (value) => {
|
|
|
|
this.composer.set("reply", value);
|
2019-11-20 13:08:04 +01:00
|
|
|
scheduleOnce("afterRender", () => $input.blur().focus());
|
2020-10-01 07:43:29 +02:00
|
|
|
},
|
2020-10-02 03:38:32 +02:00
|
|
|
triggerRule: (textarea) =>
|
|
|
|
!inCodeBlock(textarea.value, caretPosition(textarea))
|
2019-11-20 13:08:04 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._enableAdvancedEditorPreviewSync()) {
|
|
|
|
this._initInputPreviewSync($input, $preview);
|
|
|
|
} else {
|
|
|
|
$input.on("scroll", () =>
|
|
|
|
throttle(this, this._syncEditorAndPreviewScroll, $input, $preview, 20)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._bindUploadTarget();
|
|
|
|
},
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-06 11:30:24 +02:00
|
|
|
@discourseComputed
|
|
|
|
allowedFileTypes() {
|
|
|
|
return this.siteSettings.authorized_extensions.split('|')
|
|
|
|
.map(ext => "." + ext)
|
|
|
|
.join(',')
|
|
|
|
},
|
|
|
|
|
2020-10-06 12:05:24 +02:00
|
|
|
@discourseComputed('currentUser')
|
|
|
|
uploadIcon(currentUser) {
|
|
|
|
return uploadIcon(false, this.siteSettings);
|
|
|
|
},
|
|
|
|
|
2020-10-01 07:43:29 +02:00
|
|
|
_setUploadPlaceholderSend() {
|
|
|
|
if (!this.composer.get("reply")) {
|
|
|
|
this.composer.set("reply", "");
|
|
|
|
}
|
|
|
|
this._super(...arguments);
|
|
|
|
},
|
|
|
|
|
|
|
|
_bindUploadTarget() {
|
|
|
|
this._super(...arguments);
|
|
|
|
const $element = $(this.element);
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-01 07:43:29 +02:00
|
|
|
$element.off("fileuploadsubmit");
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-01 07:43:29 +02:00
|
|
|
$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", { 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" };
|
|
|
|
data.formData.authenticity_token = getToken();
|
|
|
|
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);
|
|
|
|
|
|
|
|
this.setProperties({ uploadProgress: 0, isUploading });
|
|
|
|
|
|
|
|
return isUploading;
|
|
|
|
});
|
2020-10-02 02:33:01 +02:00
|
|
|
|
|
|
|
$element.on("fileuploadprogressall", (e, data) => {
|
|
|
|
this.set(
|
|
|
|
"uploadProgress",
|
|
|
|
parseInt((data.loaded / data.total) * 100, 10)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
$element.on("fileuploadfail", (e, data) => {
|
|
|
|
this._setUploadPlaceholderDone(data);
|
|
|
|
this._resetUpload(true);
|
|
|
|
|
|
|
|
const userCancelled = this._xhr && this._xhr._userCancelled;
|
|
|
|
this._xhr = null;
|
2020-10-02 06:40:10 +02:00
|
|
|
|
2020-10-02 02:33:01 +02:00
|
|
|
if (!userCancelled) {
|
|
|
|
displayErrorForUpload(data, this.siteSettings);
|
|
|
|
}
|
|
|
|
});
|
2020-10-02 06:40:10 +02:00
|
|
|
|
|
|
|
$element.on("fileuploadsend", (e, data) => {
|
|
|
|
this._pasted = false;
|
|
|
|
this._validUploads++;
|
|
|
|
|
|
|
|
this._setUploadPlaceholderSend(data);
|
|
|
|
|
|
|
|
this.appEvents.trigger("wizard-editor:insert-text", {
|
2020-10-06 11:30:24 +02:00
|
|
|
fieldId: this.field.id,
|
2020-10-02 06:40:10 +02:00
|
|
|
text: this.uploadPlaceholder
|
|
|
|
});
|
|
|
|
|
|
|
|
if (data.xhr && data.originalFiles.length === 1) {
|
|
|
|
this.set("isCancellable", true);
|
|
|
|
this._xhr = data.xhr();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$element.on("fileuploaddone", (e, data) => {
|
|
|
|
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(
|
|
|
|
"wizard-editor:replace-text", {
|
2020-10-06 11:30:24 +02:00
|
|
|
fieldId: this.field.id,
|
2020-10-02 06:40:10 +02:00
|
|
|
oldVal: this.uploadPlaceholder.trim(),
|
|
|
|
newVal: markdown
|
|
|
|
}
|
|
|
|
);
|
|
|
|
this._resetUpload(false);
|
|
|
|
} else {
|
|
|
|
this._resetUpload(true);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_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(
|
|
|
|
"wizard-editor:replace-text", {
|
2020-10-06 11:30:24 +02:00
|
|
|
fieldId: this.field.id,
|
2020-10-02 06:40:10 +02:00
|
|
|
oldVal: this.uploadPlaceholder,
|
|
|
|
newVal: ""
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._resetUploadFilenamePlaceholder();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_registerImageScaleButtonClick($preview) {
|
|
|
|
const imageScaleRegex = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
|
|
|
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
|
|
|
|
const index = parseInt($(e.target).parent().attr("data-image-index"), 10);
|
|
|
|
|
|
|
|
const scale = e.target.attributes["data-scale"].value;
|
|
|
|
const matchingPlaceholder = this.get("composer.reply").match(
|
|
|
|
imageScaleRegex
|
|
|
|
);
|
|
|
|
|
|
|
|
if (matchingPlaceholder) {
|
|
|
|
const match = matchingPlaceholder[index];
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
const replacement = match.replace(
|
|
|
|
imageScaleRegex,
|
|
|
|
`![$1|$2, ${scale}%$4]($5)`
|
|
|
|
);
|
|
|
|
|
|
|
|
this.appEvents.trigger(
|
|
|
|
"wizard-editor:replace-text",
|
|
|
|
{
|
2020-10-06 11:30:24 +02:00
|
|
|
fieldId: this.field.id,
|
2020-10-02 06:40:10 +02:00
|
|
|
oldVal: matchingPlaceholder[index],
|
|
|
|
newVal: replacement,
|
|
|
|
options: {
|
|
|
|
regex: imageScaleRegex,
|
|
|
|
index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
return;
|
|
|
|
});
|
2020-10-02 02:33:01 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
click(e) {
|
|
|
|
if ($(e.target).hasClass('wizard-composer-hyperlink')) {
|
|
|
|
this.set('showHyperlinkBox', false);
|
|
|
|
}
|
2019-11-20 13:08:04 +01:00
|
|
|
},
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2019-11-20 13:08:04 +01:00
|
|
|
actions: {
|
|
|
|
extraButtons(toolbar) {
|
2020-10-02 06:40:10 +02:00
|
|
|
const component = this;
|
|
|
|
|
2019-11-20 13:08:04 +01:00
|
|
|
if (this.allowUpload && this.uploadIcon && !this.site.mobileView) {
|
|
|
|
toolbar.addButton({
|
|
|
|
id: "upload",
|
|
|
|
group: "insertions",
|
|
|
|
icon: this.uploadIcon,
|
|
|
|
title: "upload",
|
2020-10-02 06:40:10 +02:00
|
|
|
sendAction: (event) => component.send("showUploadModal", event),
|
2019-11-20 13:08:04 +01:00
|
|
|
});
|
|
|
|
}
|
2020-10-01 15:48:20 +02:00
|
|
|
|
|
|
|
toolbar.addButton({
|
|
|
|
id: "link",
|
|
|
|
group: "insertions",
|
|
|
|
shortcut: "K",
|
|
|
|
trimLeading: true,
|
2020-10-02 02:33:01 +02:00
|
|
|
unshift: true,
|
2020-10-01 15:48:20 +02:00
|
|
|
sendAction: (event) => component.set("showHyperlinkBox", true),
|
|
|
|
});
|
2020-10-01 07:43:29 +02:00
|
|
|
},
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-01 07:43:29 +02:00
|
|
|
previewUpdated($preview) {
|
|
|
|
highlightSyntax($preview[0], this.siteSettings, this.session);
|
|
|
|
this._super(...arguments);
|
|
|
|
},
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-01 15:48:20 +02:00
|
|
|
addLink(linkName, linkUrl) {
|
|
|
|
let link = `[${linkName}](${linkUrl})`;
|
2020-10-02 06:40:10 +02:00
|
|
|
this.appEvents.trigger("wizard-editor:insert-text", {
|
2020-10-06 11:30:24 +02:00
|
|
|
fieldId: this.field.id,
|
2020-10-02 06:40:10 +02:00
|
|
|
text: link
|
|
|
|
});
|
2020-10-01 15:48:20 +02:00
|
|
|
this.set("showHyperlinkBox", false);
|
|
|
|
},
|
2020-10-02 02:33:01 +02:00
|
|
|
|
2020-10-01 15:48:20 +02:00
|
|
|
hideBox() {
|
|
|
|
this.set("showHyperlinkBox", false);
|
|
|
|
},
|
2020-10-02 06:40:10 +02:00
|
|
|
|
|
|
|
showUploadModal() {
|
|
|
|
$(this.element.querySelector(".wizard-composer-upload")).trigger("click");
|
|
|
|
}
|
2020-10-01 07:43:29 +02:00
|
|
|
},
|
|
|
|
});
|