Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvement(caret): caret.setToBlock() offset argument improved #2922

Open
wants to merge 4 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- `Fix` - Fix when / overides selected text outside of the editor
- `DX` - Tools submodules removed from the repository
- `Improvement` - Shift + Down/Up will allow to select next/previous line instead of Inline Toolbar flipping

- `Improvement` - The API `caret.setToBlock()` offset now works across the entire block content, not just the first or last node.

### 2.30.7

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.31.0-rc.9",
"version": "2.31.0-rc.10",
"description": "Editor.js — open source block-style WYSIWYG editor with JSON output",
"main": "dist/editorjs.umd.js",
"module": "dist/editorjs.mjs",
Expand Down
63 changes: 63 additions & 0 deletions src/components/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,69 @@ export default class Dom {
right: left + rect.width,
};
}

/**
* Find text node and offset by total content offset
*
* @param {Node} root - root node to start search from
* @param {number} totalOffset - offset relative to the root node content
* @returns {{node: Node | null, offset: number}} - node and offset inside node
*/
public static getNodeByOffset(root: Node, totalOffset: number): {node: Node | null; offset: number} {
let currentOffset = 0;
let lastTextNode: Node | null = null;

const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
null
);

let node: Node | null = walker.nextNode();

while (node) {
const textContent = node.textContent;
const nodeLength = textContent === null ? 0 : textContent.length;

lastTextNode = node;

if (currentOffset + nodeLength >= totalOffset) {
break;
}

currentOffset += nodeLength;
node = walker.nextNode();
}

/**
* If no node found or last node is empty, return null
*/
if (!lastTextNode) {
return {
node: null,
offset: 0,
};
}

const textContent = lastTextNode.textContent;

if (textContent === null || textContent.length === 0) {
return {
node: null,
offset: 0,
};
}

/**
* Calculate offset inside found node
*/
const nodeOffset = Math.min(totalOffset - currentOffset, textContent.length);

return {
node: lastTextNode,
offset: nodeOffset,
};
}
}

/**
Expand Down
35 changes: 22 additions & 13 deletions src/components/modules/caret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class Caret extends Module {
* @param {Block} block - Block class
* @param {string} position - position where to set caret.
* If default - leave default behaviour and apply offset if it's passed
* @param {number} offset - caret offset regarding to the text node
* @param {number} offset - caret offset regarding to the block content
*/
public setToBlock(block: Block, position: string = this.positions.DEFAULT, offset = 0): void {
const { BlockManager, BlockSelection } = this.Editor;
Expand Down Expand Up @@ -88,23 +88,32 @@ export default class Caret extends Module {
return;
}

const nodeToSet = $.getDeepestNode(element, position === this.positions.END);
const contentLength = $.getContentLength(nodeToSet);
let nodeToSet: Node;
let offsetToSet = offset;

switch (true) {
case position === this.positions.START:
offset = 0;
break;
case position === this.positions.END:
case offset > contentLength:
offset = contentLength;
break;
if (position === this.positions.START) {
nodeToSet = $.getDeepestNode(element, false) as Node;
offsetToSet = 0;
} else if (position === this.positions.END) {
nodeToSet = $.getDeepestNode(element, true) as Node;
offsetToSet = $.getContentLength(nodeToSet);
} else {
const { node, offset: nodeOffset } = $.getNodeByOffset(element, offset);

if (node) {
nodeToSet = node;
offsetToSet = nodeOffset;
} else { // case for empty block's input
nodeToSet = $.getDeepestNode(element, false) as Node;
offsetToSet = 0;
}
}

this.set(nodeToSet as HTMLElement, offset);
this.set(nodeToSet as HTMLElement, offsetToSet);

BlockManager.setCurrentBlockByChildNode(block.holder);
BlockManager.currentBlock.currentInput = element;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
BlockManager.currentBlock!.currentInput = element;
}

/**
Expand Down
19 changes: 19 additions & 0 deletions test/cypress/support/utils/createParagraphMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { nanoid } from 'nanoid';

/**
* Creates a paragraph mock
*
* @param text - text for the paragraph
* @returns paragraph mock
*/
export function createParagraphMock(text: string): {
id: string;
type: string;
data: { text: string };
} {
return {
id: nanoid(),
type: 'paragraph',
data: { text },
};
}
Loading