From: Alexander Ebert Date: Mon, 29 Apr 2024 12:58:58 +0000 (+0200) Subject: Add support for uploads through drag and drop on the editor X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=93ec991bfa54f27598b3a9ef26f729e2fc21522e;p=GitHub%2FWoltLab%2FWCF.git Add support for uploads through drag and drop on the editor --- diff --git a/ts/WoltLabSuite/Core/Api/Files/Chunk/Chunk.ts b/ts/WoltLabSuite/Core/Api/Files/Chunk/Chunk.ts index 84dd41ea9f..0bc71b9f63 100644 --- a/ts/WoltLabSuite/Core/Api/Files/Chunk/Chunk.ts +++ b/ts/WoltLabSuite/Core/Api/Files/Chunk/Chunk.ts @@ -1,19 +1,20 @@ import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; import { ApiResult, apiResultFromError, apiResultFromValue } from "../../Result"; -export type Response = - | { - completed: false; - } - | { - completed: true; - generateThumbnails: boolean; - fileID: number; - typeName: string; - mimeType: string; - link: string; - data: Record; - }; +export type ResponseIncomplete = { + completed: false; +}; +export type ResponseCompleted = { + completed: true; + generateThumbnails: boolean; + fileID: number; + typeName: string; + mimeType: string; + link: string; + data: Record; +}; + +export type Response = ResponseIncomplete | ResponseCompleted; export async function uploadChunk( identifier: string, diff --git a/ts/WoltLabSuite/Core/Component/Attachment/List.ts b/ts/WoltLabSuite/Core/Component/Attachment/List.ts index 3920343d9d..1dd33f8c63 100644 --- a/ts/WoltLabSuite/Core/Component/Attachment/List.ts +++ b/ts/WoltLabSuite/Core/Component/Attachment/List.ts @@ -1,8 +1,9 @@ import { deleteFile } from "WoltLabSuite/Core/Api/Files/DeleteFile"; -import { dispatchToCkeditor } from "../Ckeditor/Event"; +import { dispatchToCkeditor, listenToCkeditor } from "../Ckeditor/Event"; import WoltlabCoreFileElement from "../File/woltlab-core-file"; import "../File/woltlab-core-file"; +import { CkeditorDropEvent } from "../File/Upload"; type FileProcessorData = { attachmentID: number; @@ -133,6 +134,12 @@ export function setup(editorId: string): void { return; } + const editor = document.getElementById(editorId); + if (editor === null) { + // TODO: error handling + return; + } + const uploadButton = container.querySelector("woltlab-core-file-upload"); if (uploadButton === null) { throw new Error("Expected the container to contain an upload button", { @@ -153,6 +160,13 @@ export function setup(editorId: string): void { upload(fileList!, event.detail, editorId); }); + listenToCkeditor(editor).uploadAttachment((payload) => { + const event = new CustomEvent("ckeditorDrop", { + detail: payload, + }); + uploadButton.dispatchEvent(event); + }); + const existingFiles = container.querySelector(".attachment__list__existingFiles"); if (existingFiles !== null) { existingFiles.querySelectorAll("woltlab-core-file").forEach((file) => { diff --git a/ts/WoltLabSuite/Core/Component/Ckeditor/Attachment.ts b/ts/WoltLabSuite/Core/Component/Ckeditor/Attachment.ts index faee715f87..26af63fc5f 100644 --- a/ts/WoltLabSuite/Core/Component/Ckeditor/Attachment.ts +++ b/ts/WoltLabSuite/Core/Component/Ckeditor/Attachment.ts @@ -19,7 +19,7 @@ type UploadResult = { }; }; -type AttachmentData = { +export type AttachmentData = { attachmentId: number; url: string; }; @@ -36,14 +36,16 @@ function uploadAttachment(element: HTMLElement, file: File, abortController?: Ab dispatchToCkeditor(element).uploadAttachment(payload); return new Promise((resolve) => { - void payload.promise!.then(({ attachmentId, url }) => { - resolve({ - "data-attachment-id": attachmentId.toString(), - urls: { - default: url, - }, - }); - }); + void payload + .promise!.then(({ attachmentId, url }) => { + resolve({ + "data-attachment-id": attachmentId.toString(), + urls: { + default: url, + }, + }); + }) + .catch(() => {}); }); } diff --git a/ts/WoltLabSuite/Core/Component/File/Upload.ts b/ts/WoltLabSuite/Core/Component/File/Upload.ts index 6d0eaad2e7..d90246a556 100644 --- a/ts/WoltLabSuite/Core/Component/File/Upload.ts +++ b/ts/WoltLabSuite/Core/Component/File/Upload.ts @@ -1,9 +1,19 @@ import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector"; import { upload as filesUpload } from "WoltLabSuite/Core/Api/Files/Upload"; import WoltlabCoreFileElement from "./woltlab-core-file"; -import { Response as UploadChunkResponse, uploadChunk } from "WoltLabSuite/Core/Api/Files/Chunk/Chunk"; +import { + ResponseCompleted, + Response as UploadChunkResponse, + uploadChunk, +} from "WoltLabSuite/Core/Api/Files/Chunk/Chunk"; import { generateThumbnails } from "WoltLabSuite/Core/Api/Files/GenerateThumbnails"; import ImageResizer from "WoltLabSuite/Core/Image/Resizer"; +import { AttachmentData } from "../Ckeditor/Attachment"; + +export type CkeditorDropEvent = { + file: File; + promise?: Promise; +}; export type ThumbnailsGenerated = { data: GenerateThumbnailsResponse; @@ -24,7 +34,7 @@ type ResizeConfiguration = { quality: number; }; -async function upload(element: WoltlabCoreFileUploadElement, file: File): Promise { +async function upload(element: WoltlabCoreFileUploadElement, file: File): Promise { const typeName = element.dataset.typeName!; const fileHash = await getSha256Hash(await file.arrayBuffer()); @@ -45,7 +55,7 @@ async function upload(element: WoltlabCoreFileUploadElement, file: File): Promis } console.log(validationError); - return; + return undefined; } const { identifier, numberOfChunks } = response.value; @@ -69,6 +79,10 @@ async function upload(element: WoltlabCoreFileUploadElement, file: File): Promis } await chunkUploadCompleted(fileElement, response.value); + + if (response.value.completed) { + return response.value; + } } } @@ -189,5 +203,44 @@ export function setup(): void { void upload(element, resizedFile); }); }); + + element.addEventListener("ckeditorDrop", (event: CustomEvent) => { + const { file } = event.detail; + + let promiseResolve: (data: AttachmentData) => void; + let promiseReject: () => void; + event.detail.promise = new Promise((resolve, reject) => { + promiseResolve = resolve; + promiseReject = reject; + }); + + clearPreviousErrors(element); + + if (!validateFile(element, file)) { + promiseReject!(); + + return; + } + + void resizeImage(element, file).then(async (resizeFile) => { + try { + const data = await upload(element, resizeFile); + if (data === undefined || typeof data.data.attachmentID !== "number") { + promiseReject(); + } else { + const attachmentData: AttachmentData = { + attachmentId: data.data.attachmentID, + url: data.link, + }; + + promiseResolve(attachmentData); + } + } catch (e) { + promiseReject(); + + throw e; + } + }); + }); }); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js index 54e633c75c..4d8df96bb2 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js @@ -94,6 +94,11 @@ define(["require", "exports", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Cked // TODO: error handling return; } + const editor = document.getElementById(editorId); + if (editor === null) { + // TODO: error handling + return; + } const uploadButton = container.querySelector("woltlab-core-file-upload"); if (uploadButton === null) { throw new Error("Expected the container to contain an upload button", { @@ -111,6 +116,12 @@ define(["require", "exports", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Cked uploadButton.addEventListener("uploadStart", (event) => { upload(fileList, event.detail, editorId); }); + (0, Event_1.listenToCkeditor)(editor).uploadAttachment((payload) => { + const event = new CustomEvent("ckeditorDrop", { + detail: payload, + }); + uploadButton.dispatchEvent(event); + }); const existingFiles = container.querySelector(".attachment__list__existingFiles"); if (existingFiles !== null) { existingFiles.querySelectorAll("woltlab-core-file").forEach((file) => { diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Ckeditor/Attachment.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Ckeditor/Attachment.js index 9a9b43b4ff..1b45f0b1f1 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Ckeditor/Attachment.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Ckeditor/Attachment.js @@ -15,14 +15,16 @@ define(["require", "exports", "./Event"], function (require, exports, Event_1) { const payload = { abortController, file }; (0, Event_1.dispatchToCkeditor)(element).uploadAttachment(payload); return new Promise((resolve) => { - void payload.promise.then(({ attachmentId, url }) => { + void payload + .promise.then(({ attachmentId, url }) => { resolve({ "data-attachment-id": attachmentId.toString(), urls: { default: url, }, }); - }); + }) + .catch(() => { }); }); } function setupInsertAttachment(ckeditor) { diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/Upload.js index acd60e9ca4..d3baeb81a6 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/Upload.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/Upload.js @@ -18,7 +18,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol throw response.error; } console.log(validationError); - return; + return undefined; } const { identifier, numberOfChunks } = response.value; const chunkSize = Math.ceil(file.size / numberOfChunks); @@ -34,6 +34,9 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol throw response.error; } await chunkUploadCompleted(fileElement, response.value); + if (response.value.completed) { + return response.value; + } } } async function chunkUploadCompleted(fileElement, result) { @@ -127,6 +130,39 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol void upload(element, resizedFile); }); }); + element.addEventListener("ckeditorDrop", (event) => { + const { file } = event.detail; + let promiseResolve; + let promiseReject; + event.detail.promise = new Promise((resolve, reject) => { + promiseResolve = resolve; + promiseReject = reject; + }); + clearPreviousErrors(element); + if (!validateFile(element, file)) { + promiseReject(); + return; + } + void resizeImage(element, file).then(async (resizeFile) => { + try { + const data = await upload(element, resizeFile); + if (data === undefined || typeof data.data.attachmentID !== "number") { + promiseReject(); + } + else { + const attachmentData = { + attachmentId: data.data.attachmentID, + url: data.link, + }; + promiseResolve(attachmentData); + } + } + catch (e) { + promiseReject(); + throw e; + } + }); + }); }); } exports.setup = setup;