From 88c548cc704cddae45510fe8dd33f65dda20032c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 11 Apr 2026 00:47:36 +0300 Subject: [PATCH] feat(ckeditor): add a toolbar to switch admonition types --- packages/ckeditor5/src/plugins.ts | 4 + .../src/plugins/admonition_toolbar.ts | 55 +++++++++++++ .../src/plugins/admonition_type_dropdown.ts | 79 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 packages/ckeditor5/src/plugins/admonition_toolbar.ts create mode 100644 packages/ckeditor5/src/plugins/admonition_type_dropdown.ts diff --git a/packages/ckeditor5/src/plugins.ts b/packages/ckeditor5/src/plugins.ts index 29ca454485..2d0f0de054 100644 --- a/packages/ckeditor5/src/plugins.ts +++ b/packages/ckeditor5/src/plugins.ts @@ -32,6 +32,8 @@ import MoveBlockUpDownPlugin from "./plugins/move_block_updown.js"; import ScrollOnUndoRedoPlugin from "./plugins/scroll_on_undo_redo.js" import InlineCodeNoSpellcheck from "./plugins/inline_code_no_spellcheck.js"; import InlineCodeToolbar from "./plugins/inline_code_toolbar.js"; +import AdmonitionTypeDropdown from "./plugins/admonition_type_dropdown.js"; +import AdmonitionToolbar from "./plugins/admonition_toolbar.js"; /** * Plugins that are specific to Trilium and not part of the CKEditor 5 core, included in both text editors but not in the attribute editor. @@ -55,6 +57,8 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [ ScrollOnUndoRedoPlugin, InlineCodeNoSpellcheck, InlineCodeToolbar, + AdmonitionTypeDropdown, + AdmonitionToolbar, ]; /** diff --git a/packages/ckeditor5/src/plugins/admonition_toolbar.ts b/packages/ckeditor5/src/plugins/admonition_toolbar.ts new file mode 100644 index 0000000000..1eb5320eaa --- /dev/null +++ b/packages/ckeditor5/src/plugins/admonition_toolbar.ts @@ -0,0 +1,55 @@ +import { Plugin, ViewDocumentFragment, WidgetToolbarRepository, type ViewNode } from "ckeditor5"; +import { Admonition } from "@triliumnext/ckeditor5-admonition"; +import AdmonitionTypeDropdown from "./admonition_type_dropdown"; + +export default class AdmonitionToolbar extends Plugin { + + static get requires() { + return [WidgetToolbarRepository, Admonition, AdmonitionTypeDropdown] as const; + } + + afterInit() { + const editor = this.editor; + const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository); + + widgetToolbarRepository.register("admonition", { + items: [ + "admonitionTypeDropdown" + ], + balloonClassName: "ck-toolbar-container admonition-type-list", + getRelatedElement(selection) { + const selectionPosition = selection.getFirstPosition(); + if (!selectionPosition) { + return null; + } + + let parent: ViewNode | ViewDocumentFragment | null = selectionPosition.parent; + while (parent) { + if (parent.is("element", "aside") || parent.is("element", "div")) { + // Check if it's an admonition by looking for the admonition class + const classes = (parent as any).getAttribute?.("class") || ""; + if (typeof classes === "string" && classes.includes("admonition")) { + return parent; + } + } + parent = parent.parent; + } + + return null; + } + }); + + // Hide balloon toolbar when in an admonition + if (editor.plugins.has("BalloonToolbar")) { + editor.listenTo(editor.plugins.get("BalloonToolbar"), "show", (evt) => { + const firstPosition = editor.model.document.selection.getFirstPosition(); + const isInAdmonition = firstPosition?.findAncestor("aside"); + + if (isInAdmonition) { + evt.stop(); // Prevent the balloon toolbar from showing + } + }, { priority: "high" }); + } + } + +} diff --git a/packages/ckeditor5/src/plugins/admonition_type_dropdown.ts b/packages/ckeditor5/src/plugins/admonition_type_dropdown.ts new file mode 100644 index 0000000000..8769be145c --- /dev/null +++ b/packages/ckeditor5/src/plugins/admonition_type_dropdown.ts @@ -0,0 +1,79 @@ +import { Plugin, type ListDropdownButtonDefinition, Collection, ViewModel, createDropdown, addListToDropdown, DropdownButtonView } from "ckeditor5"; +import { Admonition, ADMONITION_TYPES, type AdmonitionCommand, type AdmonitionType } from "@triliumnext/ckeditor5-admonition"; + +const ADMONITION_ICONS: Record = { + note: "📝", + tip: "💡", + important: "📌", + caution: "⚠️", + warning: "🚨" +}; + +/** + * Toolbar item which displays the list of admonition types in a dropdown. + */ +export default class AdmonitionTypeDropdown extends Plugin { + + static get requires() { + return [Admonition] as const; + } + + public init() { + const editor = this.editor; + const componentFactory = editor.ui.componentFactory; + + const itemDefinitions = this._getTypeListItemDefinitions(); + const command = editor.commands.get("admonition") as AdmonitionCommand; + + componentFactory.add("admonitionTypeDropdown", _locale => { + const dropdownView = createDropdown(editor.locale, DropdownButtonView); + dropdownView.buttonView.set({ + withText: true + }); + dropdownView.bind("isEnabled").to(command, "value", value => !!value); + dropdownView.buttonView.bind("label").to(command, "value", (value) => { + if (!value) return ""; + const typeDef = ADMONITION_TYPES[value as AdmonitionType]; + const icon = ADMONITION_ICONS[value as AdmonitionType]; + return typeDef ? `${icon} ${typeDef.title}` : value; + }); + dropdownView.on("execute", evt => { + const source = evt.source as any; + editor.execute("admonition", { + forceValue: source._admonitionType + }); + editor.editing.view.focus(); + }); + addListToDropdown(dropdownView, itemDefinitions); + return dropdownView; + }); + } + + private _getTypeListItemDefinitions(): Collection { + const editor = this.editor; + const command = editor.commands.get("admonition") as AdmonitionCommand; + const itemDefinitions = new Collection(); + + for (const [type, typeDef] of Object.entries(ADMONITION_TYPES)) { + const icon = ADMONITION_ICONS[type as AdmonitionType]; + const definition: ListDropdownButtonDefinition = { + type: "button", + model: new ViewModel({ + _admonitionType: type, + label: `${icon} ${typeDef.title}`, + role: "menuitemradio", + withText: true + }) + }; + + definition.model.bind("isOn").to(command, "value", value => { + return value === type; + }); + + itemDefinitions.add(definition); + } + + return itemDefinitions; + } + +}