|
2 | 2 | * https://github.com/TriliumNext/Notes/issues/1002
|
3 | 3 | */
|
4 | 4 |
|
5 |
| -import { Command, DocumentSelection, Element, Node, Plugin } from 'ckeditor5'; |
6 |
| - |
| 5 | +import { Command, DocumentSelection, Element, Node, Plugin, Range } from 'ckeditor5'; |
7 | 6 | export default class MoveBlockUpDownPlugin extends Plugin {
|
8 | 7 |
|
9 | 8 | init() {
|
@@ -81,36 +80,54 @@ abstract class MoveBlockUpDownCommand extends Command {
|
81 | 80 | }
|
82 | 81 | }
|
83 | 82 |
|
84 |
| - // Restore selection to all items if many have been moved |
85 |
| - if ( |
86 |
| - startOffset <= (firstBlock.maxOffset ?? Infinity) && |
87 |
| - endOffset <= (lastBlock.maxOffset ?? Infinity) |
88 |
| - ) { |
89 |
| - writer.setSelection( |
90 |
| - writer.createRange( |
91 |
| - writer.createPositionAt(firstBlock, startOffset), |
92 |
| - writer.createPositionAt(lastBlock, endOffset) |
93 |
| - ) |
| 83 | + // Restore selection |
| 84 | + let range: Range; |
| 85 | + const maxStart = firstBlock.maxOffset ?? startOffset; |
| 86 | + const maxEnd = lastBlock.maxOffset ?? endOffset; |
| 87 | + // If original offsets valid within bounds, restore partial selection |
| 88 | + if (startOffset <= maxStart && endOffset <= maxEnd) { |
| 89 | + const clampedStart = Math.min(startOffset, maxStart); |
| 90 | + const clampedEnd = Math.min(endOffset, maxEnd); |
| 91 | + range = writer.createRange( |
| 92 | + writer.createPositionAt(firstBlock, clampedStart), |
| 93 | + writer.createPositionAt(lastBlock, clampedEnd) |
| 94 | + ); |
| 95 | + } else { // Fallback: select entire moved blocks (handles tables) |
| 96 | + range = writer.createRange( |
| 97 | + writer.createPositionBefore(firstBlock), |
| 98 | + writer.createPositionAfter(lastBlock) |
94 | 99 | );
|
95 | 100 | }
|
| 101 | + writer.setSelection(range); |
| 102 | + this.editor.editing.view.focus(); |
96 | 103 |
|
97 | 104 | this.scrollToSelection();
|
98 |
| - }); |
| 105 | + }); |
99 | 106 | }
|
100 | 107 |
|
101 | 108 | getSelectedBlocks(selection: DocumentSelection) {
|
102 | 109 | const blocks = [...selection.getSelectedBlocks()];
|
| 110 | + const resolved: Element[] = []; |
| 111 | + |
| 112 | + // Selects elements (such as Mermaid) when there are no blocks |
| 113 | + if (!blocks.length) { |
| 114 | + const selectedObj = selection.getSelectedElement(); |
| 115 | + if (selectedObj) { |
| 116 | + return [selectedObj]; |
| 117 | + } |
| 118 | + } |
103 | 119 |
|
104 |
| - // If the selected block is an object, such as a block quote or admonition, return the entire block. |
105 |
| - if (blocks.length === 1) { |
106 |
| - const block = blocks[0]; |
107 |
| - const parent = block.parent; |
108 |
| - if (!parent?.name?.startsWith('$')) { |
109 |
| - return [parent as Element]; |
| 120 | + for (const block of blocks) { |
| 121 | + let el: Element = block; |
| 122 | + // Traverse up until the parent is the root ($root) or there is no parent |
| 123 | + while (el.parent && el.parent.name !== '$root') { |
| 124 | + el = el.parent as Element; |
110 | 125 | }
|
| 126 | + resolved.push(el); |
111 | 127 | }
|
112 | 128 |
|
113 |
| - return blocks; |
| 129 | + // Deduplicate adjacent duplicates (e.g., nested selections resolving to same block) |
| 130 | + return resolved.filter((blk, idx) => idx === 0 || blk !== resolved[idx - 1]); |
114 | 131 | }
|
115 | 132 |
|
116 | 133 | scrollToSelection() {
|
|
0 commit comments