Fix inserting multiple media files via clipboard
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Media / Manager / Editor.js
CommitLineData
56eb7314 1/**
306b6bdd 2 * Provides the media manager dialog for selecting media for Redactor editors.
56eb7314 3 *
ed355f64
MS
4 * @author Matthias Schmidt
5 * @copyright 2001-2021 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 * @module WoltLabSuite/Core/Media/Manager/Editor
56eb7314 8 */
fbb238d9 9define(["require", "exports", "tslib", "./Base", "../../Core", "../../Event/Handler", "../../Dom/Traverse", "../../Language", "../../Ui/Dialog", "../../Controller/Clipboard", "../../Dom/Util"], function (require, exports, tslib_1, Base_1, Core, EventHandler, DomTraverse, Language, UiDialog, Clipboard, Util_1) {
50aa3a01 10 "use strict";
ed355f64
MS
11 Base_1 = tslib_1.__importDefault(Base_1);
12 Core = tslib_1.__importStar(Core);
13 EventHandler = tslib_1.__importStar(EventHandler);
14 DomTraverse = tslib_1.__importStar(DomTraverse);
15 Language = tslib_1.__importStar(Language);
16 UiDialog = tslib_1.__importStar(UiDialog);
17 Clipboard = tslib_1.__importStar(Clipboard);
fbb238d9 18 Util_1 = tslib_1.__importDefault(Util_1);
ed355f64
MS
19 class MediaManagerEditor extends Base_1.default {
20 constructor(options) {
21 options = Core.extend({
22 callbackInsert: null,
23 }, options);
24 super(options);
25 this._forceClipboard = true;
26 this._activeButton = null;
27 const context = this._options.editor ? this._options.editor.core.toolbar()[0] : undefined;
28 this._buttons = (context || window.document).getElementsByClassName(this._options.buttonClass || "jsMediaEditorButton");
29 Array.from(this._buttons).forEach((button) => {
30 button.addEventListener("click", (ev) => this._click(ev));
50aa3a01 31 });
ed355f64
MS
32 this._mediaToInsert = new Map();
33 this._mediaToInsertByClipboard = false;
34 this._uploadData = null;
35 this._uploadId = null;
36 if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
37 const editorId = this._options.editor.$editor[0].dataset.elementId;
38 const uuid1 = EventHandler.add("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, (data) => this._editorUpload(data));
39 const uuid2 = EventHandler.add("com.woltlab.wcf.redactor2", `pasteFromClipboard_${editorId}`, (data) => this._editorUpload(data));
40 EventHandler.add("com.woltlab.wcf.redactor2", `destroy_${editorId}`, () => {
41 EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid1);
42 EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid2);
43 });
44 EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => this._mediaUploaded(data));
45 }
50aa3a01 46 }
ed355f64
MS
47 _addButtonEventListeners() {
48 super._addButtonEventListeners();
49 if (!this._mediaManagerMediaList) {
50aa3a01 50 return;
ed355f64
MS
51 }
52 DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
53 const insertIcon = listItem.querySelector(".jsMediaInsertButton");
50aa3a01 54 if (insertIcon) {
ed355f64
MS
55 insertIcon.classList.remove("jsMediaInsertButton");
56 insertIcon.addEventListener("click", (ev) => this._openInsertDialog(ev));
50aa3a01 57 }
ed355f64
MS
58 });
59 }
50aa3a01
TD
60 /**
61 * Builds the dialog to setup inserting media files.
62 */
ed355f64
MS
63 _buildInsertDialog() {
64 let thumbnailOptions = "";
65 this._getThumbnailSizes().forEach((thumbnailSize) => {
66 thumbnailOptions +=
67 '<option value="' +
68 thumbnailSize +
69 '">' +
70 Language.get("wcf.media.insert.imageSize." + thumbnailSize) +
71 "</option>";
72 });
73 thumbnailOptions += '<option value="original">' + Language.get("wcf.media.insert.imageSize.original") + "</option>";
74 const dialog = `
75 <div class="section">
76 <dl class="thumbnailSizeSelection">
77 <dt>${Language.get("wcf.media.insert.imageSize")}</dt>
78 <dd>
79 <select name="thumbnailSize">
80 ${thumbnailOptions}
81 </select>
82 </dd>
83 </dl>
84 </div>
85 <div class="formSubmit">
86 <button class="buttonPrimary">${Language.get("wcf.global.button.insert")}</button>
87 </div>`;
50aa3a01 88 UiDialog.open({
ed355f64 89 _dialogSetup: () => {
50aa3a01
TD
90 return {
91 id: this._getInsertDialogId(),
92 options: {
fbb238d9 93 onClose: () => this._editorClose(),
ed355f64
MS
94 onSetup: (content) => {
95 content.querySelector(".buttonPrimary").addEventListener("click", (ev) => this._insertMedia(ev));
fbb238d9 96 Util_1.default.show(content.querySelector(".thumbnailSizeSelection"));
ed355f64
MS
97 },
98 title: Language.get("wcf.media.insert"),
50aa3a01 99 },
ed355f64 100 source: dialog,
50aa3a01 101 };
ed355f64 102 },
50aa3a01 103 });
ed355f64
MS
104 }
105 _click(event) {
50aa3a01 106 this._activeButton = event.currentTarget;
ed355f64
MS
107 super._click(event);
108 }
109 _dialogShow() {
110 super._dialogShow();
50aa3a01
TD
111 // check if data needs to be uploaded
112 if (this._uploadData) {
ed355f64
MS
113 const fileUploadData = this._uploadData;
114 if (fileUploadData.file) {
115 this._upload.uploadFile(fileUploadData.file);
50aa3a01
TD
116 }
117 else {
ed355f64
MS
118 const blobUploadData = this._uploadData;
119 this._uploadId = this._upload.uploadBlob(blobUploadData.blob);
50aa3a01
TD
120 }
121 this._uploadData = null;
122 }
ed355f64 123 }
50aa3a01
TD
124 /**
125 * Handles pasting and dragging and dropping files into the editor.
50aa3a01 126 */
ed355f64 127 _editorUpload(data) {
50aa3a01
TD
128 this._uploadData = data;
129 UiDialog.open(this);
ed355f64 130 }
50aa3a01
TD
131 /**
132 * Returns the id of the insert dialog based on the media files to be inserted.
50aa3a01 133 */
ed355f64 134 _getInsertDialogId() {
fb628e67 135 return ["mediaInsert", ...this._mediaToInsert.keys()].join("-");
ed355f64 136 }
50aa3a01
TD
137 /**
138 * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
50aa3a01 139 */
ed355f64
MS
140 _getThumbnailSizes() {
141 return ["small", "medium", "large"]
142 .map((size) => {
fb628e67 143 const sizeSupported = Array.from(this._mediaToInsert.values()).every((media) => {
fbb238d9 144 return media[size + "ThumbnailType"] !== null;
50aa3a01 145 });
fb628e67 146 if (sizeSupported) {
ed355f64 147 return size;
50aa3a01 148 }
ed355f64
MS
149 return null;
150 })
151 .filter((s) => s !== null);
152 }
50aa3a01 153 /**
ed355f64 154 * Inserts media files into the editor.
50aa3a01 155 */
ed355f64 156 _insertMedia(event, thumbnailSize, closeEditor = false) {
50aa3a01
TD
157 if (closeEditor === undefined)
158 closeEditor = true;
50aa3a01
TD
159 // update insert options with selected values if method is called by clicking on 'insert' button
160 // in dialog
161 if (event) {
162 UiDialog.close(this._getInsertDialogId());
ed355f64
MS
163 const dialogContent = event.currentTarget.closest(".dialogContent");
164 const thumbnailSizeSelect = dialogContent.querySelector("select[name=thumbnailSize]");
165 thumbnailSize = thumbnailSizeSelect.value;
50aa3a01
TD
166 }
167 if (this._options.callbackInsert !== null) {
2a296d05 168 this._options.callbackInsert(this._mediaToInsert, "separate" /* Separate */, thumbnailSize);
50aa3a01
TD
169 }
170 else {
57324f3a 171 this._options.editor.buffer.set();
d1742bbe 172 this._mediaToInsert.forEach((media) => this._insertMediaItem(thumbnailSize, media));
50aa3a01
TD
173 }
174 if (this._mediaToInsertByClipboard) {
fbb238d9 175 Clipboard.unmark("com.woltlab.wcf.media", Array.from(this._mediaToInsert.keys()));
50aa3a01 176 }
ed355f64 177 this._mediaToInsert = new Map();
50aa3a01
TD
178 this._mediaToInsertByClipboard = false;
179 // close manager dialog
180 if (closeEditor) {
181 UiDialog.close(this);
182 }
ed355f64 183 }
50aa3a01 184 /**
ed355f64 185 * Inserts a single media item into the editor.
50aa3a01 186 */
ed355f64
MS
187 _insertMediaItem(thumbnailSize, media) {
188 if (media.isImage) {
189 let available = "";
190 ["small", "medium", "large", "original"].some((size) => {
191 if (media[size + "ThumbnailHeight"] != 0) {
50aa3a01
TD
192 available = size;
193 if (thumbnailSize == size) {
ed355f64 194 return true;
50aa3a01
TD
195 }
196 }
ed355f64
MS
197 return false;
198 });
50aa3a01 199 thumbnailSize = available;
ed355f64
MS
200 if (!thumbnailSize) {
201 thumbnailSize = "original";
50aa3a01 202 }
ed355f64
MS
203 let link = media.link;
204 if (thumbnailSize !== "original") {
205 link = media[thumbnailSize + "ThumbnailLink"];
206 }
207 this._options.editor.insert.html(`<img src="${link}" class="woltlabSuiteMedia" data-media-id="${media.mediaID}" data-media-size="${thumbnailSize}">`);
50aa3a01
TD
208 }
209 else {
ed355f64 210 this._options.editor.insert.text(`[wsm='${media.mediaID}'][/wsm]`);
50aa3a01 211 }
ed355f64 212 }
50aa3a01
TD
213 /**
214 * Is called after media files are successfully uploaded to insert copied media.
50aa3a01 215 */
ed355f64 216 _mediaUploaded(data) {
50aa3a01 217 if (this._uploadId !== null && this._upload === data.upload) {
ed355f64
MS
218 if (this._uploadId === data.uploadId ||
219 (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
220 this._mediaToInsert = new Map(data.media.entries());
221 this._insertMedia(null, "medium", false);
50aa3a01
TD
222 this._uploadId = null;
223 }
224 }
ed355f64 225 }
50aa3a01
TD
226 /**
227 * Handles clicking on the insert button.
50aa3a01 228 */
ed355f64
MS
229 _openInsertDialog(event) {
230 const target = event.currentTarget;
231 this.insertMedia([~~target.dataset.objectId]);
232 }
50aa3a01
TD
233 /**
234 * Is called to insert the media files with the given ids into an editor.
50aa3a01 235 */
ed355f64 236 clipboardInsertMedia(mediaIds) {
50aa3a01 237 this.insertMedia(mediaIds, true);
ed355f64 238 }
50aa3a01
TD
239 /**
240 * Prepares insertion of the media files with the given ids.
50aa3a01 241 */
ed355f64
MS
242 insertMedia(mediaIds, insertedByClipboard) {
243 this._mediaToInsert = new Map();
50aa3a01
TD
244 this._mediaToInsertByClipboard = insertedByClipboard || false;
245 // open the insert dialog if all media files are images
fbb238d9
MS
246 let imagesOnly = true;
247 mediaIds.forEach((mediaId) => {
248 const media = this._media.get(mediaId);
50aa3a01
TD
249 this._mediaToInsert.set(media.mediaID, media);
250 if (!media.isImage) {
251 imagesOnly = false;
252 }
fbb238d9 253 });
50aa3a01 254 if (imagesOnly) {
ed355f64 255 const thumbnailSizes = this._getThumbnailSizes();
50aa3a01
TD
256 if (thumbnailSizes.length) {
257 UiDialog.close(this);
ed355f64 258 const dialogId = this._getInsertDialogId();
50aa3a01 259 if (UiDialog.getDialog(dialogId)) {
ed355f64 260 UiDialog.openStatic(dialogId, null);
50aa3a01
TD
261 }
262 else {
263 this._buildInsertDialog();
264 }
265 }
266 else {
ed355f64 267 this._insertMedia(undefined, "original");
50aa3a01
TD
268 }
269 }
270 else {
271 this._insertMedia();
272 }
ed355f64
MS
273 }
274 getMode() {
275 return "editor";
276 }
277 setupMediaElement(media, mediaElement) {
278 super.setupMediaElement(media, mediaElement);
50aa3a01 279 // add media insertion icon
ed355f64
MS
280 const buttons = mediaElement.querySelector("nav.buttonGroupNavigation > ul");
281 const listItem = document.createElement("li");
282 listItem.className = "jsMediaInsertButton";
fbb238d9 283 listItem.dataset.objectId = media.mediaID.toString();
50aa3a01 284 buttons.appendChild(listItem);
ed355f64
MS
285 listItem.innerHTML = `
286 <a>
287 <span class="icon icon16 fa-plus jsTooltip" title="${Language.get("wcf.global.button.insert")}"></span>
288 <span class="invisible">${Language.get("wcf.global.button.insert")}</span>
289 </a>`;
50aa3a01 290 }
ed355f64
MS
291 }
292 Core.enableLegacyInheritance(MediaManagerEditor);
50aa3a01 293 return MediaManagerEditor;
56eb7314 294});