Skip to content

Commit 6c8705d

Browse files
committed
feat: 输入匹配时,增加防抖机制
1 parent 5d52cf6 commit 6c8705d

File tree

2 files changed

+147
-126
lines changed

2 files changed

+147
-126
lines changed

app/src/components/welcome-page.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ export default function WelcomePage() {
5353
</span>
5454
</div>
5555
<div className="hidden text-xs opacity-50 sm:block sm:text-lg">{t("slogan")}</div>
56-
57-
{/* 临时加一个 */}
58-
<div className="hidden text-xs opacity-25 sm:block sm:text-xs">区块化双向引用即将上线……</div>
5956
</div>
6057
{/* 底部区域 */}
6158
<div className="flex sm:gap-16">

app/src/core/service/controlService/controller/concrete/utilsControl.tsx

Lines changed: 147 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { PathString } from "@/utils/pathString";
2424
import { DateChecker } from "@/utils/dateChecker";
2525
import { TextNodeSmartTools } from "@/core/service/dataManageService/textNodeSmartTools";
2626
import { ReferenceManager } from "@/core/stage/stageManager/concreteMethods/StageReferenceManager";
27+
import _ from "lodash";
2728

2829
/**
2930
* 这里是专门存放代码相同的地方
@@ -394,33 +395,117 @@ export class ControllerUtils {
394395
) {
395396
// 处理#开头的逻辑节点补全
396397
if (text.startsWith("#")) {
397-
// 提取搜索文本,去掉所有#
398-
const searchText = text.replaceAll("#", "").toLowerCase();
399-
400-
const logicNodeEntries = Object.entries(LogicNodeNameToRenderNameMap).map(([key, renderName]) => ({
401-
key,
402-
name: key.replaceAll("#", "").toLowerCase(),
403-
renderName,
404-
}));
405-
406-
const fuse = new Fuse(logicNodeEntries, {
407-
keys: ["name"],
408-
threshold: 0.3, // (0 = exact, 1 = very fuzzy)
398+
this.handleAutoCompleteLogic(text, node, ele, setWindowId);
399+
// 处理[[格式的补全
400+
} else if (text.startsWith("[[")) {
401+
this.handleAutoCompleteReferenceDebounced(text, node, ele, setWindowId);
402+
}
403+
}
404+
private handleAutoCompleteReferenceDebounced = _.debounce(
405+
(text: string, node: TextNode, ele: HTMLTextAreaElement, setWindowId: (id: string) => void) => {
406+
this.handleAutoCompleteReference(text, node, ele, setWindowId);
407+
console.log("ref匹配执行了");
408+
},
409+
500,
410+
);
411+
412+
private handleAutoCompleteLogic(
413+
text: string,
414+
node: TextNode,
415+
ele: HTMLTextAreaElement,
416+
setWindowId: (id: string) => void,
417+
) {
418+
// 提取搜索文本,去掉所有#
419+
const searchText = text.replaceAll("#", "").toLowerCase();
420+
421+
const logicNodeEntries = Object.entries(LogicNodeNameToRenderNameMap).map(([key, renderName]) => ({
422+
key,
423+
name: key.replaceAll("#", "").toLowerCase(),
424+
renderName,
425+
}));
426+
427+
const fuse = new Fuse(logicNodeEntries, {
428+
keys: ["name"],
429+
threshold: 0.3, // (0 = exact, 1 = very fuzzy)
430+
});
431+
432+
const searchResults = fuse.search(searchText);
433+
const matchingNodes = searchResults.map((result) => [result.item.key, result.item.renderName]);
434+
435+
// 打开自动补全窗口
436+
if (this.currentAutoCompleteWindowId) {
437+
SubWindow.close(this.currentAutoCompleteWindowId);
438+
}
439+
if (matchingNodes.length > 0) {
440+
const windowId = AutoCompleteWindow.open(
441+
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
442+
Object.fromEntries(matchingNodes),
443+
(value) => {
444+
ele.value = value;
445+
},
446+
).id;
447+
this.currentAutoCompleteWindowId = windowId;
448+
setWindowId(windowId);
449+
} else {
450+
const windowId = AutoCompleteWindow.open(
451+
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
452+
{
453+
tip:
454+
searchText === "" ? "暂无匹配的逻辑节点名称,请输入全大写字母" : `暂无匹配的逻辑节点名称【${searchText}】`,
455+
},
456+
(value) => {
457+
ele.value = value;
458+
},
459+
).id;
460+
this.currentAutoCompleteWindowId = windowId;
461+
setWindowId(windowId);
462+
}
463+
}
464+
465+
private async handleAutoCompleteReference(
466+
text: string,
467+
node: TextNode,
468+
ele: HTMLTextAreaElement,
469+
setWindowId: (id: string) => void,
470+
) {
471+
// 提取搜索文本,去掉开头的[[
472+
const searchText = text.slice(2).toLowerCase().replace("]]", "");
473+
// 检查是否包含#
474+
const hasHash = searchText.includes("#");
475+
476+
if (!hasHash) {
477+
// 获取最近文件列表
478+
const recentFiles = await RecentFileManager.getRecentFiles();
479+
480+
// 处理最近文件列表,提取文件名
481+
const fileEntries = recentFiles.map((file) => {
482+
// 提取文件名(不含扩展名)
483+
const fileName = PathString.getFileNameFromPath(file.uri.path);
484+
return { name: fileName, time: file.time }; // 使用对象格式以便Fuse.js搜索
485+
});
486+
487+
const fuse = new Fuse(fileEntries, {
488+
keys: ["name"], // 搜索name属性
489+
threshold: 0.3,
409490
});
410491

411492
const searchResults = fuse.search(searchText);
412-
const matchingNodes = searchResults.map((result) => [result.item.key, result.item.renderName]);
493+
const matchingFiles = searchResults.map((result) => [
494+
result.item.name,
495+
DateChecker.formatRelativeTime(result.item.time),
496+
]); // 转换为相对时间格式
413497

414498
// 打开自动补全窗口
415499
if (this.currentAutoCompleteWindowId) {
416500
SubWindow.close(this.currentAutoCompleteWindowId);
417501
}
418-
if (matchingNodes.length > 0) {
502+
if (matchingFiles.length > 0) {
419503
const windowId = AutoCompleteWindow.open(
420504
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
421-
Object.fromEntries(matchingNodes),
505+
Object.fromEntries(matchingFiles),
422506
(value) => {
423-
ele.value = value;
507+
// 用户选择后,需要保留[[前缀并添加选择的文件名
508+
ele.value = `[[${value}`;
424509
},
425510
).id;
426511
this.currentAutoCompleteWindowId = windowId;
@@ -429,126 +514,65 @@ export class ControllerUtils {
429514
const windowId = AutoCompleteWindow.open(
430515
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
431516
{
432-
tip:
433-
searchText === ""
434-
? "暂无匹配的逻辑节点名称,请输入全大写字母"
435-
: `暂无匹配的逻辑节点名称【${searchText}】`,
517+
tip: searchText === "" ? "暂无最近文件" : `暂无匹配的最近文件【${searchText}】`,
436518
},
437519
(value) => {
438-
ele.value = value;
520+
ele.value = `[[${value}`;
439521
},
440522
).id;
441523
this.currentAutoCompleteWindowId = windowId;
442524
setWindowId(windowId);
443525
}
444-
// 处理[[格式的补全
445-
} else if (text.startsWith("[[")) {
446-
// 提取搜索文本,去掉开头的[[
447-
const searchText = text.slice(2).toLowerCase().replace("]]", "");
448-
// 检查是否包含#
449-
const hasHash = searchText.includes("#");
450-
451-
if (!hasHash) {
452-
// 获取最近文件列表
453-
const recentFiles = await RecentFileManager.getRecentFiles();
454-
455-
// 处理最近文件列表,提取文件名
456-
const fileEntries = recentFiles.map((file) => {
457-
// 提取文件名(不含扩展名)
458-
const fileName = PathString.getFileNameFromPath(file.uri.path);
459-
return { name: fileName, time: file.time }; // 使用对象格式以便Fuse.js搜索
460-
});
526+
} else {
527+
// 包含#,拆分文件名和section名称
528+
const [fileName, sectionName] = searchText.split("#", 2);
461529

462-
const fuse = new Fuse(fileEntries, {
463-
keys: ["name"], // 搜索name属性
464-
threshold: 0.3,
465-
});
530+
// 获取该文件中的所有section
531+
const sections = await CrossFileContentQuery.getSectionsByFileName(fileName);
466532

467-
const searchResults = fuse.search(searchText);
468-
const matchingFiles = searchResults.map((result) => [
469-
result.item.name,
470-
DateChecker.formatRelativeTime(result.item.time),
471-
]); // 转换为相对时间格式
533+
// 将section名称转换为对象数组,以便Fuse.js搜索
534+
const sectionObjects = sections.map((section) => ({ name: section }));
535+
let searchResults;
472536

473-
// 打开自动补全窗口
474-
if (this.currentAutoCompleteWindowId) {
475-
SubWindow.close(this.currentAutoCompleteWindowId);
476-
}
477-
if (matchingFiles.length > 0) {
478-
const windowId = AutoCompleteWindow.open(
479-
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
480-
Object.fromEntries(matchingFiles),
481-
(value) => {
482-
// 用户选择后,需要保留[[前缀并添加选择的文件名
483-
ele.value = `[[${value}`;
484-
},
485-
).id;
486-
this.currentAutoCompleteWindowId = windowId;
487-
setWindowId(windowId);
488-
} else {
489-
const windowId = AutoCompleteWindow.open(
490-
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
491-
{
492-
tip: searchText === "" ? "暂无最近文件" : `暂无匹配的最近文件【${searchText}】`,
493-
},
494-
(value) => {
495-
ele.value = `[[${value}`;
496-
},
497-
).id;
498-
this.currentAutoCompleteWindowId = windowId;
499-
setWindowId(windowId);
500-
}
537+
// 当section名称为空时,显示所有section(最多20个)
538+
if (!sectionName?.trim()) {
539+
// 取前20个section
540+
searchResults = sectionObjects.slice(0, 20).map((item) => ({ item }));
501541
} else {
502-
// 包含#,拆分文件名和section名称
503-
const [fileName, sectionName] = searchText.split("#", 2);
504-
505-
// 获取该文件中的所有section
506-
const sections = await CrossFileContentQuery.getSectionsByFileName(fileName);
507-
508-
// 将section名称转换为对象数组,以便Fuse.js搜索
509-
const sectionObjects = sections.map((section) => ({ name: section }));
510-
let searchResults;
511-
512-
// 当section名称为空时,显示所有section(最多20个)
513-
if (!sectionName?.trim()) {
514-
// 取前20个section
515-
searchResults = sectionObjects.slice(0, 20).map((item) => ({ item }));
516-
} else {
517-
// 创建Fuse搜索器,对section名称进行模糊匹配
518-
const fuse = new Fuse(sectionObjects, { keys: ["name"], threshold: 0.3 });
519-
searchResults = fuse.search(sectionName);
520-
}
542+
// 创建Fuse搜索器,对section名称进行模糊匹配
543+
const fuse = new Fuse(sectionObjects, { keys: ["name"], threshold: 0.3 });
544+
searchResults = fuse.search(sectionName);
545+
}
521546

522-
const matchingSections = searchResults.map((result) => [result.item.name, ""]);
547+
const matchingSections = searchResults.map((result) => [result.item.name, ""]);
523548

524-
// 打开自动补全窗口
525-
if (this.currentAutoCompleteWindowId) {
526-
SubWindow.close(this.currentAutoCompleteWindowId);
527-
}
528-
if (matchingSections.length > 0) {
529-
const windowId = AutoCompleteWindow.open(
530-
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
531-
Object.fromEntries(matchingSections),
532-
(value) => {
533-
// 用户选择后,需要保留[[前缀、文件名和#,并添加选择的section名称
534-
ele.value = `[[${fileName}#${value}`;
535-
},
536-
).id;
537-
this.currentAutoCompleteWindowId = windowId;
538-
setWindowId(windowId);
539-
} else {
540-
const windowId = AutoCompleteWindow.open(
541-
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
542-
{
543-
tip: sectionName === "" ? `这个文件中没有section,无法创建引用` : `暂无匹配的section【${sectionName}】`,
544-
},
545-
(value) => {
546-
ele.value = `[[${fileName}#${value}`;
547-
},
548-
).id;
549-
this.currentAutoCompleteWindowId = windowId;
550-
setWindowId(windowId);
551-
}
549+
// 打开自动补全窗口
550+
if (this.currentAutoCompleteWindowId) {
551+
SubWindow.close(this.currentAutoCompleteWindowId);
552+
}
553+
if (matchingSections.length > 0) {
554+
const windowId = AutoCompleteWindow.open(
555+
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
556+
Object.fromEntries(matchingSections),
557+
(value) => {
558+
// 用户选择后,需要保留[[前缀、文件名和#,并添加选择的section名称
559+
ele.value = `[[${fileName}#${value}`;
560+
},
561+
).id;
562+
this.currentAutoCompleteWindowId = windowId;
563+
setWindowId(windowId);
564+
} else {
565+
const windowId = AutoCompleteWindow.open(
566+
this.project.renderer.transformWorld2View(node.rectangle).leftBottom,
567+
{
568+
tip: sectionName === "" ? `这个文件中没有section,无法创建引用` : `暂无匹配的section【${sectionName}】`,
569+
},
570+
(value) => {
571+
ele.value = `[[${fileName}#${value}`;
572+
},
573+
).id;
574+
this.currentAutoCompleteWindowId = windowId;
575+
setWindowId(windowId);
552576
}
553577
}
554578
}

0 commit comments

Comments
 (0)