Render quote tab dynamic
authorCyperghost <olaf_schmitz_1@t-online.de>
Tue, 17 Dec 2024 12:48:11 +0000 (13:48 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 8 Jan 2025 16:25:19 +0000 (17:25 +0100)
com.woltlab.wcf/templates/__messageFormQuote.tpl
com.woltlab.wcf/templates/__messageFormQuoteInline.tpl
com.woltlab.wcf/templates/messageFormTabs.tpl
com.woltlab.wcf/templates/messageFormTabsInline.tpl
ts/WoltLabSuite/Core/Component/Message/MessageTabMenu.ts
ts/WoltLabSuite/Core/Component/Quote/List.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Message/MessageTabMenu.js
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/event/listener/PreloadPhrasesCollectingListener.class.php

index 30e8947507e6a8b761b7e28cb0aa1abdd0228744..d4b635e1060efdf5799f807aaaed163eea056039 100644 (file)
@@ -1,5 +1,11 @@
 {* TODO *}
 
-<div id="quote-{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="jsOnly messageTabMenuContent">
+<div id="quotes_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="messageTabMenuContent">
 
 </div>
+
+<script data-relocate="true">
+  require(["WoltLabSuite/Core/Component/Quote/List"], ({ setup }) => {
+       setup("{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}");
+  });
+</script>
index 30e8947507e6a8b761b7e28cb0aa1abdd0228744..d4b635e1060efdf5799f807aaaed163eea056039 100644 (file)
@@ -1,5 +1,11 @@
 {* TODO *}
 
-<div id="quote-{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="jsOnly messageTabMenuContent">
+<div id="quotes_{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="messageTabMenuContent">
 
 </div>
+
+<script data-relocate="true">
+  require(["WoltLabSuite/Core/Component/Quote/List"], ({ setup }) => {
+       setup("{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}");
+  });
+</script>
index 3fbf13842ceb15990d531157d43cac5165c5a6cd..90b8083122b2f8e970da522b37e6885208eeed8f 100644 (file)
                        {/if}
                        {event name='tabMenuTabs'}
 
-                       <li data-name="quote">
-                               {* TODO change count *}
+                       <li data-name="quotes" hidden>
                                <button type="button">
                                        {icon name='quote-left'}
-                                       <span>{lang count=10}wcf.message.quote.showQuotes{/lang}</span>
+                                       <span>{lang}wcf.bbcode.quote{/lang}</span>
                                </button>
                        </li>
                </ul>
index ec9e0de82e70f666c674b6368867779b859b509f..e05eefa86d89f9e6262c1cfd22aaa13b7b993150 100644 (file)
                        {/if}
                        {event name='tabMenuTabs'}
 
-                       <li data-name="quote">
-                               {* TODO change count *}
+                       <li data-name="quotes" hidden>
                                <button type="button">
                                        {icon name='quote-left'}
-                                       <span>{lang count=10}wcf.message.quote.showQuotes{/lang}</span>
+                                       <span>{lang}wcf.bbcode.quote{/lang}</span>
                                </button>
                        </li>
                </ul>
index 0d3d4d7579647afda5c2657141256f53a368db18..3b38892d181e5ea3b1e4ff399eaf2e028441da3d 100644 (file)
@@ -65,6 +65,31 @@ class TabMenu {
     this.#activeTabName = tabName;
   }
 
+  showTab(tabName: string, title?: string): void {
+    this.#tabs
+      .filter((element) => element.dataset.name === tabName)
+      .forEach((element) => {
+        element.hidden = false;
+
+        // Set new title
+        if (title) {
+          element.querySelector("span")!.textContent = title;
+        }
+      });
+  }
+
+  hideTab(tabName: string): void {
+    this.#tabs
+      .filter((element) => element.dataset.name === tabName)
+      .forEach((element) => {
+        element.hidden = true;
+
+        if (element.classList.contains("active")) {
+          this.#closeAllTabs();
+        }
+      });
+  }
+
   setTabCounter(tabName: string, value: number): void {
     const tab = this.#tabs.find((element) => element.dataset.name === tabName);
     if (tab === undefined) {
diff --git a/ts/WoltLabSuite/Core/Component/Quote/List.ts b/ts/WoltLabSuite/Core/Component/Quote/List.ts
new file mode 100644 (file)
index 0000000..27604f2
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Handles quotes for CKEditor 5 message fields.
+ *
+ * @author Olaf Braun
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+import * as Core from "WoltLabSuite/Core/Core";
+import { listenToCkeditor } from "WoltLabSuite/Core/Component/Ckeditor/Event";
+import type { CKEditor } from "WoltLabSuite/Core/Component/Ckeditor";
+import { getTabMenu } from "WoltLabSuite/Core/Component/Message/MessageTabMenu";
+import { getPhrase } from "WoltLabSuite/Core/Language";
+
+export const STORAGE_KEY = Core.getStoragePrefix() + "quotes";
+const quoteLists = new Map<string, QuoteList>();
+
+class QuoteList {
+  #container: HTMLElement;
+  #editor: CKEditor;
+  #editorId: string;
+
+  constructor(editorId: string, editor: CKEditor) {
+    this.#editorId = editorId;
+    this.#editor = editor;
+    this.#container = document.getElementById(`quotes_${editorId}`)!;
+    if (this.#container === null) {
+      throw new Error(`The quotes container for '${editorId}' does not exist.`);
+    }
+
+    window.addEventListener("storage", (event) => {
+      if (event.key !== STORAGE_KEY) {
+        return;
+      }
+
+      this.renderQuotes(event.newValue);
+    });
+
+    this.renderQuotes(window.localStorage.getItem(STORAGE_KEY));
+  }
+
+  public renderQuotes(template: string | null): void {
+    this.#container.innerHTML = template || "";
+
+    if (template) {
+      getTabMenu(this.#editorId)?.showTab(
+        "quotes",
+        getPhrase("wcf.message.quote.showQuotes", {
+          count: this.#container.childElementCount,
+        }),
+      );
+    } else {
+      getTabMenu(this.#editorId)?.hideTab("quotes");
+    }
+  }
+}
+
+export function getQuoteList(editorId: string): QuoteList | undefined {
+  return quoteLists.get(editorId);
+}
+
+export function setup(editorId: string): void {
+  if (quoteLists.has(editorId)) {
+    return;
+  }
+
+  const editor = document.getElementById(editorId);
+  if (editor === null) {
+    throw new Error(`The editor '${editorId}' does not exist.`);
+  }
+
+  listenToCkeditor(editor).ready(({ ckeditor }) => {
+    if (ckeditor.features.quoteBlock) {
+      quoteLists.set(editorId, new QuoteList(editorId, ckeditor));
+    }
+  });
+}
index fcbfb6d4fe1398528ce42de718636cdcf995504e..7974eff75d1935666d3db92613fe5047f9fa2d35 100644 (file)
@@ -53,6 +53,27 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol
             this.#tabContainers[tabIndex].classList.add("active");
             this.#activeTabName = tabName;
         }
+        showTab(tabName, title) {
+            this.#tabs
+                .filter((element) => element.dataset.name === tabName)
+                .forEach((element) => {
+                element.hidden = false;
+                // Set new title
+                if (title) {
+                    element.querySelector("span").textContent = title;
+                }
+            });
+        }
+        hideTab(tabName) {
+            this.#tabs
+                .filter((element) => element.dataset.name === tabName)
+                .forEach((element) => {
+                element.hidden = true;
+                if (element.classList.contains("active")) {
+                    this.#closeAllTabs();
+                }
+            });
+        }
         setTabCounter(tabName, value) {
             const tab = this.#tabs.find((element) => element.dataset.name === tabName);
             if (tab === undefined) {
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js
new file mode 100644 (file)
index 0000000..c00ea38
--- /dev/null
@@ -0,0 +1,58 @@
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/Core/Component/Ckeditor/Event", "WoltLabSuite/Core/Component/Message/MessageTabMenu", "WoltLabSuite/Core/Language"], function (require, exports, tslib_1, Core, Event_1, MessageTabMenu_1, Language_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.STORAGE_KEY = void 0;
+    exports.getQuoteList = getQuoteList;
+    exports.setup = setup;
+    Core = tslib_1.__importStar(Core);
+    exports.STORAGE_KEY = Core.getStoragePrefix() + "quotes";
+    const quoteLists = new Map();
+    class QuoteList {
+        #container;
+        #editor;
+        #editorId;
+        constructor(editorId, editor) {
+            this.#editorId = editorId;
+            this.#editor = editor;
+            this.#container = document.getElementById(`quotes_${editorId}`);
+            if (this.#container === null) {
+                throw new Error(`The quotes container for '${editorId}' does not exist.`);
+            }
+            window.addEventListener("storage", (event) => {
+                if (event.key !== exports.STORAGE_KEY) {
+                    return;
+                }
+                this.renderQuotes(event.newValue);
+            });
+            this.renderQuotes(window.localStorage.getItem(exports.STORAGE_KEY));
+        }
+        renderQuotes(template) {
+            this.#container.innerHTML = template || "";
+            if (template) {
+                (0, MessageTabMenu_1.getTabMenu)(this.#editorId)?.showTab("quotes", (0, Language_1.getPhrase)("wcf.message.quote.showQuotes", {
+                    count: this.#container.childElementCount,
+                }));
+            }
+            else {
+                (0, MessageTabMenu_1.getTabMenu)(this.#editorId)?.hideTab("quotes");
+            }
+        }
+    }
+    function getQuoteList(editorId) {
+        return quoteLists.get(editorId);
+    }
+    function setup(editorId) {
+        if (quoteLists.has(editorId)) {
+            return;
+        }
+        const editor = document.getElementById(editorId);
+        if (editor === null) {
+            throw new Error(`The editor '${editorId}' does not exist.`);
+        }
+        (0, Event_1.listenToCkeditor)(editor).ready(({ ckeditor }) => {
+            if (ckeditor.features.quoteBlock) {
+                quoteLists.set(editorId, new QuoteList(editorId, ckeditor));
+            }
+        });
+    }
+});
index 3c6626ee1f5a9ea45c6a3c738a186aba2af17c99..a07af28317f88ce973dc363fa9150f39103fda55 100644 (file)
@@ -136,6 +136,8 @@ final class PreloadPhrasesCollectingListener
         $event->preload('wcf.message.share.permalink.html');
         $event->preload('wcf.message.share.socialMedia');
 
+        $event->preload('wcf.message.quote.showQuotes');
+
         $event->preload('wcf.moderation.report.reportContent');
 
         $event->preload('wcf.page.jumpTo');