Implement navigateNext()

This commit is contained in:
Tomoyuki Hata 2021-01-31 23:28:25 +09:00
parent 5f64c0c566
commit 75c3010443
4 changed files with 121 additions and 36 deletions

View file

@ -16,6 +16,7 @@
},
"globals": {
"Node": true,
"NodeFilter": true,
"Range": true,
"HTMLElement": true,
"HTMLDivElement": true,

1
dist/editor.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -403,16 +403,17 @@ export default class BlockEvents extends Module {
this.Editor.BlockManager.clearFocused();
this.Editor.Toolbar.close();
const isDownPressed = event.keyCode === _.keyCodes.DOWN;
const shouldEnableCBS = this.Editor.Caret.isAtEnd || this.Editor.BlockSelection.anyBlockSelected;
if (event.shiftKey && event.keyCode === _.keyCodes.DOWN && shouldEnableCBS) {
if (event.shiftKey && isDownPressed && shouldEnableCBS) {
this.Editor.CrossBlockSelection.toggleBlockSelectedState();
return;
}
const navigateNext = event.keyCode === _.keyCodes.DOWN || (event.keyCode === _.keyCodes.RIGHT && !this.isRtl);
const isNavigated = navigateNext ? this.Editor.Caret.navigateNext() : this.Editor.Caret.navigatePrevious();
const navigateNext = isDownPressed || (event.keyCode === _.keyCodes.RIGHT && !this.isRtl);
const isNavigated = navigateNext ? this.Editor.Caret.navigateNext(isDownPressed) : this.Editor.Caret.navigatePrevious();
if (isNavigated) {
/**
@ -470,7 +471,7 @@ export default class BlockEvents extends Module {
}
const navigatePrevious = event.keyCode === _.keyCodes.UP || (event.keyCode === _.keyCodes.LEFT && !this.isRtl);
const isNavigated = navigatePrevious ? this.Editor.Caret.navigatePrevious() : this.Editor.Caret.navigateNext();
const isNavigated = navigatePrevious ? this.Editor.Caret.navigatePrevious() : this.Editor.Caret.navigateNext(false);
if (isNavigated) {
/**

View file

@ -388,44 +388,25 @@ export default class Caret extends Module {
* Before moving caret, we should check if caret position is at the end of Plugins node
* Using {@link Dom#getDeepestNode} to get a last node and match with current selection
*
* @param {boolean} isDownPressed - Is Down key pressed
*
* @returns {boolean}
*/
public navigateNext(): boolean {
const { BlockManager, Tools } = this.Editor;
const { currentBlock, nextContentfulBlock } = BlockManager;
const { nextInput } = currentBlock;
const isAtEnd = this.isAtEnd;
public navigateNext(isDownPressed: boolean): boolean {
const shouldNavigateToNext = this.isAtEnd || (isDownPressed && !this.isNextLineExisted());
const next = shouldNavigateToNext && this.detectNext();
let nextBlock = nextContentfulBlock;
if (next) {
const offset = isDownPressed ? next.downOffset : 0;
const position = offset === undefined ? this.positions.END : this.positions.DEFAULT;
if (!nextBlock && !nextInput) {
/**
* This code allows to exit from the last non-initial tool:
* https://github.com/codex-team/editor.js/issues/1103
*/
/**
* 1. If there is a last block and it is default, do nothing
* 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing
* (https://github.com/codex-team/editor.js/issues/1414)
*/
if (Tools.isDefault(currentBlock.tool) || !isAtEnd) {
return false;
}
/**
* If there is no nextBlock, but currentBlock is not default,
* insert new default block at the end and navigate to it
*/
nextBlock = BlockManager.insertAtEnd();
}
if (isAtEnd) {
/** If next Tool`s input exists, focus on it. Otherwise set caret to the next Block */
if (!nextInput) {
this.setToBlock(nextBlock, this.positions.START);
if (next.nextInput) {
// TODO: It may be have a bug with ignore inline tags
this.setToInput(next.nextInput, position, offset);
} else {
this.setToInput(nextInput, this.positions.START);
// TODO: It may be have a bug with ignore inline tags
this.setToBlock(next.nextBlock, position, offset);
}
return true;
@ -548,6 +529,73 @@ export default class Caret extends Module {
selection.addRange(newRange);
}
/**
* TODO: JSDoc
* TODO: specify return type
*/
private detectNext() {
const { BlockManager, Tools } = this.Editor;
const nextInput = BlockManager.currentBlock.nextInput;
let nextBlock = BlockManager.nextContentfulBlock;
if (!nextBlock && !nextInput) {
/**
* If there is a last block and it is default, do nothing
* This code allows to exit from the last non-initial tool:
* https://github.com/codex-team/editor.js/issues/1103
*/
if (Tools.isDefault(BlockManager.currentBlock.tool)) {
return false;
}
/**
* If there is no nextBlock, but currentBlock is not default,
* insert new default block at the end and navigate to it
*/
nextBlock = BlockManager.insertAtEnd();
}
const currentBoundingClientRect = Selection.get().getRangeAt(0)
.getBoundingClientRect();
const range = new Range();
const treeWalker = document.createTreeWalker(nextInput ?? nextBlock.firstInput, NodeFilter.SHOW_TEXT);
let node = treeWalker.firstChild();
let offset = 0;
let prevX: number | undefined;
while (node) {
if (!(node instanceof Text)) {
throw new Error('Unexpected node type');
}
for (let index = 0; index < node.length; index++) {
range.setStart(node, index);
const boundingClientRect = range.getBoundingClientRect();
if (prevX !== undefined && Math.abs(currentBoundingClientRect.x - prevX) < Math.abs(currentBoundingClientRect.x - boundingClientRect.x)) {
return {
nextBlock,
nextInput,
downOffset: offset - 1,
};
}
offset++;
prevX = boundingClientRect.x;
}
node = treeWalker.nextNode();
}
return {
nextBlock,
nextInput,
};
}
/**
* Get all first-level (first child of [contenteditabel]) siblings from passed node
* Then you can check it for emptiness
@ -591,4 +639,38 @@ export default class Caret extends Module {
return siblings;
}
/**
* TODO: JSDoc
*/
private isNextLineExisted(): boolean {
const { BlockManager } = this.Editor;
const currentBoundingClientRect = Selection.get().getRangeAt(0)
.getBoundingClientRect();
const range = new Range();
const treeWalker = document.createTreeWalker(BlockManager.currentBlock.currentInput, NodeFilter.SHOW_TEXT);
let node = treeWalker.firstChild();
while (node) {
if (!(node instanceof Text)) {
throw new Error('Unexpected node type');
}
for (let index = 0; index < node.length; index++) {
range.setStart(node, index);
const boundingClientRect = range.getBoundingClientRect();
if (currentBoundingClientRect.y < boundingClientRect.y) {
return true;
}
}
node = treeWalker.nextNode();
}
return false;
}
}