Skip to content

Commit 7ca084c

Browse files
committed
fix(高亮定位): 修复动画高亮定位偏差问题并改进文本替换逻辑
修复 `anim-highlight-old` 和 `anim-highlight-new` 高亮定位存在偏差的问题 问题根因:新文本高亮时直接使用原始索引,但 `correctedText` 与 `originalText` 长度可能不同 修复方案:在 `ReaderPanel.tsx` 中动态查找新文本位置,添加降级处理 改进文本替换逻辑: 1. 在 `appStore.ts` 中重构替换逻辑,优先使用动态查找而非固定索引 2. 添加模糊匹配和容错处理 3. 修复空段落处理问题 4. 增加调试日志 更新版本号至 0.8.7
1 parent ee8e05c commit 7ca084c

11 files changed

Lines changed: 252 additions & 71 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v0.8.7 (2026-05-10)
4+
5+
### 🐛 Bug 修复
6+
7+
**动画高亮定位偏差问题**
8+
- 修复 `anim-highlight-old``anim-highlight-new` 高亮定位存在偏差的问题
9+
- 问题根因:新文本高亮时直接使用原始索引,但 `correctedText``originalText` 长度可能不同
10+
- 修复方案:在 `ReaderPanel.tsx` 中,新文本高亮阶段使用 `para.indexOf(newText)` 动态查找新文本在实际段落中的位置,而非使用固定索引
11+
- 旧文本高亮(`highlight-old` / `replacing` 阶段)继续使用原始索引,因为此时段落内容尚未改变
12+
- 添加降级处理:如果新文本在段落中找不到,则使用原始索引作为备选方案
13+
14+
---
15+
316
## v0.8.6 (2026-05-09)
417

518
### ✨ 新功能

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "proofreader",
33
"private": true,
4-
"version": "0.8.6",
4+
"version": "0.8.7",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "proofreader"
3-
version = "0.8.6"
3+
version = "0.8.7"
44
edition = "2021"
55

66
[lib]

src-tauri/target/.rustc_info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"rustc_fingerprint":8181815494667461818,"outputs":{"2595164081506252816":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"neon\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"11449549311697287552":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: aarch64-apple-darwin\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"3285132367535543654":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"popcnt\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"sse4.1\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"10144619854796028087":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"eabi\"\ntarget_arch=\"arm\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
1+
{"rustc_fingerprint":8181815494667461818,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: aarch64-apple-darwin\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"2595164081506252816":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"neon\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"3285132367535543654":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"popcnt\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"sse4.1\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"10144619854796028087":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"eabi\"\ntarget_arch=\"arm\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"11449549311697287552":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/Users/hegeken/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"android\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-schema-generator/schemas/config.schema.json",
33
"productName": "Proof Reader",
4-
"version": "0.8.6",
4+
"version": "0.8.7",
55
"identifier": "cn.helilab.proofreader",
66
"build": {
77
"frontendDist": "../dist",

src/App.css

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,7 @@ select {
21592159
flex: 1;
21602160
display: flex;
21612161
flex-direction: column;
2162-
overflow: hidden;
2162+
overflow: visible;
21632163
}
21642164

21652165
.proofread-panel.empty {
@@ -2176,6 +2176,10 @@ select {
21762176
flex-shrink: 0;
21772177
flex-wrap: wrap;
21782178
gap: 8px;
2179+
position: sticky;
2180+
top: 0;
2181+
background: var(--bg-surface);
2182+
z-index: 10;
21792183
}
21802184

21812185
.toolbar-left,
@@ -2379,6 +2383,26 @@ select {
23792383
gap: 6px;
23802384
}
23812385

2386+
.btn-apply-all {
2387+
display: flex;
2388+
align-items: center;
2389+
gap: 4px;
2390+
padding: 6px 10px;
2391+
background: var(--bg-surface);
2392+
border: 1px solid var(--border);
2393+
border-radius: var(--r-sm);
2394+
font-size: 12px;
2395+
color: var(--text-muted);
2396+
cursor: pointer;
2397+
transition: all var(--duration) var(--ease);
2398+
}
2399+
2400+
.btn-apply-all:hover {
2401+
background: var(--bg-hover);
2402+
border-color: var(--border-strong);
2403+
color: var(--text);
2404+
}
2405+
23822406
.error-item {
23832407
padding: 10px 12px;
23842408
background: var(--bg-surface);

src/components/ProofreadPanel.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,8 @@ export function ProofreadPanel() {
266266
paraIndex,
267267
err.originalText,
268268
err.correctedText,
269-
err.startIndex,
270-
err.endIndex,
271269
);
270+
console.log(`[ProofreadPanel] 采纳修改: chapterId=${chapterId}, paraIndex=${paraIndex}, original="${err.originalText}", corrected="${err.correctedText}", success=${replaced}`);
272271
toggleErrorApplied(chapterId, paraIndex, err.id); // 使用原始段落索引
273272

274273
if (!replaced) {
@@ -493,7 +492,7 @@ export function ProofreadPanel() {
493492
<div
494493
key={paraResult.paragraphIndex}
495494
ref={(el) => {
496-
paragraphRefs.current[i] = el;
495+
paragraphRefs.current[paraResult.paragraphIndex] = el;
497496
}}
498497
className={`proofread-paragraph ${highlightedParagraph === paraResult.paragraphIndex ? "highlighted" : ""
499498
}`}

src/components/ReaderPanel.tsx

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -563,20 +563,62 @@ export function ReaderPanel({
563563
const isEditing = editingIndex === filteredIndex;
564564

565565
// 如果是动画目标,提取需要高亮的文本片段
566-
const highlightInfo =
567-
isAnimTarget && applyAnimation!.startIndex !== undefined
568-
? {
569-
before: para.slice(0, applyAnimation!.startIndex),
570-
highlight: para.slice(
571-
applyAnimation!.startIndex,
572-
applyAnimation!.endIndex,
573-
),
574-
after: para.slice(applyAnimation!.endIndex),
575-
isOld:
576-
applyAnimation!.phase === "highlight-old" ||
577-
applyAnimation!.phase === "replacing",
578-
}
579-
: null;
566+
const getHighlightInfo = () => {
567+
if (!isAnimTarget || applyAnimation!.startIndex === undefined) {
568+
return null;
569+
}
570+
571+
const isOldPhase =
572+
applyAnimation!.phase === "highlight-old" ||
573+
applyAnimation!.phase === "replacing";
574+
575+
// 旧文本高亮:使用原始索引
576+
if (isOldPhase) {
577+
return {
578+
before: para.slice(0, applyAnimation!.startIndex),
579+
highlight: para.slice(
580+
applyAnimation!.startIndex,
581+
applyAnimation!.endIndex,
582+
),
583+
after: para.slice(applyAnimation!.endIndex),
584+
isOld: true,
585+
};
586+
}
587+
588+
// 新文本高亮:由于新文本长度可能与原文本不同,需要重新查找位置
589+
const newText = applyAnimation!.correctedText;
590+
if (!newText) {
591+
console.warn("[ReaderPanel] correctedText is undefined");
592+
return null;
593+
}
594+
595+
const newStartIdx = para.indexOf(newText);
596+
597+
if (newStartIdx >= 0) {
598+
// 在当前段落中找到了新文本,使用实际位置
599+
return {
600+
before: para.slice(0, newStartIdx),
601+
highlight: newText,
602+
after: para.slice(newStartIdx + newText.length),
603+
isOld: false,
604+
};
605+
} else {
606+
// 降级:使用原始索引(可能有偏差,但至少能显示高亮)
607+
console.warn(
608+
`[ReaderPanel] 新文本 "${newText}" 未在段落中找到,使用原始索引`,
609+
);
610+
return {
611+
before: para.slice(0, applyAnimation!.startIndex),
612+
highlight: para.slice(
613+
applyAnimation!.startIndex,
614+
applyAnimation!.startIndex + newText.length,
615+
),
616+
after: para.slice(applyAnimation!.startIndex + newText.length),
617+
isOld: false,
618+
};
619+
}
620+
};
621+
const highlightInfo = getHighlightInfo();
580622

581623
// 检测空段落(连续换行),直接跳过不渲染
582624
const isEmptyParagraph = para.trim() === "";

0 commit comments

Comments
 (0)