|
|
|
@ -2,8 +2,7 @@
|
|
|
|
* https://github.com/TriliumNext/Notes/issues/1002
|
|
|
|
* https://github.com/TriliumNext/Notes/issues/1002
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { Command, DocumentSelection, Element, Node, Plugin } from 'ckeditor5';
|
|
|
|
import { Command, DocumentSelection, Element, Node, Plugin, Range } from 'ckeditor5';
|
|
|
|
|
|
|
|
|
|
|
|
export default class MoveBlockUpDownPlugin extends Plugin {
|
|
|
|
export default class MoveBlockUpDownPlugin extends Plugin {
|
|
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
init() {
|
|
|
|
@ -81,18 +80,26 @@ abstract class MoveBlockUpDownCommand extends Command {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Restore selection to all items if many have been moved
|
|
|
|
// Restore selection
|
|
|
|
if (
|
|
|
|
let range: Range;
|
|
|
|
startOffset <= (firstBlock.maxOffset ?? Infinity) &&
|
|
|
|
const maxStart = firstBlock.maxOffset ?? startOffset;
|
|
|
|
endOffset <= (lastBlock.maxOffset ?? Infinity)
|
|
|
|
const maxEnd = lastBlock.maxOffset ?? endOffset;
|
|
|
|
) {
|
|
|
|
// If original offsets valid within bounds, restore partial selection
|
|
|
|
writer.setSelection(
|
|
|
|
if (startOffset <= maxStart && endOffset <= maxEnd) {
|
|
|
|
writer.createRange(
|
|
|
|
const clampedStart = Math.min(startOffset, maxStart);
|
|
|
|
writer.createPositionAt(firstBlock, startOffset),
|
|
|
|
const clampedEnd = Math.min(endOffset, maxEnd);
|
|
|
|
writer.createPositionAt(lastBlock, endOffset)
|
|
|
|
range = writer.createRange(
|
|
|
|
)
|
|
|
|
writer.createPositionAt(firstBlock, clampedStart),
|
|
|
|
|
|
|
|
writer.createPositionAt(lastBlock, clampedEnd)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} else { // Fallback: select entire moved blocks (handles tables)
|
|
|
|
|
|
|
|
range = writer.createRange(
|
|
|
|
|
|
|
|
writer.createPositionBefore(firstBlock),
|
|
|
|
|
|
|
|
writer.createPositionAfter(lastBlock)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writer.setSelection(range);
|
|
|
|
|
|
|
|
this.editor.editing.view.focus();
|
|
|
|
|
|
|
|
|
|
|
|
this.scrollToSelection();
|
|
|
|
this.scrollToSelection();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -100,17 +107,27 @@ abstract class MoveBlockUpDownCommand extends Command {
|
|
|
|
|
|
|
|
|
|
|
|
getSelectedBlocks(selection: DocumentSelection) {
|
|
|
|
getSelectedBlocks(selection: DocumentSelection) {
|
|
|
|
const blocks = [...selection.getSelectedBlocks()];
|
|
|
|
const blocks = [...selection.getSelectedBlocks()];
|
|
|
|
|
|
|
|
const resolved: Element[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Selects elements (such as Mermaid) when there are no blocks
|
|
|
|
|
|
|
|
if (!blocks.length) {
|
|
|
|
|
|
|
|
const selectedObj = selection.getSelectedElement();
|
|
|
|
|
|
|
|
if (selectedObj) {
|
|
|
|
|
|
|
|
return [selectedObj];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If the selected block is an object, such as a block quote or admonition, return the entire block.
|
|
|
|
for (const block of blocks) {
|
|
|
|
if (blocks.length === 1) {
|
|
|
|
let el: Element = block;
|
|
|
|
const block = blocks[0];
|
|
|
|
// Traverse up until the parent is the root ($root) or there is no parent
|
|
|
|
const parent = block.parent;
|
|
|
|
while (el.parent && el.parent.name !== '$root') {
|
|
|
|
if (!parent?.name?.startsWith('$')) {
|
|
|
|
el = el.parent as Element;
|
|
|
|
return [parent as Element];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resolved.push(el);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return blocks;
|
|
|
|
// Deduplicate adjacent duplicates (e.g., nested selections resolving to same block)
|
|
|
|
|
|
|
|
return resolved.filter((blk, idx) => idx === 0 || blk !== resolved[idx - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
scrollToSelection() {
|
|
|
|
scrollToSelection() {
|
|
|
|
|