feat(ckeditor): add a toolbar to switch admonition types

This commit is contained in:
Elian Doran
2026-04-11 00:47:36 +03:00
parent daafe251da
commit 88c548cc70
3 changed files with 138 additions and 0 deletions

View File

@@ -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,
];
/**

View File

@@ -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" });
}
}
}

View File

@@ -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<AdmonitionType, string> = {
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<ListDropdownButtonDefinition> {
const editor = this.editor;
const command = editor.commands.get("admonition") as AdmonitionCommand;
const itemDefinitions = new Collection<ListDropdownButtonDefinition>();
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;
}
}