From 27d1a87fb0f66ddbbc0a9d60113d09145cc3a8b3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 27 May 2025 20:36:57 +0300 Subject: [PATCH] feat(editor): allow moving blocks up/down --- docs/Release Notes/Release Notes/v0.94.0.md | 1 + packages/ckeditor5/src/augmentation.ts | 9 +- packages/ckeditor5/src/plugins.ts | 4 +- .../src/plugins/move_block_updown.ts | 95 +++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 packages/ckeditor5/src/plugins/move_block_updown.ts diff --git a/docs/Release Notes/Release Notes/v0.94.0.md b/docs/Release Notes/Release Notes/v0.94.0.md index 66d88c302..15d64f3de 100644 --- a/docs/Release Notes/Release Notes/v0.94.0.md +++ b/docs/Release Notes/Release Notes/v0.94.0.md @@ -55,6 +55,7 @@ * [Math in text notes: equations can now be displayed on multiple lines](https://github.com/TriliumNext/Notes/pull/2003) by @SiriusXT * [Metrics endpoint](https://github.com/TriliumNext/Notes/pull/2024) by @perfectra1n * Docker: Rootless [Dockerfiles are now available](https://github.com/TriliumNext/Notes/pull/1923/files) by @perfectra1n +* [Text notes: add a way to move up and down text lines via a keyboard shortcut](https://github.com/TriliumNext/Notes/issues/1002) by @dogfuntom ## 📖 Documentation diff --git a/packages/ckeditor5/src/augmentation.ts b/packages/ckeditor5/src/augmentation.ts index 607759cd9..c7a2a12fe 100644 --- a/packages/ckeditor5/src/augmentation.ts +++ b/packages/ckeditor5/src/augmentation.ts @@ -30,13 +30,18 @@ declare module "ckeditor5" { } interface EditorConfig { - syntaxHighlighting: { + syntaxHighlighting?: { loadHighlightJs: () => Promise; mapLanguageName(mimeType: string): string; defaultMimeType: string; enabled: boolean; }, - + moveBlockUp?: { + keystroke: string; + }, + moveBlockDown?: { + keystroke: string; + }, clipboard?: { copy(text: string): void; } diff --git a/packages/ckeditor5/src/plugins.ts b/packages/ckeditor5/src/plugins.ts index 29abde14c..39932ef6d 100644 --- a/packages/ckeditor5/src/plugins.ts +++ b/packages/ckeditor5/src/plugins.ts @@ -25,6 +25,7 @@ import "@triliumnext/ckeditor5-footnotes/index.css"; import "@triliumnext/ckeditor5-math/index.css"; import CodeBlockToolbar from "./plugins/code_block_toolbar.js"; import CodeBlockLanguageDropdown from "./plugins/code_block_language_dropdown.js"; +import MoveBlockUpDownPlugin from "./plugins/move_block_updown.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. @@ -42,7 +43,8 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [ Uploadfileplugin, SyntaxHighlighting, CodeBlockLanguageDropdown, - CodeBlockToolbar + CodeBlockToolbar, + MoveBlockUpDownPlugin ]; /** diff --git a/packages/ckeditor5/src/plugins/move_block_updown.ts b/packages/ckeditor5/src/plugins/move_block_updown.ts new file mode 100644 index 000000000..ef4f6a8dd --- /dev/null +++ b/packages/ckeditor5/src/plugins/move_block_updown.ts @@ -0,0 +1,95 @@ +/** + * https://github.com/TriliumNext/Notes/issues/1002 + */ + +import { Command, DocumentSelection, Element, Plugin } from 'ckeditor5'; + +export default class MoveBlockUpDownPlugin extends Plugin { + + init() { + const editor = this.editor; + editor.config.define('moveBlockUp', { + keystroke: 'ctrl+arrowup', + }); + editor.config.define('moveBlockDown', { + keystroke: 'ctrl+arrowdown', + }); + + const keystrokeUp = editor.config.get('moveBlockUp.keystroke')!; + const keystrokeDown = editor.config.get('moveBlockDown.keystroke')!; + + editor.commands.add('moveBlockUp', new MoveBlockUpCommand(editor)); + editor.commands.add('moveBlockDown', new MoveBlockDownCommand(editor)); + + editor.keystrokes.set(keystrokeUp, 'moveBlockUp'); + editor.keystrokes.set(keystrokeDown, 'moveBlockDown'); + } + +} + +abstract class MoveBlockUpDownCommand extends Command { + + abstract getSelectedBlocks(selection: DocumentSelection); + abstract getSibling(selectedBlock: Element); + abstract get offset(): "before" | "after"; + + refresh() { + const selection = this.editor.model.document.selection; + const selectedBlocks = this.getSelectedBlocks(selection); + + this.isEnabled = true; + for (const selectedBlock of selectedBlocks) { + if (!this.getSibling(selectedBlock)) this.isEnabled = false; + } + } + + execute() { + const model = this.editor.model; + const selection = model.document.selection; + const selectedBlocks = this.getSelectedBlocks(selection); + + model.change((writer) => { + for (const selectedBlock of selectedBlocks) { + const sibling = this.getSibling(selectedBlock); + if (sibling) { + const range = model.createRangeOn(selectedBlock); + writer.move(range, sibling, this.offset); + } + } + }); + } +} + +class MoveBlockUpCommand extends MoveBlockUpDownCommand { + + getSelectedBlocks(selection: DocumentSelection) { + return [...selection.getSelectedBlocks()]; + } + + getSibling(selectedBlock: Element) { + return selectedBlock.previousSibling; + } + + get offset() { + return "before" as const; + } + +} + +class MoveBlockDownCommand extends MoveBlockUpDownCommand { + + /** @override */ + getSelectedBlocks(selection: DocumentSelection) { + return [...selection.getSelectedBlocks()].reverse(); + } + + /** @override */ + getSibling(selectedBlock: Element) { + return selectedBlock.nextSibling; + } + + /** @override */ + get offset() { + return "after" as const; + } +}