chore: replace all cn comments of fe to en version by volc api (#320)

This commit is contained in:
tecvan
2025-07-31 10:32:15 +08:00
committed by GitHub
parent 716ec0cba8
commit 71f6245a01
2960 changed files with 15545 additions and 15545 deletions

View File

@@ -22,12 +22,12 @@ describe('compareNodePosition', () => {
let siblingNode: HTMLElement;
beforeEach(() => {
// 在每个测试之前创建新的 DOM 结构
// Create a new DOM structure before each test
parentNode = document.createElement('div');
childNode = document.createElement('span');
siblingNode = document.createElement('p');
parentNode.appendChild(childNode); // childNode parentNode 的子节点
parentNode.appendChild(siblingNode); // siblingNode childNode 的同级节点
parentNode.appendChild(childNode); // childNode is a sub-node of parentNode
parentNode.appendChild(siblingNode); // siblingNode is a sibling of childNode
});
it('should return "before" if nodeA is before nodeB', () => {

View File

@@ -17,7 +17,7 @@
import { findAncestorNodeByTagName } from '../src/utils/helper/find-ancestor-node-by-tag-name';
describe('findAncestorNodeByTagName', () => {
// 设置 DOM 环境
// Setting up the DOM environment
document.body.innerHTML = `
<div id="ancestor">
<div id="parent">
@@ -41,7 +41,7 @@ describe('findAncestorNodeByTagName', () => {
it('should return the node itself if it matches the tag name', () => {
const result = findAncestorNodeByTagName(child, 'div');
expect(result).toBe(parent); // 因为 child 的直接父级 parent 也是 div
expect(result).toBe(parent); // Because the child's immediate parent is also a div
});
it('should return null if the input node is null', () => {

View File

@@ -18,7 +18,7 @@ import { findLastChildNode } from '../src/utils/helper/find-last-child-node';
describe('findLastChildNode', () => {
it('should return the last child node of a nested node structure', () => {
// 创建一个嵌套的节点结构
// Create a nested node structure
const parentNode = document.createElement('div');
const childNode1 = document.createElement('span');
const childNode2 = document.createElement('p');
@@ -28,21 +28,21 @@ describe('findLastChildNode', () => {
childNode1.appendChild(childNode2);
childNode2.appendChild(lastChildNode);
// 调用 findLastChildNode 函数
// Call the findLastChildNode function
const result = findLastChildNode(parentNode);
// 验证结果是否为最深层的子节点
// Verify that the result is the deepest sub-node
expect(result).toBe(lastChildNode);
});
it('should return the node itself if it has no children', () => {
// 创建一个没有子节点的节点
// Create a node without a sub-node
const singleNode = document.createElement('div');
// 调用 findLastChildNode 函数
// Call the findLastChildNode function
const result = findLastChildNode(singleNode);
// 验证结果是否为节点本身
// Verify whether the result is the node itself
expect(result).toBe(singleNode);
});
});

View File

@@ -17,7 +17,7 @@
import { findLastSiblingNode } from '../src/utils/helper/find-last-sibling-node';
describe('findLastSiblingNode', () => {
// 设置 DOM 环境
// Setting up the DOM environment
document.body.innerHTML = `
<div id="ancestor">
<div id="sibling1" data-scope="valid"></div>

View File

@@ -17,7 +17,7 @@
import { findNearestAnchor } from '../src/utils/helper/find-nearest-link-node';
describe('findNearestAnchor', () => {
// 设置 DOM 环境
// Setting up the DOM environment
document.body.innerHTML = `
<div>
<a id="anchor1" href="#">

View File

@@ -20,7 +20,7 @@ describe('getAllChildNodesInNode', () => {
let root: HTMLElement;
beforeEach(() => {
// 在每个测试用例之前设置 DOM 结构
// Setting up the DOM structure before each test case
document.body.innerHTML = `
<div id="root">
<span>Text 1</span>
@@ -46,8 +46,8 @@ describe('getAllChildNodesInNode', () => {
it('should correctly handle text and element nodes', () => {
const nodes = getAllChildNodesInNode(root);
// 检查返回的节点类型是否正确
expect(nodes.some(node => node.nodeType === Node.TEXT_NODE)).toBe(true); // 至少有一个文本节点
expect(nodes.some(node => node.nodeType === Node.ELEMENT_NODE)).toBe(true); // 至少有一个元素节点
// Check if the returned node type is correct
expect(nodes.some(node => node.nodeType === Node.TEXT_NODE)).toBe(true); // At least one text node
expect(nodes.some(node => node.nodeType === Node.ELEMENT_NODE)).toBe(true); // There is at least one element node
});
});

View File

@@ -20,7 +20,7 @@ describe('getAllNodesInRange', () => {
let root: HTMLElement;
beforeEach(() => {
// 在每个测试用例之前设置 DOM 结构
// Setting up the DOM structure before each test case
document.body.innerHTML = `
<div id="root">
<span>Text 1</span>
@@ -35,11 +35,11 @@ describe('getAllNodesInRange', () => {
it('should return all nodes within a range, including text and element nodes', () => {
const range = new Range();
range.setStart(root, 0); // 设置范围开始于 root 的第一个子节点
range.setEnd(root, 3); // 设置范围结束于 root 的最后一个子节点
range.setStart(root, 0); // Setup scope starts at the first sub-node of root
range.setEnd(root, 3); // The setting range ends at the last sub-node of root.
const nodes = getAllNodesInRange(range);
// 预期包含:span, div, Text 3
// Expected to include: span, div, Text 3
expect(nodes.length).toBe(5);
expect((nodes[0] as Node).nodeType).toBe(Node.ELEMENT_NODE); // span
expect((nodes[1] as Node).nodeType).toBe(Node.TEXT_NODE); // text
@@ -50,8 +50,8 @@ describe('getAllNodesInRange', () => {
it('should return an empty array if the range is collapsed', () => {
const range = document.createRange();
range.setStart(root, 1); // 设置范围的开始和结束都在同一位置
range.setEnd(root, 1); // 这将创建一个折叠的范围,即没有包含任何节点
range.setStart(root, 1); // Set the start and end of the range at the same location
range.setEnd(root, 1); // This will create a collapsed scope that contains no nodes
const nodes = getAllNodesInRange(range);
expect(nodes).toEqual([root]);

View File

@@ -20,7 +20,7 @@ describe('getRangeDirection', () => {
it('should return "none" when the range start and end are the same', () => {
const range = document.createRange();
const div = document.createElement('div');
document.body.appendChild(div); // 确保节点在 DOM
document.body.appendChild(div); // Make sure the node is in the DOM
range.setStart(div, 0);
range.setEnd(div, 0);
@@ -30,11 +30,11 @@ describe('getRangeDirection', () => {
it('should return "forward" when the range is selected forwards', () => {
const div = document.createElement('div');
document.body.appendChild(div); // 确保节点在 DOM
document.body.appendChild(div); // Make sure the node is in the DOM
div.textContent = 'Test content';
const range = document.createRange();
range.setStart(div.firstChild as Node, 0);
range.setEnd(div.firstChild as Node, 4); // 选择了 "Test"
range.setEnd(div.firstChild as Node, 4); // Select "Test"
const direction = getRangeDirection(range);
expect(direction).toBe('forward');

View File

@@ -33,23 +33,23 @@ const TIMEOUT = 100;
interface GrabParams {
/**
* 选择目标容器的 Ref
* Select the Ref of the target container
*/
contentRef: MutableRefObject<HTMLDivElement | null>;
/**
* 浮动菜单的 Ref
* Floating Menu Ref
*/
floatMenuRef: MutableRefObject<HTMLDivElement | null> | undefined;
/**
* 选择事件的回调
* Select the callback for the event
*/
onSelectChange: (selectionData: SelectionData | null) => void;
/**
* 位置信息的回调
* Callback of location information
*/
onPositionChange: (position: GrabPosition | null) => void;
/**
* Resize/Scroll/Wheel 是节流的时间
* Resize/Scroll/Wheel is the time of throttling
*/
resizeThrottleTime?: number;
}
@@ -64,32 +64,32 @@ export const useGrab = ({
const timeoutRef = useRef<number>();
/**
* 选区对象存放用于hooks内部流转状态用
* Selection object storage (for internal flow state of hooks)
*/
const selection = useRef<Selection | null>(null);
/**
* 选区最终计算结果的数据
* Data on the final calculation result of the constituency
*/
const selectionData = useRef<SelectionData | null>(null);
/**
* 是否在 Scrolling
* In Scrolling
*/
const [isScrolling, setIsScrolling] = useState(false);
/**
* Scrolling 计时器
* Scrolling timer
*/
const scrollingTimer = useRef<number | null>(null);
/**
* 是否有 SelectionData (优化挂载逻辑用)
* Is there SelectionData (for optimizing mount logic)
*/
const hasSelectionData = useRef(false);
/**
* 清除内部数据 + 触发回调
* Clear internal data + trigger callback
*/
const clearSelection = () => {
onSelectChange(null);
@@ -104,15 +104,15 @@ export const useGrab = ({
};
/**
* 处理屏幕发生变化 Scroll + Resize + Wheel + SelectionChange(移动设备)
* Handle screen changes Scroll + Resize + Wheel + SelectionChange (mobile device)
*/
const handleScreenChange = () => {
// 获取选区
// Get Constituency
const innerSelection = window.getSelection();
const { direction = Direction.Unknown } = selectionData.current ?? {};
// 如果选区为空,则返回
// If the selection is empty, return
if (!innerSelection) {
onSelectChange(null);
return;
@@ -125,19 +125,19 @@ export const useGrab = ({
return;
}
// 默认使用获取选区最后一行的位置信息 既Forward的情况
// Default use to get the location information of the last line of the selection (both Forward cases)
const rangeRect = Array.from(rectData.rangeRects).at(
direction === Direction.Backward ? 0 : -1,
);
// 如果最后一行选区信息不正确则返回
// Returns if the last line of selection information is incorrect
if (!rangeRect) {
onPositionChange(null);
return;
}
let [x, y] = [0, 0];
// 判断如果选区是从前往后选择,则展示在最后一行的末尾,否则展示在开头
// If the selection is selected from front to back, it is displayed at the end of the last line, otherwise it is displayed at the beginning
if (direction === Direction.Backward) {
x = rangeRect.left;
y = rangeRect.top + rangeRect.height;
@@ -147,7 +147,7 @@ export const useGrab = ({
}
/**
* 加了一个避让屏幕的逻辑
* Added a logic to avoid the screen
*/
const position = {
x: x > screen.width - MAX_WIDTH ? x - MAX_WIDTH : x,
@@ -161,7 +161,7 @@ export const useGrab = ({
};
/**
* 智能处理屏幕发生变化 有一个计时器 + 滚动告知的逻辑
* Smart processing screen changes, there is a timer + scroll notification logic
*/
const handleSmartScreenChange = useEventCallback(() => {
if (scrollingTimer.current) {
@@ -176,7 +176,7 @@ export const useGrab = ({
});
/**
* 处理获取选区的逻辑
* Handling the logic of getting the selection
*/
const handleGetSelection = () => {
if (!contentRef.current) {
@@ -184,11 +184,11 @@ export const useGrab = ({
return;
}
// 获取选区
// eslint-disable-next-line @typescript-eslint/naming-convention -- 内部变量
// Get Constituency
// eslint-disable-next-line @typescript-eslint/naming-convention -- internal variable
const _selection = window.getSelection();
// 如果选区为空,则返回
// If the selection is empty, return
if (!_selection) {
onSelectChange(null);
return;
@@ -196,19 +196,19 @@ export const useGrab = ({
selection.current = _selection;
// 获取选区数据
// eslint-disable-next-line @typescript-eslint/naming-convention -- 内部变量
// Get constituency data
// eslint-disable-next-line @typescript-eslint/naming-convention -- internal variable
const _selectionData = getSelectionData({
selection: _selection,
});
// 选区如果为空则隐藏浮层Button
// Hide Floating Button if selection is empty
if (!_selectionData || !_selectionData.nodesAncestorIsMessageBox) {
onSelectChange(null);
return;
}
// 设置展示和位置信息
// Set display and location information
selectionData.current = _selectionData;
hasSelectionData.current = Boolean(_selectionData);
@@ -217,7 +217,7 @@ export const useGrab = ({
};
/**
* 鼠标抬起的动作
* The action of raising the mouse
*/
const handleMouseUp = useEventCallback(() => {
clearTimeout(timeoutRef.current);
@@ -225,7 +225,7 @@ export const useGrab = ({
});
/**
* 键盘按下的动作
* The action of keyboard pressing
*/
const handleKeyDown = useEventCallback((e: KeyboardEvent) => {
const forbiddenKeyboardSelect = () => {
@@ -244,10 +244,10 @@ export const useGrab = ({
});
/**
* 鼠标按下的动作
* Mouse press.
*/
const handleMouseDown = useEventCallback((e: MouseEvent) => {
// 检查是否有选区,且点击事件的目标不在选区内
// Check if there is a constituency, and the target of the click event is not in the constituency
if (!contentRef.current || !floatMenuRef || !floatMenuRef?.current) {
return;
@@ -275,7 +275,7 @@ export const useGrab = ({
return;
}
// 监听鼠标down事件
// Listen for mouse down events
window.addEventListener('mousedown', handleMouseDown);
return () => {
@@ -283,7 +283,7 @@ export const useGrab = ({
};
}, [hasSelectionData.current]);
// visible时,挂载监听事件,优化监听
// When visible, mount the listening event to optimize listening
useEffect(() => {
if (!hasSelectionData.current) {
return;
@@ -307,7 +307,7 @@ export const useGrab = ({
};
}, [hasSelectionData.current]);
// target上挂载监听
// mount monitor on target
useEffect(() => {
const target = contentRef.current;
@@ -315,7 +315,7 @@ export const useGrab = ({
return;
}
// 监听选择相关的鼠标抬起事件
// Monitor selection-related mouse lift events
target.addEventListener('pointerup', handleMouseUp);
if (isTouchDevice()) {
@@ -330,15 +330,15 @@ export const useGrab = ({
return {
/**
* 清除内置状态和选区
* Clear built-in state and selection
*/
clearSelection,
/**
* 是否在滚动中
* Is it scrolling?
*/
isScrolling,
/**
* 重新计算选区位置
* Recalculate the selection position
*/
computePosition: handleSmartScreenChange,
};

View File

@@ -17,8 +17,8 @@
import { getPictureNodeUrl } from './get-picture-node-url';
/**
* 获取 TagName Picture 的节点的有效节点
* @param childNodes NodeListOf<Node> 子节点列表
* Get a valid node for the node whose TagName is Picture
* @param childNodes NodeListOf < Node > sub-node list
* @returns Node | null
*/
export const findPictureValidChildNode = (childNodes: NodeListOf<Node>) =>

View File

@@ -15,8 +15,8 @@
*/
/**
* 获取图片 Node 中的 Url
* @param node Node 任意Node
* Get URLs in image Node
* @param node Node
* @returns string
*/
export const getPictureNodeUrl = (node: Node) => {

View File

@@ -50,28 +50,28 @@ export const getSelectionData = ({
const direction = getSelectionDirection(selection);
/**
* 通过获取特定标识 判断是否是可以划选的元素
* Determine whether it is an element that can be selected by getting a specific identifier
*/
const ancestorNodeWithAttribute = getAncestorAttributeNode(
range.commonAncestorContainer.parentNode,
CONTENT_ATTRIBUTE_NAME,
);
// 特定标识
// specific logo
const ancestorAttributeValue =
ancestorNodeWithAttribute?.attributes.getNamedItem(CONTENT_ATTRIBUTE_NAME)
?.value ?? null;
// 信息来源
// source of information
const messageSource = ancestorNodeWithAttribute?.attributes.getNamedItem(
MESSAGE_SOURCE_ATTRIBUTE_NAME,
)?.value;
if (!hasFix) {
// 尝试修复选区
// Try to fix the selection
const needFix = shouldRefineRange(range);
// 如果修复过,则重新获取执行并返回
// If repaired, retrieve the execution and return
if (needFix) {
const isFix = refineRange({ range });
@@ -92,7 +92,7 @@ export const getSelectionData = ({
return;
}
// 格式化的选区NodeList
// Formatted Selection NodeList
const normalizeSelectionNodeList = getNormalizeNodeList(
documentFragment.childNodes,
);
@@ -101,15 +101,15 @@ export const getSelectionData = ({
return;
}
// 人性化文本内容
// user-friendly text content
const humanizedContentText = getHumanizedContentText(
normalizeSelectionNodeList,
);
// 原始文本内容
// raw text content
const originContentText = getOriginContentText(normalizeSelectionNodeList);
// 如果修复选区成功了,那么他们的组件
// If the repair selection is successful, then their components
return {
humanizedContentText,

View File

@@ -15,8 +15,8 @@
*/
/**
* 判断节点包含关系
* 参考文档「https://developer.mozilla.org/zh-CN/docs/Web/API/Node/compareDocumentPosition
* Determine the node inclusion relationship
* Reference document "https://developer.mozilla.org/zh-CN/docs/Web/API/Node/compareDocumentPosition"
* @param nodeA
* @param nodeB
*
@@ -24,16 +24,16 @@
export const compareNodePosition = (nodeA: Node, nodeB: Node) => {
const comparison = nodeA.compareDocumentPosition(nodeB);
// 之所以条件跟返回是反的请参考官方文档包含关系展示的是B - A的关系
// The reason why the condition is inverse to return, please refer to the official documentation, including the relationship showing the B-A relationship
if (comparison & Node.DOCUMENT_POSITION_CONTAINED_BY) {
return 'contains'; // nodeA 包含 nodeB
return 'contains'; // NodeA contains nodeB
} else if (comparison & Node.DOCUMENT_POSITION_CONTAINS) {
return 'containedBy'; // nodeA nodeB 包含
return 'containedBy'; // NodeA is contained by nodeB
} else if (comparison & Node.DOCUMENT_POSITION_FOLLOWING) {
return 'before'; // nodeA nodeB 之前
return 'before'; // nodeA before nodeB
} else if (comparison & Node.DOCUMENT_POSITION_PRECEDING) {
return 'after'; // nodeA nodeB 之后
return 'after'; // NodeA after nodeB
}
return 'none'; // 节点是相同的或者没有可比较的关系
return 'none'; // Nodes are the same or have no comparable relationship
};

View File

@@ -15,31 +15,31 @@
*/
/**
* 通过 TagName 寻找祖先节点(包括自身)
* Find ancestor nodes (including itself) by TagName
* @param node Node | null
* @param tagName 目标 tagName
* @param tagName Target tagName
* @returns Node | null
*/
export const findAncestorNodeByTagName = (
node: Node | null,
tagName: string,
): Element | null => {
// 将标签名转换为大写,因为 DOM 中的标签名通常是大写的
// Convert the tag signature to uppercase, as tag signatures in the DOM are usually uppercase
const upperTagName = tagName.toUpperCase();
// 遍历节点的祖先节点直到找到匹配的标签名或到达根节点
// Traverse the node's ancestors until a matching tag is found or the root node is reached
while (node) {
// 确保当前节点是元素节点,并且标签名匹配
// Make sure that the current node is an element node and that the tag signatures match
if (
node.nodeType === Node.ELEMENT_NODE &&
(node as Element).tagName === upperTagName
) {
return node as Element;
}
// 移动到父节点
// Move to Parent Node
node = node.parentNode;
}
// 如果没有找到符合条件的祖先节点,返回 null
// If no eligible ancestor is found, return null.
return null;
};

View File

@@ -15,7 +15,7 @@
*/
/**
* 寻找某节点最后一个子节点
* Find the last sub-node of a node
* @param node Node
* @returns Node
*/

View File

@@ -17,8 +17,8 @@
import { getAncestorAttributeValue } from '../get-ancestor-attribute-value';
/**
* 寻找某节点的最后一个兄弟节点
* @param node 寻找节点
* Find the last sibling of a node
* @param node find node
* @returns
*/
export const findLastSiblingNode = ({

View File

@@ -17,16 +17,16 @@
export const findNearestAnchor = (
node: Node | null,
): HTMLAnchorElement | null => {
// 从当前节点开始向上遍历
// Traverse up from the current node
while (node) {
// 如果当前节点是元素节点并且是<a>标签
// If the current node is an element node and is a < a > tag
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'A') {
// 返回这个<a>标签
// Return this < a > tag
return node as HTMLAnchorElement;
}
// 向上移动到父节点
// Move up to the parent node
node = node.parentNode;
}
// 如果遍历到根节点还没有找到<a>标签,返回null
// If the < a > tag is not found at the root node, return null.
return null;
};

View File

@@ -30,10 +30,10 @@ export const findNotContainsPreviousSibling = (
return null;
}
// 获取两个节点之间的关系
// Get the relationship between two nodes
const relationship = compareNodePosition(sibling, node);
// 如果两个节点之间没有包含关系,则返回当前兄弟节点
// If there is no containing relationship between the two nodes, the current sibling is returned
if (!['containedBy', 'contains'].includes(relationship)) {
return sibling;
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
// 辅助函数,用于获取选区内的所有节点
// Helper function to obtain all nodes in the selection
export const getAllChildNodesInNode = (node: Node): Node[] => {
const nodes: Node[] = [];
const treeWalker = document.createTreeWalker(node, NodeFilter.SHOW_ALL, {
@@ -24,7 +24,7 @@ export const getAllChildNodesInNode = (node: Node): Node[] => {
: NodeFilter.FILTER_REJECT,
});
// eslint-disable-next-line prefer-destructuring -- 符合预期,因为要改数据并且允许为空
// eslint-disable-next-line prefer-destructuring -- as expected, because the data is to be changed and allowed to be empty
let currentNode: Node | null = treeWalker.currentNode;
while (currentNode) {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
// 辅助函数,用于获取选区内的所有节点
// Helper function to obtain all nodes in the selection
export const getAllNodesInRange = (range: Range): Node[] => {
const nodes: Node[] = [];
const treeWalker = document.createTreeWalker(
@@ -28,7 +28,7 @@ export const getAllNodesInRange = (range: Range): Node[] => {
},
);
// eslint-disable-next-line prefer-destructuring -- 符合预期,因为要改数据并且允许为空
// eslint-disable-next-line prefer-destructuring -- as expected, because the data is to be changed and allowed to be empty
let currentNode: Node | null = treeWalker.currentNode;
while (currentNode) {

View File

@@ -18,7 +18,7 @@ export const getRangeDirection = (range: Range) => {
const position = range.compareBoundaryPoints(Range.START_TO_END, range);
if (position === 0) {
return 'none'; // 选区起点和终点相同,即没有选择文本
return 'none'; // The starting and ending points of the selection are the same, i.e. no text is selected
}
return position === -1 ? 'backward' : 'forward';

View File

@@ -18,27 +18,27 @@ import { Direction } from '../../types/selection';
import { compareNodePosition } from './compare-node-position';
export const getSelectionDirection = (selection: Selection): Direction => {
// 确保有选区存在
// Make sure there are constituencies
if (!selection || selection.isCollapsed) {
return Direction.Unknown; // 没有选区或选区未展开
return Direction.Unknown; // No constituencies or constituencies not expanded
}
const { anchorNode } = selection;
const { focusNode } = selection;
// 确保 anchorNode focusNode 都不为 null
// Make sure that neither anchorNode nor focusNode is null
if (!anchorNode || !focusNode) {
return Direction.Unknown; // 无法确定方向
return Direction.Unknown; // Unable to determine direction
}
const { anchorOffset } = selection;
const { focusOffset } = selection;
// 比较 anchor focus 的位置
// Compare anchor and focus positions
if (anchorNode === focusNode) {
// 如果 anchor focus 在同一个节点,通过偏移量判断方向
// If the anchor and focus are on the same node, determine the direction by the offset
return anchorOffset <= focusOffset ? Direction.Forward : Direction.Backward;
} else {
// 如果不在同一个节点,使用 Document Position 来判断
// If not in the same node, use Document Position to determine
const position = compareNodePosition(anchorNode, focusNode);
if (position === 'before') {
@@ -48,6 +48,6 @@ export const getSelectionDirection = (selection: Selection): Direction => {
}
}
// 如果无法确定方向,返回 'unknown'
// If the direction cannot be determined, return'unknown'
return Direction.Unknown;
};

View File

@@ -15,11 +15,11 @@
*/
export const hasVisibleSelection = (range: Range): boolean => {
// 克隆Range内的所有节点
// Clone all nodes within the Range
const documentFragment = range.cloneContents();
const textNodes: Text[] = [];
// 递归函数来收集所有文本节点
// Recursive function to collect all text nodes
function collectTextNodes(node: Node) {
if (node.nodeType === Node.TEXT_NODE) {
textNodes.push(node as Text);
@@ -28,9 +28,9 @@ export const hasVisibleSelection = (range: Range): boolean => {
}
}
// 从文档片段的根节点开始收集文本节点
// Collect text nodes from the root node of the document fragment
collectTextNodes(documentFragment);
// 检查收集到的文本节点中是否有非空白的文本
// Check for non-blank text in the collected text nodes
return textNodes.some(textNode => /\S/.test(textNode.textContent || ''));
};

View File

@@ -20,7 +20,7 @@ import { isGrabLink } from './is-grab-link';
import { isGrabImage } from './is-grab-image';
/**
* 获取人性化文本内容
* Access to user-friendly text content
*/
export const getHumanizedContentText = (normalizeNodeList: GrabNode[]) => {
let content = '';

View File

@@ -24,7 +24,7 @@ import {
} from '../../types/node';
/**
* 获取格式化的 NodeList
* Get formatted NodeList
* @param childNodeList NodeListOf<Node>
*/
export const getNormalizeNodeList = (childNodeList: NodeListOf<Node>) => {
@@ -55,7 +55,7 @@ export const getNormalizeNodeList = (childNodeList: NodeListOf<Node>) => {
};
/**
* 生成 Grab Node
* Generating Grab Nodes
* @param node Node | null
*/
export const generateGrabNode = (node: Node | null) => {
@@ -65,19 +65,19 @@ export const generateGrabNode = (node: Node | null) => {
const isTable = ['TH', 'TD'].includes(node.nodeName.toUpperCase());
// 文本节点
// text node
if (node.nodeType === node.TEXT_NODE || isTable) {
return generateGrabText(node, isTable);
}
// 元素节点
// element node
if (node.nodeType === node.ELEMENT_NODE && node instanceof Element) {
return generateGrabElement(node);
}
};
/**
* 生成 Text 节点
* Generate Text Node
* @param node Node | null
*/
export const generateGrabText = (node: Node | null, isTable?: boolean) => {
@@ -97,7 +97,7 @@ export const generateGrabText = (node: Node | null, isTable?: boolean) => {
};
/**
* 生成 Element 节点
* Generating Element Node
* @param node Element | null
*/
export const generateGrabElement = (node: Element | null) => {

View File

@@ -15,8 +15,8 @@
*/
/**
* 获取格式化的 SelectionNodeList 数据
* 获取喂给大模型的文本内容
* Get formatted SelectionNodeList data
* Get the text content fed to the large model
*/
import { type GrabNode } from '../../types/node';
import { isGrabTextNode } from './is-grab-text-node';

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { parseMarkdownHelper } from '@coze-common/chat-area-utils';
import { parseMarkdown } from '@coze-arch/bot-md-box-adapter/lazy';
@@ -22,7 +22,7 @@ import { GrabElementType, type GrabNode } from '../types/node';
const { isImage, isLink, isParent, isText } = parseMarkdownHelper;
/**
* 获取GrabNode节点
* Get a GrabNode node
* @param markdown string
* @returns GrabNode[]
*/
@@ -33,14 +33,14 @@ export const parseMarkdownToGrabNode = (markdown: string) => {
};
/**
* 从Markdown的AST解析成GrabNode节点
* Parsing from Markdown's AST to a GrabNode node
* @param ast markdown ast by parseMarkdown (md-box)
* @returns GrabNode[]
*/
export const getGrabNodeFromAst = (ast: unknown): GrabNode[] => {
const normalizedNodeList: GrabNode[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 符合预期
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- as expected
const traverseAst = (_ast: any) => {
if (isText(_ast)) {
normalizedNodeList.push({

View File

@@ -17,12 +17,12 @@
import { findPictureValidChildNode } from '../find-picture-valid-child-node';
/**
* 处理特殊 Node 节点数据
* Processing special Node data
* @param node Node
* @returns node | undefined
*/
export const processSpecialNode = (node: Node) => {
// 针对picture类型的特殊优化
// Special optimization for picture types
if (node.nodeName.toUpperCase() === 'PICTURE') {
const pictureNode = findPictureValidChildNode(node.childNodes);
@@ -31,12 +31,12 @@ export const processSpecialNode = (node: Node) => {
}
}
// 针对链接的特殊优化
// Special optimization for links
if (node.nodeName.toUpperCase() === 'A') {
return node;
}
// 针对表格的特殊优化
// Special optimization for tables
if (['TH', 'TD'].includes(node.nodeName.toUpperCase())) {
return node;
}

View File

@@ -28,30 +28,30 @@ export const fixEndEmpty = ({
endNode: Node;
endOffset: number;
}): boolean => {
// 检查是否需要修复:结束节点和开始节点不同且结束偏移量为0
// Check if it needs to be fixed: the end node and the start node are different and the end offset is 0
if (startNode === endNode || endOffset !== 0) {
return false; // 不需要修复
return false; // No repair required.
}
// 初始化当前节点为结束节点的前一个兄弟节点
// Initializes the current node to the previous sibling of the end node
let currentNode: Node | null = findNotContainsPreviousSibling(endNode);
// 寻找一个有效的非空前一个兄弟节点
// Find a valid non-unprecedented sibling node
while (currentNode) {
if (currentNode.nodeType === Node.TEXT_NODE) {
// 如果是文本节点,检查是否非空
// If it is a text node, check if it is not empty
const textContent = currentNode.textContent?.trim();
if (textContent && currentNode.textContent?.length) {
// 非空,修复选区结束位置
// Not empty, fix the end of the selection
range.setEnd(currentNode, currentNode.textContent.length);
return true;
}
} else if (currentNode.nodeType === Node.ELEMENT_NODE) {
// 如果是元素节点,检查是否有可见内容
// If it is an element node, check if there is any visible content
const textContent = currentNode.textContent?.trim();
if (textContent) {
// 有可见内容,尝试更精确地设置结束位置
// 如果元素内部有文本节点,尝试定位到最后一个文本节点
// With visible content, try to set the end position more precisely
// If there is a text node inside the element, try to navigate to the last text node
let lastTextNode: Node | null = null;
const allChildNodes = getAllChildNodesInNode(currentNode);
@@ -68,10 +68,10 @@ export const fixEndEmpty = ({
}
}
}
// 如果当前节点为空或不满足条件,继续向前/向上遍历
// If the current node is empty or does not meet the conditions, continue to traverse forward/upward
currentNode = findNotContainsPreviousSibling(currentNode);
}
// 如果遍历完所有前一个兄弟节点都没有找到合适的节点返回false
// If no suitable node is found after traversing all previous siblings, return false.
return false;
};

View File

@@ -29,7 +29,7 @@ export const fixEndNode = ({
let endNode: Node | null = range.endContainer;
let { endOffset } = range;
// 确保结束节点符合条件
// Make sure the end node meets the conditions
while (
endNode &&
!(
@@ -39,14 +39,14 @@ export const fixEndNode = ({
) {
if (endNode.nextSibling) {
endNode = endNode.nextSibling;
endOffset = 0; // 从下一个兄弟节点的开始位置开始
endOffset = 0; // Start from the starting position of the next sibling node
} else if (endNode.parentNode && endNode.parentNode !== document) {
endNode = endNode.parentNode;
endOffset = endNode
? findLastChildNode(endNode).textContent?.length ?? 0
: 0; // 从父节点的最后位置开始
: 0; // Start from the last position of the parent node
} else {
// 没有符合条件的结束节点
// No eligible end nodes
endNode = null;
break;
}

View File

@@ -21,13 +21,13 @@ export const fixLink = (range: Range, startNode: Node, endNode: Node) => {
const endAnchor = findNearestAnchor(endNode);
let isFix = false;
// 如果起始节点在链接内,将选区的起点设置为链接的开始
// If the starting node is within the link, set the starting point of the selection to the start of the link
if (startAnchor) {
range.setStartBefore(startAnchor);
isFix = true;
}
// 如果结束节点在链接内,将选区的终点设置为链接的结束
// If the end node is within the link, set the end of the selection to the end of the link
if (endAnchor) {
range.setEndAfter(endAnchor);
isFix = true;
@@ -37,8 +37,8 @@ export const fixLink = (range: Range, startNode: Node, endNode: Node) => {
};
/**
* 1. 链接[A ...文字... 链]接B
* 2. 链接A ...文[字... 链]接B
* 3. ...文[字 链]接B
* 4. 链[接]B
* 1. Link [A... text... link] to B
* 2. Link A... text [word... link] to B
* 3.... Text [word, chain] to B
* 4. Chain [link] B
*/

View File

@@ -28,7 +28,7 @@ export const fixStartNode = ({
let startNode: Node | null = range.startContainer;
let { startOffset } = range;
// 确保起始节点符合条件
// Make sure the starting node meets the requirements
while (
startNode &&
!(
@@ -38,12 +38,12 @@ export const fixStartNode = ({
) {
if (startNode.previousSibling) {
startNode = startNode.previousSibling;
startOffset = 0; // 从前一个兄弟节点的开始位置开始
startOffset = 0; // From the starting position of the previous sibling node
} else if (startNode.parentNode && startNode.parentNode !== document) {
startNode = startNode.parentNode;
startOffset = 0; // 从父节点的开始位置开始
startOffset = 0; // Start at the beginning of the parent node
} else {
// 没有符合条件的起始节点
// No eligible starting nodes
startNode = null;
break;
}

View File

@@ -15,8 +15,8 @@
*/
/**
* 修复选区的实际函数
* @param range Range 选区
* Fix the actual function of the selection
* @param range Range
*/
import { findLastSiblingNode } from '../helper/find-last-sibling-node';
import { findLastChildNode } from '../helper/find-last-child-node';
@@ -31,7 +31,7 @@ import { fixEndEmpty } from './fix-end-empty';
// eslint-disable-next-line complexity
export const refineRange = ({ range }: { range: Range }): boolean => {
// 初期算法只用StartNode当作选区的开始节点
// The initial algorithm only uses StartNode as the starting node of the selection
const targetAttributeValue = getAncestorAttributeValue(
range.startContainer,
CONTENT_ATTRIBUTE_NAME,
@@ -53,43 +53,43 @@ export const refineRange = ({ range }: { range: Range }): boolean => {
targetAttributeValue,
});
// 如果找到了起始节点,但是没找到结束节点,那么继续尝试使用开始节点的最后一个兄弟元素修复选区
// If the start node is found, but the end node is not found, then continue to try to fix the selection using the last sibling of the start node
if (startNode && !endNode) {
const { parentNode } = startNode;
let lastSibling: Node | null = null;
// 尝试找到最近的<li>或<a>标签
// Try to find the nearest < li > or < a > tag
const liParentNode = findAncestorNodeByTagName(parentNode, 'LI');
const aParentNode = liParentNode
? findAncestorNodeByTagName(liParentNode, 'A')
: findAncestorNodeByTagName(parentNode, 'A');
// 找到最近的<div>标签
// Find the nearest < div > tag
const divParentNode = findAncestorNodeByTagName(parentNode, 'DIV');
// 根据找到的节点类型决定如何查找最后一个兄弟节点
// Determine how to find the last sibling based on the type of node found
if (aParentNode) {
// 如果找到了<a>,使用它的父节点
// If < a > is found, use its parent node
lastSibling = findLastSiblingNode({
node: aParentNode,
scopeAncestorAttributeName: CONTENT_ATTRIBUTE_NAME,
targetAttributeValue,
});
} else if (liParentNode) {
// 如果找到了<li>,使用它的父节点
// If a < li > is found, use its parent node
lastSibling = findLastSiblingNode({
node: liParentNode,
scopeAncestorAttributeName: CONTENT_ATTRIBUTE_NAME,
targetAttributeValue,
});
} else if (divParentNode) {
// 如果找到了<div>,使用他的父节点(例如代码块的情况)
// If a < div > is found, use its parent node (e.g. in the case of a code block).
lastSibling = findLastSiblingNode({
node: divParentNode,
scopeAncestorAttributeName: CONTENT_ATTRIBUTE_NAME,
targetAttributeValue,
});
} else {
// 否则,使用起始节点
// Otherwise, use the starting node
lastSibling = findLastSiblingNode({
node: startNode,
scopeAncestorAttributeName: CONTENT_ATTRIBUTE_NAME,
@@ -97,7 +97,7 @@ export const refineRange = ({ range }: { range: Range }): boolean => {
});
}
// 如果起始节点和找到的兄弟节点一样,那么就用其实节点的父元素去找他最后的一个节点
// If the starting node is the same as the found sibling, then use the parent element of the actual node to find its last node
if (startNode === lastSibling) {
lastSibling = findLastSiblingNode({
node: parentNode,
@@ -115,7 +115,7 @@ export const refineRange = ({ range }: { range: Range }): boolean => {
}
}
// 如果起始节点和结束节点都找到了,修正选区
// If both the start and end nodes are found, correct the selection
if (startNode && endNode) {
const relation = compareNodePosition(startNode, endNode);

View File

@@ -20,21 +20,21 @@ import { findAncestorNodeByTagName } from './helper/find-ancestor-node-by-tag-na
import { getAncestorAttributeValue } from './get-ancestor-attribute-value';
export const shouldRefineRange = (range: Range): boolean => {
// 获取选区的所有节点
// Get all nodes of the selection
const nodes = getAllNodesInRange(range);
let validNodeLength = 0;
let hasNodeInLink = false;
// 遍历所有节点,检查它们的祖先是否都有特定类名属性
// Traverse all nodes to check if their ancestors have a specific class name attribute
for (const node of nodes) {
const attributeValue = getAncestorAttributeValue(
node,
CONTENT_ATTRIBUTE_NAME,
);
// 如果不存在才需要覆盖 hasNodeInLink确保找到有节点在链接中
// If it doesn't exist, you need to overwrite hasNodeInLink and make sure to find a node in the link.
if (!hasNodeInLink) {
hasNodeInLink = Boolean(findAncestorNodeByTagName(node, 'A'));
}