mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 13:37:26 +02:00
feat(ckeditor): add copy button for inline code
This commit is contained in:
@@ -31,6 +31,7 @@ import CodeBlockLanguageDropdown from "./plugins/code_block_language_dropdown.js
|
||||
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";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -53,6 +54,7 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [
|
||||
MoveBlockUpDownPlugin,
|
||||
ScrollOnUndoRedoPlugin,
|
||||
InlineCodeNoSpellcheck,
|
||||
InlineCodeToolbar,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,28 +38,43 @@ export class CopyToClipboardCommand extends Command {
|
||||
this.executeCallback = this.editor.config.get("clipboard")?.copy;
|
||||
}
|
||||
|
||||
// Try code block first
|
||||
const codeBlockEl = selection.getFirstPosition()?.findAncestor("codeBlock");
|
||||
if (!codeBlockEl) {
|
||||
console.warn("Unable to find code block element to copy from.");
|
||||
if (codeBlockEl) {
|
||||
const codeText = Array.from(codeBlockEl.getChildren())
|
||||
.map(child => "data" in child ? child.data : "\n")
|
||||
.join("");
|
||||
this.copyText(codeText, "code block");
|
||||
return;
|
||||
}
|
||||
|
||||
const codeText = Array.from(codeBlockEl.getChildren())
|
||||
.map(child => "data" in child ? child.data : "\n")
|
||||
.join("");
|
||||
|
||||
if (codeText) {
|
||||
if (!this.executeCallback) {
|
||||
navigator.clipboard.writeText(codeText).then(() => {
|
||||
console.log('Code block copied to clipboard');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy code block', err);
|
||||
});
|
||||
} else {
|
||||
this.executeCallback(codeText);
|
||||
// Try inline code (text with 'code' attribute)
|
||||
const position = selection.getFirstPosition();
|
||||
if (position) {
|
||||
const textNode = position.textNode || position.nodeBefore || position.nodeAfter;
|
||||
if (textNode && "data" in textNode && textNode.hasAttribute?.("code")) {
|
||||
this.copyText(textNode.data as string, "inline code");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn("No code block or inline code found to copy from.");
|
||||
}
|
||||
|
||||
private copyText(text: string, source: string) {
|
||||
if (!text) {
|
||||
console.warn(`No text found in ${source}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.executeCallback) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
console.log(`${source} copied to clipboard`);
|
||||
}).catch(err => {
|
||||
console.error(`Failed to copy ${source}`, err);
|
||||
});
|
||||
} else {
|
||||
console.warn('No code block selected or found.');
|
||||
this.executeCallback(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
126
packages/ckeditor5/src/plugins/inline_code_toolbar.ts
Normal file
126
packages/ckeditor5/src/plugins/inline_code_toolbar.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { BalloonPanelView, ButtonView, Plugin, ToolbarView } from "ckeditor5";
|
||||
import CopyToClipboardButton from "./copy_to_clipboard_button";
|
||||
import copyIcon from "../icons/copy.svg?raw";
|
||||
|
||||
/**
|
||||
* Shows a small toolbar with a copy button when the cursor is on inline code.
|
||||
*/
|
||||
export default class InlineCodeToolbar extends Plugin {
|
||||
|
||||
static get requires() {
|
||||
return [CopyToClipboardButton] as const;
|
||||
}
|
||||
|
||||
private balloon?: BalloonPanelView;
|
||||
private toolbar?: ToolbarView;
|
||||
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
|
||||
// Create toolbar with copy button
|
||||
this.toolbar = new ToolbarView(editor.locale);
|
||||
const copyButton = new ButtonView(editor.locale);
|
||||
copyButton.set({
|
||||
icon: copyIcon,
|
||||
tooltip: "Copy to clipboard"
|
||||
});
|
||||
copyButton.on("execute", () => {
|
||||
editor.execute("copyToClipboard");
|
||||
this.hideToolbar();
|
||||
});
|
||||
this.toolbar.items.add(copyButton);
|
||||
|
||||
// Create balloon panel
|
||||
this.balloon = new BalloonPanelView(editor.locale);
|
||||
this.balloon.content.add(this.toolbar);
|
||||
this.balloon.class = "ck-toolbar-container";
|
||||
|
||||
editor.ui.view.body.add(this.balloon);
|
||||
|
||||
// Show/hide based on selection
|
||||
this.listenTo(editor.model.document.selection, "change:range", () => {
|
||||
this.updateToolbarVisibility();
|
||||
});
|
||||
|
||||
// Hide on editor blur
|
||||
this.listenTo(editor.ui.focusTracker, "change:isFocused", (_evt, _name, isFocused) => {
|
||||
if (!isFocused) {
|
||||
this.hideToolbar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateToolbarVisibility() {
|
||||
const editor = this.editor;
|
||||
const selection = editor.model.document.selection;
|
||||
const position = selection.getFirstPosition();
|
||||
|
||||
// Don't show for code blocks (they have their own toolbar)
|
||||
if (position?.findAncestor("codeBlock")) {
|
||||
this.hideToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if cursor is on inline code
|
||||
const textNode = position?.textNode;
|
||||
if (textNode?.hasAttribute("code")) {
|
||||
this.showToolbar(textNode);
|
||||
} else {
|
||||
this.hideToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
private showToolbar(textNode: unknown) {
|
||||
if (!this.balloon) return;
|
||||
|
||||
const editor = this.editor;
|
||||
const view = editor.editing.view;
|
||||
const mapper = editor.editing.mapper;
|
||||
|
||||
// Map model text node to view element
|
||||
const viewRange = mapper.toViewRange(editor.model.createRangeOn(textNode as any));
|
||||
const viewElement = viewRange.getContainedElement();
|
||||
|
||||
if (!viewElement) {
|
||||
this.hideToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
const domElement = view.domConverter.mapViewToDom(viewElement);
|
||||
if (!domElement || !(domElement instanceof HTMLElement)) {
|
||||
this.hideToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = domElement.getBoundingClientRect();
|
||||
this.balloon.pin({
|
||||
target: {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
});
|
||||
this.balloon.isVisible = true;
|
||||
}
|
||||
|
||||
private hideToolbar() {
|
||||
if (this.balloon) {
|
||||
this.balloon.isVisible = false;
|
||||
this.balloon.unpin();
|
||||
}
|
||||
}
|
||||
|
||||
override destroy() {
|
||||
super.destroy();
|
||||
if (this.balloon) {
|
||||
this.balloon.destroy();
|
||||
}
|
||||
if (this.toolbar) {
|
||||
this.toolbar.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user