mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-19 11:06:25 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
|
||||
import type { VElement, VLine } from '../components/index.js';
|
||||
import { INLINE_ROOT_ATTR, ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import type { DomPoint, TextPoint } from '../types.js';
|
||||
import {
|
||||
isInlineRoot,
|
||||
isNativeTextInVText,
|
||||
isVElement,
|
||||
isVLine,
|
||||
} from './guard.js';
|
||||
import { calculateTextLength, getTextNodesFromElement } from './text.js';
|
||||
|
||||
export function nativePointToTextPoint(
|
||||
node: unknown,
|
||||
offset: number
|
||||
): TextPoint | null {
|
||||
if (isNativeTextInVText(node)) {
|
||||
return [node, offset];
|
||||
}
|
||||
|
||||
if (isVElement(node)) {
|
||||
const texts = getTextNodesFromElement(node);
|
||||
const vElement = texts[0].parentElement?.closest('[data-v-element="true"]');
|
||||
|
||||
if (
|
||||
texts.length === 1 &&
|
||||
vElement instanceof HTMLElement &&
|
||||
vElement.dataset.vEmbed === 'true'
|
||||
) {
|
||||
return [texts[0], 0];
|
||||
}
|
||||
|
||||
if (texts.length > 0) {
|
||||
return texts[offset] ? [texts[offset], 0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
if (isVLine(node) || isInlineRoot(node)) {
|
||||
return getTextPointRoughlyFromElementByOffset(node, offset, true);
|
||||
}
|
||||
|
||||
if (!(node instanceof Node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const vNodes = getVNodesFromNode(node);
|
||||
|
||||
if (vNodes) {
|
||||
return getTextPointFromVNodes(vNodes, node, offset);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function textPointToDomPoint(
|
||||
text: Text,
|
||||
offset: number,
|
||||
rootElement: HTMLElement
|
||||
): DomPoint | null {
|
||||
if (rootElement.dataset.vRoot !== 'true') {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.InlineEditorError,
|
||||
'textRangeToDomPoint should be called with editor root element'
|
||||
);
|
||||
}
|
||||
|
||||
if (!rootElement.contains(text)) return null;
|
||||
|
||||
const texts = getTextNodesFromElement(rootElement);
|
||||
if (texts.length === 0) return null;
|
||||
|
||||
const goalIndex = texts.indexOf(text);
|
||||
let index = 0;
|
||||
for (const text of texts.slice(0, goalIndex)) {
|
||||
index += calculateTextLength(text);
|
||||
}
|
||||
|
||||
if (text.wholeText !== ZERO_WIDTH_SPACE) {
|
||||
index += offset;
|
||||
}
|
||||
|
||||
const textParentElement = text.parentElement;
|
||||
if (!textParentElement) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.InlineEditorError,
|
||||
'text element parent not found'
|
||||
);
|
||||
}
|
||||
|
||||
const lineElement = textParentElement.closest('v-line');
|
||||
|
||||
if (!lineElement) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.InlineEditorError,
|
||||
'line element not found'
|
||||
);
|
||||
}
|
||||
|
||||
const lineIndex = Array.from(rootElement.querySelectorAll('v-line')).indexOf(
|
||||
lineElement
|
||||
);
|
||||
|
||||
return { text, index: index + lineIndex };
|
||||
}
|
||||
|
||||
function getVNodesFromNode(node: Node): VElement[] | VLine[] | null {
|
||||
const vLine = node.parentElement?.closest('v-line');
|
||||
|
||||
if (vLine) {
|
||||
return Array.from(vLine.querySelectorAll('v-element'));
|
||||
}
|
||||
|
||||
const container =
|
||||
node instanceof Element
|
||||
? node.closest(`[${INLINE_ROOT_ATTR}]`)
|
||||
: node.parentElement?.closest(`[${INLINE_ROOT_ATTR}]`);
|
||||
|
||||
if (container) {
|
||||
return Array.from(container.querySelectorAll('v-line'));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTextPointFromVNodes(
|
||||
vNodes: VLine[] | VElement[],
|
||||
node: Node,
|
||||
offset: number
|
||||
): TextPoint | null {
|
||||
const first = vNodes[0];
|
||||
for (let i = 0; i < vNodes.length; i++) {
|
||||
const vNode = vNodes[i];
|
||||
|
||||
if (i === 0 && AFollowedByB(node, vNode)) {
|
||||
return getTextPointRoughlyFromElementByOffset(first, offset, true);
|
||||
}
|
||||
|
||||
if (AInsideB(node, vNode)) {
|
||||
return getTextPointRoughlyFromElementByOffset(first, offset, false);
|
||||
}
|
||||
|
||||
if (i === vNodes.length - 1 && APrecededByB(node, vNode)) {
|
||||
return getTextPointRoughlyFromElement(vNode);
|
||||
}
|
||||
|
||||
if (
|
||||
i < vNodes.length - 1 &&
|
||||
APrecededByB(node, vNode) &&
|
||||
AFollowedByB(node, vNodes[i + 1])
|
||||
) {
|
||||
return getTextPointRoughlyFromElement(vNode);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTextPointRoughlyFromElement(element: Element): TextPoint | null {
|
||||
const texts = getTextNodesFromElement(element);
|
||||
if (texts.length === 0) return null;
|
||||
const text = texts[texts.length - 1];
|
||||
return [text, calculateTextLength(text)];
|
||||
}
|
||||
|
||||
function getTextPointRoughlyFromElementByOffset(
|
||||
element: Element,
|
||||
offset: number,
|
||||
fromStart: boolean
|
||||
): TextPoint | null {
|
||||
const texts = getTextNodesFromElement(element);
|
||||
if (texts.length === 0) return null;
|
||||
const text = fromStart ? texts[0] : texts[texts.length - 1];
|
||||
return [text, offset === 0 ? offset : text.length];
|
||||
}
|
||||
|
||||
function AInsideB(a: Node, b: Node): boolean {
|
||||
return (
|
||||
b.compareDocumentPosition(a) === Node.DOCUMENT_POSITION_CONTAINED_BY ||
|
||||
b.compareDocumentPosition(a) ===
|
||||
(Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING)
|
||||
);
|
||||
}
|
||||
|
||||
function AFollowedByB(a: Node, b: Node): boolean {
|
||||
return a.compareDocumentPosition(b) === Node.DOCUMENT_POSITION_FOLLOWING;
|
||||
}
|
||||
|
||||
function APrecededByB(a: Node, b: Node): boolean {
|
||||
return a.compareDocumentPosition(b) === Node.DOCUMENT_POSITION_PRECEDING;
|
||||
}
|
||||
Reference in New Issue
Block a user