Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 1x 1x 1x | import {Editor, Element as SlateElement, Node, Text, type Path, type Range} from 'slate';
import {getActiveEmbed} from './embed';
import {getActiveMathBlockEntry, getActiveMathInlineEntry} from './math';
import {getLinkAttributes} from './link';
import type {CustomElement, CustomText} from '../types';
const CARET_MARK_ORDER: Array<[keyof Pick<CustomText, 'bold' | 'italic' | 'underline' | 'code'>, string]> = [
['bold', 'bold'],
['italic', 'italic'],
['underline', 'underline'],
['code', 'code'],
];
export interface CaretPathInfo {
path: string[];
linkHref?: string;
embedUrl?: string;
codeText?: string;
mathThingId?: string;
}
/** Get text of an inline mark inside which `Path` is located. */
const getMarkText = (editor: Editor, path: Path, type: keyof CustomText): string => {
const node = Node.get(editor, path);
Iif (!Text.isText(node) || node[type] !== true) return '';
const [parentElement, parentPath] = Editor.parent(editor, path);
Iif (!parentElement) return '';
const textIndex = path[path.length - 1];
const children = parentElement.children;
let left = textIndex;
let right = textIndex;
while (left > 0) {
const leftNode = children[left - 1];
if (!Text.isText(leftNode) || leftNode[type] !== true) break;
left--;
}
while (right < children.length - 1) {
const rightNode = children[right + 1];
Eif (!Text.isText(rightNode) || rightNode[type] !== true) break;
right++;
}
const startPath = [...parentPath, left];
const endPath = [...parentPath, right];
const range: Range = {
anchor: Editor.start(editor, startPath),
focus: Editor.end(editor, endPath),
};
return Editor.string(editor, range);
};
export const getCaretPathInfo = (editor: Editor): CaretPathInfo => {
const {selection} = editor;
Iif (!selection) return {path: []};
const activeEmbed = getActiveEmbed(editor);
if (activeEmbed) {
return {
path: ['embed'],
embedUrl: activeEmbed.url,
};
}
const activeMathBlock = getActiveMathBlockEntry(editor);
Iif (activeMathBlock) {
return {
path: ['math'],
mathThingId: activeMathBlock[0]['@thing'],
};
}
const activeMathInline = getActiveMathInlineEntry(editor);
Iif (activeMathInline) {
return {
path: ['math-inline'],
mathThingId: activeMathInline[0]['@thing'],
};
}
const point = selection.focus;
const segments: string[] = [];
const [blockMatch] = Editor.nodes(editor, {
at: point,
match: (node) => SlateElement.isElement(node) && Editor.isBlock(editor, node),
});
Eif (blockMatch) {
const [element] = blockMatch as [CustomElement, number[]];
segments.push(element.type);
}
let textNode: CustomText | null = null;
try {
const node = Node.get(editor, point.path);
Eif (Text.isText(node)) textNode = node as CustomText;
} catch {}
const marks = (Editor.marks(editor) ?? {}) as Partial<CustomText>;
const markState: Partial<CustomText> = {...(textNode ?? {text: ''}), ...marks};
const codeText = markState.code && textNode ? getMarkText(editor, point.path, 'code') : '';
for (const [key, label] of CARET_MARK_ORDER) {
if (markState[key]) segments.push(label);
}
const link = getLinkAttributes(markState);
Iif (link) segments.push('link');
return {
path: segments,
...(link ? {linkHref: link.href} : {}),
...(codeText ? {codeText} : {}),
};
};
|