重构:引用输入区的选区兜底收敛成一个 helper

抽出 $selectionOrRootEnd():选区缺失或指向已被重建掉的节点时统一回落到草稿末尾
insertReferences / insertText / $insertContentAtSelection 三处重复的兜底判断改为调用它
行为不变:插件插入与粘贴插入仍走同一套「插到草稿末尾」的策略,只是不再各写一份
This commit is contained in:
2026-09-22 19:53:42 +08:00
parent ec1f207021
commit 9e50947d02
@@ -300,6 +300,22 @@ function applyContentToRoot(
});
}
/**
* 取当前可用的选区:选区缺失、或指向已被重建掉的节点时(跨会话恢复草稿后就是这种),
* 统一回落到草稿末尾,避免把内容插到一个已经不存在的位置。取不到时返回 `null`。
*/
function $selectionOrRootEnd() {
let selection = $getSelection();
if (
!$isRangeSelection(selection) ||
!selection.anchor.getNode().isAttached()
) {
$getRoot().selectEnd();
selection = $getSelection();
}
return $isRangeSelection(selection) ? selection : null;
}
/**
* 粘贴插入:在光标处就地插入 content 对应的节点,正文其余部分逐字不动。
*
@@ -311,31 +327,19 @@ function $insertContentAtSelection(
content: readonly DirectCodexUserContentPart[],
providers: readonly ReferenceProvider[],
) {
// 与 insertReferences 同一处置:选区可能指向已被重建掉的节点,统一回落到草稿末尾
// 每插一段都重新取一次选区:插入会移动光标,缓存下来的那个 RangeSelection 会过期。
const currentSelection = () => {
let selection = $getSelection();
if (
!$isRangeSelection(selection) ||
!selection.anchor.getNode().isAttached()
) {
$getRoot().selectEnd();
selection = $getSelection();
}
return $isRangeSelection(selection) ? selection : null;
};
if (!currentSelection()) return;
// 每插一段都重新取一次选区:插入会移动光标,上一轮拿到的那个 RangeSelection 会过期
if (!$selectionOrRootEnd()) return;
content.forEach((part) => {
if (part.type === 'input_text') {
part.text.split('\n').forEach((line, index) => {
if (index > 0) currentSelection()?.insertParagraph();
if (line) currentSelection()?.insertText(line);
if (index > 0) $selectionOrRootEnd()?.insertParagraph();
if (line) $selectionOrRootEnd()?.insertText(line);
});
return;
}
const reference = referenceFromPart(providers, part);
if (reference) {
currentSelection()?.insertNodes([
$selectionOrRootEnd()?.insertNodes([
$createResourceReferenceNode(reference),
]);
}
@@ -413,24 +417,14 @@ function ResourceReferenceEditor({
(nextReferences: ChatReference[]) => {
if (nextReferences.length === 0) return;
editor.update(() => {
let selection = $getSelection();
// 跨会话恢复草稿后选区可能仍指向已被重建掉的节点,这里统一回落到草稿末尾,
// 避免把引用插到一个已经不存在的位置。
if (
!$isRangeSelection(selection) ||
!selection.anchor.getNode().isAttached()
) {
$getRoot().selectEnd();
selection = $getSelection();
}
if ($isRangeSelection(selection)) {
selection.insertNodes(
nextReferences.flatMap((reference) => [
$createResourceReferenceNode(reference),
$createTextNode(' '),
]),
);
}
const selection = $selectionOrRootEnd();
if (!selection) return;
selection.insertNodes(
nextReferences.flatMap((reference) => [
$createResourceReferenceNode(reference),
$createTextNode(' '),
]),
);
});
editor.focus();
},
@@ -442,19 +436,11 @@ function ResourceReferenceEditor({
const insert = text.replace(/\s+$/u, '');
if (!insert.trim()) return;
editor.update(() => {
let selection = $getSelection();
if (
!$isRangeSelection(selection) ||
!selection.anchor.getNode().isAttached()
) {
$getRoot().selectEnd();
selection = $getSelection();
}
if ($isRangeSelection(selection)) {
const rootText = $getRoot().getTextContent();
if (rootText && !/\s$/u.test(rootText)) selection.insertText(' ');
selection.insertText(insert);
}
const selection = $selectionOrRootEnd();
if (!selection) return;
const rootText = $getRoot().getTextContent();
if (rootText && !/\s$/u.test(rootText)) selection.insertText(' ');
selection.insertText(insert);
});
editor.focus();
},