Reorganize the action buttons for attachments
authorAlexander Ebert <ebert@woltlab.com>
Sat, 4 May 2024 11:11:54 +0000 (13:11 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:39 +0000 (12:19 +0200)
ts/WoltLabSuite/Core/Component/Attachment/List.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js
wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php
wcfsetup/install/files/style/ui/attachment.scss

index 652edbbe5946bfa6d7e2c6b38d17c37d20c7d1d3..298282a41ac14a80cf80b653f9b597bfc74e91f5 100644 (file)
@@ -6,6 +6,7 @@ import "../File/woltlab-core-file";
 import { CkeditorDropEvent } from "../File/Upload";
 import { formatFilesize } from "WoltLabSuite/Core/FileUtil";
 import DomChangeListener from "WoltLabSuite/Core/Dom/Change/Listener";
+import { initFragment, toggleDropdown } from "WoltLabSuite/Core/Ui/Dropdown/Simple";
 
 type FileProcessorData = {
   attachmentID: number;
@@ -44,17 +45,9 @@ function upload(fileList: HTMLElement, file: WoltlabCoreFileElement, editorId: s
         return;
       }
 
-      const buttonList = document.createElement("div");
-      buttonList.classList.add("attachment__item__buttons");
-      buttonList.append(
-        getDeleteAttachButton(fileId, (data as FileProcessorData).attachmentID, editorId, element),
-        getInsertAttachBbcodeButton(
-          (data as FileProcessorData).attachmentID,
-          file.isImage() && file.link ? file.link : "",
-          editorId,
-        ),
-      );
+      const extraButtons: HTMLButtonElement[] = [];
 
+      let insertButton: HTMLButtonElement;
       if (file.isImage()) {
         const thumbnail = file.thumbnails.find((thumbnail) => thumbnail.identifier === "tiny");
         if (thumbnail !== undefined) {
@@ -63,7 +56,17 @@ function upload(fileList: HTMLElement, file: WoltlabCoreFileElement, editorId: s
 
         const url = file.thumbnails.find((thumbnail) => thumbnail.identifier === "")?.link;
         if (url !== undefined) {
-          buttonList.append(getInsertThumbnailButton((data as FileProcessorData).attachmentID, url, editorId));
+          insertButton = getInsertThumbnailButton((data as FileProcessorData).attachmentID, url, editorId);
+
+          extraButtons.push(
+            getInsertAttachBbcodeButton((data as FileProcessorData).attachmentID, file.link ? file.link : "", editorId),
+          );
+        } else {
+          insertButton = getInsertAttachBbcodeButton(
+            (data as FileProcessorData).attachmentID,
+            file.link ? file.link : "",
+            editorId,
+          );
         }
 
         if (file.link !== undefined && file.filename !== undefined) {
@@ -78,11 +81,57 @@ function upload(fileList: HTMLElement, file: WoltlabCoreFileElement, editorId: s
 
           DomChangeListener.trigger();
         }
+      } else {
+        insertButton = getInsertAttachBbcodeButton(
+          (data as FileProcessorData).attachmentID,
+          file.isImage() && file.link ? file.link : "",
+          editorId,
+        );
+      }
+
+      const dropdownMenu = document.createElement("ul");
+      dropdownMenu.classList.add("dropdownMenu");
+      for (const button of extraButtons) {
+        const listItem = document.createElement("li");
+        listItem.append(button);
+        dropdownMenu.append(listItem);
+      }
+
+      if (dropdownMenu.childElementCount !== 0) {
+        const listItem = document.createElement("li");
+        listItem.classList.add("dropdownDivider");
+        dropdownMenu.append(listItem);
       }
 
+      const listItem = document.createElement("li");
+      listItem.append(getDeleteAttachButton(fileId, (data as FileProcessorData).attachmentID, editorId, element));
+      dropdownMenu.append(listItem);
+
+      const moreOptions = document.createElement("button");
+      moreOptions.classList.add("button", "small", "jsTooltip");
+      moreOptions.type = "button";
+      moreOptions.title = "TODO: more options";
+      moreOptions.innerHTML = '<fa-icon name="ellipsis-vertical"></fa-icon>';
+
+      const buttonList = document.createElement("div");
+      buttonList.classList.add("attachment__item__buttons");
+      insertButton.classList.add("button", "small");
+      buttonList.append(insertButton, moreOptions);
+
       element.append(buttonList);
+
+      initFragment(moreOptions, dropdownMenu);
+      moreOptions.addEventListener("click", (event) => {
+        event.stopPropagation();
+
+        toggleDropdown(moreOptions.id);
+      });
     })
-    .catch(() => {
+    .catch((e) => {
+      if (e instanceof Error) {
+        throw e;
+      }
+
       if (file.validationError === undefined) {
         return;
       }
@@ -101,7 +150,6 @@ function getDeleteAttachButton(
 ): HTMLButtonElement {
   const button = document.createElement("button");
   button.type = "button";
-  button.classList.add("button", "small");
   button.textContent = "TODO: delete";
 
   button.addEventListener("click", () => {
@@ -128,7 +176,6 @@ function getDeleteAttachButton(
 function getInsertAttachBbcodeButton(attachmentId: number, url: string, editorId: string): HTMLButtonElement {
   const button = document.createElement("button");
   button.type = "button";
-  button.classList.add("button", "small");
   button.textContent = "TODO: insert";
 
   button.addEventListener("click", () => {
@@ -150,7 +197,6 @@ function getInsertAttachBbcodeButton(attachmentId: number, url: string, editorId
 function getInsertThumbnailButton(attachmentId: number, url: string, editorId: string): HTMLButtonElement {
   const button = document.createElement("button");
   button.type = "button";
-  button.classList.add("button", "small");
   button.textContent = "TODO: insert thumbnail";
 
   button.addEventListener("click", () => {
index 9b0bc37881a68afdedb0e29b84c6bb1c1c3402eb..ef710df98291ffac868b97c135fd7804b78f2223 100644 (file)
@@ -1,4 +1,4 @@
-define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Ckeditor/Event", "WoltLabSuite/Core/FileUtil", "WoltLabSuite/Core/Dom/Change/Listener", "../File/woltlab-core-file"], function (require, exports, tslib_1, DeleteFile_1, Event_1, FileUtil_1, Listener_1) {
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile", "../Ckeditor/Event", "WoltLabSuite/Core/FileUtil", "WoltLabSuite/Core/Dom/Change/Listener", "WoltLabSuite/Core/Ui/Dropdown/Simple", "../File/woltlab-core-file"], function (require, exports, tslib_1, DeleteFile_1, Event_1, FileUtil_1, Listener_1, Simple_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.setup = void 0;
@@ -29,9 +29,8 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
                 // TODO: error handling
                 return;
             }
-            const buttonList = document.createElement("div");
-            buttonList.classList.add("attachment__item__buttons");
-            buttonList.append(getDeleteAttachButton(fileId, data.attachmentID, editorId, element), getInsertAttachBbcodeButton(data.attachmentID, file.isImage() && file.link ? file.link : "", editorId));
+            const extraButtons = [];
+            let insertButton;
             if (file.isImage()) {
                 const thumbnail = file.thumbnails.find((thumbnail) => thumbnail.identifier === "tiny");
                 if (thumbnail !== undefined) {
@@ -39,7 +38,11 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
                 }
                 const url = file.thumbnails.find((thumbnail) => thumbnail.identifier === "")?.link;
                 if (url !== undefined) {
-                    buttonList.append(getInsertThumbnailButton(data.attachmentID, url, editorId));
+                    insertButton = getInsertThumbnailButton(data.attachmentID, url, editorId);
+                    extraButtons.push(getInsertAttachBbcodeButton(data.attachmentID, file.link ? file.link : "", editorId));
+                }
+                else {
+                    insertButton = getInsertAttachBbcodeButton(data.attachmentID, file.link ? file.link : "", editorId);
                 }
                 if (file.link !== undefined && file.filename !== undefined) {
                     const link = document.createElement("a");
@@ -52,9 +55,44 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
                     Listener_1.default.trigger();
                 }
             }
+            else {
+                insertButton = getInsertAttachBbcodeButton(data.attachmentID, file.isImage() && file.link ? file.link : "", editorId);
+            }
+            const dropdownMenu = document.createElement("ul");
+            dropdownMenu.classList.add("dropdownMenu");
+            for (const button of extraButtons) {
+                const listItem = document.createElement("li");
+                listItem.append(button);
+                dropdownMenu.append(listItem);
+            }
+            if (dropdownMenu.childElementCount !== 0) {
+                const listItem = document.createElement("li");
+                listItem.classList.add("dropdownDivider");
+                dropdownMenu.append(listItem);
+            }
+            const listItem = document.createElement("li");
+            listItem.append(getDeleteAttachButton(fileId, data.attachmentID, editorId, element));
+            dropdownMenu.append(listItem);
+            const moreOptions = document.createElement("button");
+            moreOptions.classList.add("button", "small", "jsTooltip");
+            moreOptions.type = "button";
+            moreOptions.title = "TODO: more options";
+            moreOptions.innerHTML = '<fa-icon name="ellipsis-vertical"></fa-icon>';
+            const buttonList = document.createElement("div");
+            buttonList.classList.add("attachment__item__buttons");
+            insertButton.classList.add("button", "small");
+            buttonList.append(insertButton, moreOptions);
             element.append(buttonList);
+            (0, Simple_1.initFragment)(moreOptions, dropdownMenu);
+            moreOptions.addEventListener("click", (event) => {
+                event.stopPropagation();
+                (0, Simple_1.toggleDropdown)(moreOptions.id);
+            });
         })
-            .catch(() => {
+            .catch((e) => {
+            if (e instanceof Error) {
+                throw e;
+            }
             if (file.validationError === undefined) {
                 return;
             }
@@ -66,7 +104,6 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
     function getDeleteAttachButton(fileId, attachmentId, editorId, element) {
         const button = document.createElement("button");
         button.type = "button";
-        button.classList.add("button", "small");
         button.textContent = "TODO: delete";
         button.addEventListener("click", () => {
             const editor = document.getElementById(editorId);
@@ -87,7 +124,6 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
     function getInsertAttachBbcodeButton(attachmentId, url, editorId) {
         const button = document.createElement("button");
         button.type = "button";
-        button.classList.add("button", "small");
         button.textContent = "TODO: insert";
         button.addEventListener("click", () => {
             const editor = document.getElementById(editorId);
@@ -105,7 +141,6 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Api/Files/DeleteFile",
     function getInsertThumbnailButton(attachmentId, url, editorId) {
         const button = document.createElement("button");
         button.type = "button";
-        button.classList.add("button", "small");
         button.textContent = "TODO: insert thumbnail";
         button.addEventListener("click", () => {
             const editor = document.getElementById(editorId);
index 673854b045ad68eccbf80ac2085934fc5efe1219..3c7f823b1dc0f0dbd8b5efff7244e7e4803ea927 100644 (file)
@@ -28,12 +28,12 @@ final class FileProcessor extends SingletonFactory
     /**
      * @var array<string, ObjectType>
      */
-    private array $processors;
+    private array $objectTypes;
 
     #[\Override]
     public function init(): void
     {
-        $this->processors = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.file');
+        $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.file');
     }
 
     public function getProcessorByName(string $objectType): ?IFileProcessor
@@ -47,7 +47,7 @@ final class FileProcessor extends SingletonFactory
             return null;
         }
 
-        foreach ($this->processors as $objectType) {
+        foreach ($this->objectTypes as $objectType) {
             if ($objectType->objectTypeID === $objectTypeID) {
                 return $objectType->getProcessor();
             }
@@ -58,7 +58,7 @@ final class FileProcessor extends SingletonFactory
 
     public function getObjectType(string $objectType): ?ObjectType
     {
-        return $this->processors[$objectType] ?? null;
+        return $this->objectTypes[$objectType] ?? null;
     }
 
     public function getHtmlElement(IFileProcessor $fileProcessor, array $context): string
@@ -160,8 +160,8 @@ final class FileProcessor extends SingletonFactory
         $statement->execute($conditions->getParameters());
         $thumbnailIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
 
-        foreach ($this->processors as $processor) {
-            $processor->delete($fileIDs, $thumbnailIDs);
+        foreach ($this->objectTypes as $objectType) {
+            $objectType->getProcessor()->delete($fileIDs, $thumbnailIDs);
         }
     }
 }
index 8ee0de940d2117bd856203ef75fdadfdc94a8b28..0af744b1902861a3d80473bfa9af2ee1749df21f 100644 (file)
@@ -194,7 +194,6 @@ html[data-color-scheme="dark"] {
 }
 
 .attachment__item__buttons {
-       align-items: end;
        column-gap: 5px;
        display: flex;
        grid-area: buttons;