mirror of
https://github.com/hansjone/mind-map.git
synced 2026-10-09 03:20:49 +08:00
Initial commit: DSH mind-map canvas monorepo
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
2ecd300ada
92 changed files with 20486 additions and 0 deletions
6
apps/web/scripts/_check_cjk.py
Normal file
6
apps/web/scripts/_check_cjk.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
import re
|
||||
t = Path(r"d:\project\chatgpt\mind-map\apps\web\src\TopologyCanvas.tsx").read_text(encoding="utf-8")
|
||||
print("quoted ??", re.findall(r'"\?{2,}"', t))
|
||||
print("选择" in t, "单点" in t, "撤销" in t)
|
||||
15
apps/web/scripts/_fix_leftover.py
Normal file
15
apps/web/scripts/_fix_leftover.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
p = Path(r"d:\project\chatgpt\mind-map\apps\web\src\TopologyCanvas.tsx")
|
||||
t = p.read_text(encoding="utf-8")
|
||||
for m in re.finditer(r'.{0,60}"\?{2,}".{0,40}', t):
|
||||
print(repr(m.group(0)))
|
||||
# fix leftover ??? for create_node
|
||||
if ': { type: "create_node", text: "???" }' in t:
|
||||
t = t.replace(': { type: "create_node", text: "???" }', ': { type: "create_node", text: "新节点" }')
|
||||
p.write_text(t, encoding="utf-8", newline="\n")
|
||||
print("fixed leftover")
|
||||
print("quoted left", re.findall(r'"\?{2,}"', t))
|
||||
print("cjk count", len(re.findall(r"[\u4e00-\u9fff]+", t)))
|
||||
13
apps/web/scripts/_inspect_cjk.py
Normal file
13
apps/web/scripts/_inspect_cjk.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
t = Path(r"d:\project\chatgpt\mind-map\apps\web\src\TopologyCanvas.tsx").read_text(encoding="utf-8")
|
||||
i = t.find('dragScope === "node"')
|
||||
print("=== drag node ===")
|
||||
print(repr(t[i - 80 : i + 320]))
|
||||
print("=== hint ===")
|
||||
j = t.find("canvas-hint")
|
||||
print(repr(t[j : j + 400]))
|
||||
print("=== remaining ?? ===")
|
||||
import re
|
||||
for m in re.finditer(r".{0,40}\?{2,}.{0,40}", t):
|
||||
print(repr(m.group(0)))
|
||||
24
apps/web/scripts/check-cjk-dist.py
Normal file
24
apps/web/scripts/check-cjk-dist.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
src = Path(r"d:\project\chatgpt\mind-map\apps\web\src\TopologyCanvas.tsx")
|
||||
dist = Path(r"d:\project\chatgpt\mind-map\apps\web\dist\assets")
|
||||
b = src.read_bytes()
|
||||
print("src has 选择 bytes:", "选择".encode("utf-8") in b)
|
||||
print("src has literal ?? button:", bool(re.search(rb">\s*\?\?\s*<", b)))
|
||||
# show hex around select label
|
||||
idx = b.find(b'["select"')
|
||||
print("src select snippet:", b[idx : idx + 30])
|
||||
|
||||
for f in dist.glob("index-*.js"):
|
||||
jb = f.read_bytes()
|
||||
print(f.name, "size", len(jb))
|
||||
print(" has 选择:", "选择".encode("utf-8") in jb)
|
||||
print(" has [\"select\",\"??\":", b'["select","??"' in jb or b'["select", "??"' in jb)
|
||||
m = re.search(rb'\["select",\s*"[^"]*"', jb)
|
||||
if m:
|
||||
print(" select match:", m.group(0))
|
||||
m2 = re.search(rb'\["pan",\s*"[^"]*"', jb)
|
||||
if m2:
|
||||
print(" pan match:", m2.group(0))
|
||||
207
apps/web/scripts/fix-topology-cjk.py
Normal file
207
apps/web/scripts/fix-topology-cjk.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Restore CJK strings in TopologyCanvas.tsx corrupted to '?' / '??'."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
text = p.read_text(encoding="utf-8")
|
||||
misses: list[str] = []
|
||||
|
||||
|
||||
def sub(pattern: str, repl: str, *, flags: int = 0, count: int = 0, label: str = "") -> None:
|
||||
global text
|
||||
new, n = re.subn(pattern, repl, text, count=count, flags=flags)
|
||||
tag = label or pattern[:56]
|
||||
if n == 0:
|
||||
misses.append(tag)
|
||||
print("MISS:", tag)
|
||||
else:
|
||||
print(f"OK x{n}:", tag)
|
||||
text = new
|
||||
|
||||
|
||||
# comments
|
||||
sub(r"// iframe \?+ Tab \?+", "// iframe 内无法全屏时,通知父页 Tab 全屏", label="fullscreen comment")
|
||||
sub(r"// Free place \?pin", "// Free place → pin", label="free place comment")
|
||||
|
||||
# create defaults
|
||||
sub(
|
||||
r'text: "\?+", parentId: parent\b',
|
||||
'text: "新节点", parentId: parent',
|
||||
label="tab child",
|
||||
)
|
||||
sub(
|
||||
r': \{ type: "create_node", text: "\?+" \},',
|
||||
': { type: "create_node", text: "新节点" },',
|
||||
label="tab orphan / enter orphan",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: -1',
|
||||
'text: "左侧子主题",\n parentId: selectedIds[0],\n sidePref: -1',
|
||||
label="arrow left",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: 1',
|
||||
'text: "右侧子主题",\n parentId: selectedIds[0],\n sidePref: 1',
|
||||
label="arrow right",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: parentOfSel',
|
||||
'text: "兄弟节点",\n parentId: parentOfSel',
|
||||
label="enter sibling",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n \.\.\.\(selectedIds\[0\]',
|
||||
'text: "新节点",\n ...(selectedIds[0]',
|
||||
label="dblclick create",
|
||||
)
|
||||
sub(r'label: "\?+"', 'label: "关联"', label="relation label")
|
||||
|
||||
# toolbar: back
|
||||
sub(
|
||||
r'(className="canvas-tool-btn"\n title=")\?+("\n onClick=\{requestBackToRoster\}\n >\n )\?+(\n </button>)',
|
||||
r'\1返回画布列表\2返回\3',
|
||||
label="back to roster",
|
||||
)
|
||||
|
||||
# tool modes
|
||||
sub(r'\["select", "\?+", "V"\]', '["select", "选择", "V"]', label="select")
|
||||
sub(r'\["pan", "\?+", "H"\]', '["pan", "平移", "H"]', label="pan")
|
||||
sub(r'\["link", "\?+", "C"\]', '["link", "连线", "C"]', label="link")
|
||||
|
||||
# drag scope
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{dragScope === "node" \? " is-active" : ""\}`\}\n title=")\?+("\n aria-pressed=\{dragScope === "node"\}\n onClick=\{\(\) => setDragScope\("node"\)\}\n >\n )\?+(\n </button>)',
|
||||
r'\1单点拖拽:只移动当前节点 (N);按住 Shift 也可临时单点\2单点\3',
|
||||
label="drag node",
|
||||
)
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{dragScope === "branch" \? " is-active" : ""\}`\}\n title=")\?+("\n aria-pressed=\{dragScope === "branch"\}\n onClick=\{\(\) => setDragScope\("branch"\)\}\n >\n )\?+(\n </button>)',
|
||||
r'\1整枝拖拽:节点与子孙一起移动 (B)\2整枝\3',
|
||||
label="drag branch",
|
||||
)
|
||||
|
||||
# bubble / card / unpin
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? "" : " is-active"\}`\}\n title=")\?+("\n aria-pressed=\{!cardView\})',
|
||||
r'\1气泡视图:精简标题\2',
|
||||
label="bubble title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "bubble" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\})',
|
||||
r'\1气泡\2',
|
||||
label="bubble label",
|
||||
)
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\}\n title=")\?+("\n aria-pressed=\{cardView\})',
|
||||
r'\1卡片视图:展开属性面板\2',
|
||||
label="card title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "card" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+(")',
|
||||
r'\1卡片\2取消钉住:让选中节点重新参与自动布局\3',
|
||||
label="card label + unpin title",
|
||||
)
|
||||
sub(
|
||||
r'(pinned: false \},\n \]\);\n \}\}\n >\n )\?+(\n </button>\n </div>\n <div className="canvas-toolbar__sep" />\n <div className="canvas-toolbar__group">\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+("\n disabled=\{!lastChangeSetId\})',
|
||||
r'\1解钉\2撤销\3',
|
||||
label="unpin + undo titles/labels",
|
||||
)
|
||||
sub(
|
||||
r'(disabled=\{!lastChangeSetId\}\n onClick=\{\(\) => void undo\(\)\}\n >\n )\?+(\n </button>)',
|
||||
r'\1撤销\2',
|
||||
label="undo label",
|
||||
)
|
||||
|
||||
# aria-labels for toolbars
|
||||
sub(
|
||||
r'(className="canvas-toolbar"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r'\1画布工具\2',
|
||||
label="toolbar aria",
|
||||
)
|
||||
sub(
|
||||
r'(className="canvas-controls"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r'\1视图控制\2',
|
||||
label="controls aria",
|
||||
)
|
||||
|
||||
# controls titles
|
||||
sub(
|
||||
r'title=\{fullscreen \? "\?+" : "\?+"\}\n aria-label=\{fullscreen \? "\?+" : "\?+"\}',
|
||||
'title={fullscreen ? "退出全屏" : "全屏"}\n aria-label={fullscreen ? "退出全屏" : "全屏"}',
|
||||
label="fullscreen",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(\+\)"\n aria-label="\?+"',
|
||||
'title="放大 (+)"\n aria-label="放大"',
|
||||
label="zoom in",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(-\)"\n aria-label="\?+"',
|
||||
'title="缩小 (-)"\n aria-label="缩小"',
|
||||
label="zoom out",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(F\)"\n aria-label="\?+"',
|
||||
'title="适应画布 (F)"\n aria-label="适应画布"',
|
||||
label="fit",
|
||||
)
|
||||
sub(
|
||||
r'title=\{locked \? "\?+ \(L\)" : "\?+ \(L\)"\}\n aria-label=\{locked \? "\?+" : "\?+"\}',
|
||||
'title={locked ? "解锁画布 (L)" : "锁定画布 (L)"}\n aria-label={locked ? "解锁" : "锁定"}',
|
||||
label="lock",
|
||||
)
|
||||
|
||||
# handles
|
||||
sub(
|
||||
r'const text = side < 0 \? "\?+" : "\?+";',
|
||||
'const text = side < 0 ? "左侧子主题" : "右侧子主题";',
|
||||
label="handle text",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--left"\n style=\{\{ left: cx - hw - 14, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r'\1添加左侧子主题\2',
|
||||
label="handle left title",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--right"\n style=\{\{ left: cx \+ hw - 2, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r'\1添加右侧子主题\2',
|
||||
label="handle right title",
|
||||
)
|
||||
|
||||
# hint footer
|
||||
sub(
|
||||
r'''\{locked
|
||||
\? "\?+"
|
||||
: dragScope === "node"
|
||||
\? "\?+"
|
||||
: "\?+"\}''',
|
||||
'''{locked
|
||||
? "已锁定 · 仅浏览/缩放/平移"
|
||||
: dragScope === "node"
|
||||
? "单点拖 · 只移动当前节点 · Shift 临时单点 · Esc 取消"
|
||||
: "整枝拖 · 空白钉住 · 落到节点换父 · N 单点 · Esc 取消"}''',
|
||||
label="canvas hint",
|
||||
)
|
||||
|
||||
# context menu labels if any remain
|
||||
sub(
|
||||
r'label: "\?+"',
|
||||
'label: "关联"',
|
||||
count=1,
|
||||
label="label leftover",
|
||||
)
|
||||
|
||||
remaining = len(re.findall(r"\?{2,}", text))
|
||||
cjk = len(re.findall(r"[\u4e00-\u9fff]+", text))
|
||||
print("remaining ?? sequences:", remaining)
|
||||
print("cjk tokens:", cjk)
|
||||
print("misses:", len(misses))
|
||||
|
||||
if misses:
|
||||
raise SystemExit(1)
|
||||
|
||||
p.write_text(text, encoding="utf-8", newline="\n")
|
||||
print("wrote", p)
|
||||
115
apps/web/scripts/fix-topology-cjk2.py
Normal file
115
apps/web/scripts/fix-topology-cjk2.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Restore remaining CJK in TopologyCanvas.tsx (context-anchored)."""
|
||||
from pathlib import Path
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
text = p.read_text(encoding="utf-8")
|
||||
|
||||
replacements = [
|
||||
("// iframe ??????????? Tab ??", "// iframe 内无法全屏时,通知父页 Tab 全屏"),
|
||||
('{ type: "create_node", text: "???", parentId: parent }', '{ type: "create_node", text: "新节点", parentId: parent }'),
|
||||
(': { type: "create_node", text: "???" },', ': { type: "create_node", text: "新节点" },'),
|
||||
(
|
||||
' text: "????",\n parentId: selectedIds[0],\n sidePref: -1,',
|
||||
' text: "左侧子主题",\n parentId: selectedIds[0],\n sidePref: -1,',
|
||||
),
|
||||
(
|
||||
' text: "????",\n parentId: selectedIds[0],\n sidePref: 1,',
|
||||
' text: "右侧子主题",\n parentId: selectedIds[0],\n sidePref: 1,',
|
||||
),
|
||||
(
|
||||
' text: "????",\n parentId: parentOfSel,',
|
||||
' text: "兄弟节点",\n parentId: parentOfSel,',
|
||||
),
|
||||
('label: "??",', 'label: "关联",'),
|
||||
("// Free place ?pin every dragged id", "// Free place → pin every dragged id"),
|
||||
(
|
||||
' text: "???",\n ...(selectedIds[0] ? { parentId: selectedIds[0] } : {}),',
|
||||
' text: "新节点",\n ...(selectedIds[0] ? { parentId: selectedIds[0] } : {}),',
|
||||
),
|
||||
('aria-label="????"\n onPointerDown={(e) => e.stopPropagation()}\n >\n {embed ? (', 'aria-label="画布工具"\n onPointerDown={(e) => e.stopPropagation()}\n >\n {embed ? ('),
|
||||
('title="??????"\n onClick={requestBackToRoster}\n >\n ??\n </button>', 'title="返回画布列表"\n onClick={requestBackToRoster}\n >\n 返回\n </button>'),
|
||||
('["select", "??", "V"]', '["select", "选择", "V"]'),
|
||||
('["pan", "??", "H"]', '["pan", "平移", "H"]'),
|
||||
('["link", "??", "C"]', '["link", "连线", "C"]'),
|
||||
(
|
||||
'title="???????????? (N)??? Shift ??????"\n aria-pressed={dragScope === "node"}\n onClick={() => setDragScope("node")}\n >\n ??\n </button>',
|
||||
'title="单点拖拽:只移动当前节点 (N);按住 Shift 也可临时单点"\n aria-pressed={dragScope === "node"}\n onClick={() => setDragScope("node")}\n >\n 单点\n </button>',
|
||||
),
|
||||
(
|
||||
'title="?????????????? (B)"\n aria-pressed={dragScope === "branch"}\n onClick={() => setDragScope("branch")}\n >\n ??\n </button>',
|
||||
'title="整枝拖拽:节点与子孙一起移动 (B)"\n aria-pressed={dragScope === "branch"}\n onClick={() => setDragScope("branch")}\n >\n 整枝\n </button>',
|
||||
),
|
||||
(
|
||||
'title="?????????????"\n aria-pressed={!cardView}\n disabled={locked}\n onClick={() => {\n if (!cardView) return;\n void setPrefs({ nodeViewMode: "bubble" });\n }}\n >\n ??\n </button>',
|
||||
'title="气泡视图:精简标题"\n aria-pressed={!cardView}\n disabled={locked}\n onClick={() => {\n if (!cardView) return;\n void setPrefs({ nodeViewMode: "bubble" });\n }}\n >\n 气泡\n </button>',
|
||||
),
|
||||
(
|
||||
'title="??????????????"\n aria-pressed={cardView}\n disabled={locked}\n onClick={() => {\n if (cardView) return;\n void setPrefs({ nodeViewMode: "card" });\n }}\n >\n ??\n </button>',
|
||||
'title="卡片视图:展开属性面板"\n aria-pressed={cardView}\n disabled={locked}\n onClick={() => {\n if (cardView) return;\n void setPrefs({ nodeViewMode: "card" });\n }}\n >\n 卡片\n </button>',
|
||||
),
|
||||
(
|
||||
'title="??????????????????"\n disabled={\n locked ||\n !selectedIds[0] ||\n !nodes.find((n) => n.id === selectedIds[0])?.pinned\n }\n onClick={() => {\n if (!selectedIds[0]) return;\n void applyOps([\n { type: "set_pinned", nodeId: selectedIds[0], pinned: false },\n ]);\n }}\n >\n ??\n </button>',
|
||||
'title="取消钉住:让选中节点重新参与自动布局"\n disabled={\n locked ||\n !selectedIds[0] ||\n !nodes.find((n) => n.id === selectedIds[0])?.pinned\n }\n onClick={() => {\n if (!selectedIds[0]) return;\n void applyOps([\n { type: "set_pinned", nodeId: selectedIds[0], pinned: false },\n ]);\n }}\n >\n 解钉\n </button>',
|
||||
),
|
||||
(
|
||||
'title="??"\n disabled={!lastChangeSetId}\n onClick={() => void undo()}\n >\n ??\n </button>',
|
||||
'title="撤销"\n disabled={!lastChangeSetId}\n onClick={() => void undo()}\n >\n 撤销\n </button>',
|
||||
),
|
||||
(
|
||||
'aria-label="????"\n onPointerDown={(e) => e.stopPropagation()}\n >\n <button\n type="button"\n className="canvas-controls__btn"\n title={fullscreen ? "????" : "??"}\n aria-label={fullscreen ? "????" : "??"}',
|
||||
'aria-label="视图控制"\n onPointerDown={(e) => e.stopPropagation()}\n >\n <button\n type="button"\n className="canvas-controls__btn"\n title={fullscreen ? "退出全屏" : "全屏"}\n aria-label={fullscreen ? "退出全屏" : "全屏"}',
|
||||
),
|
||||
(
|
||||
'title="?? (+)"\n aria-label="??"',
|
||||
'title="放大 (+)"\n aria-label="放大"',
|
||||
),
|
||||
(
|
||||
'title="?? (-)"\n aria-label="??"',
|
||||
'title="缩小 (-)"\n aria-label="缩小"',
|
||||
),
|
||||
(
|
||||
'title="???? (F)"\n aria-label="????"',
|
||||
'title="适应画布 (F)"\n aria-label="适应画布"',
|
||||
),
|
||||
(
|
||||
'title={locked ? "???? (L)" : "???? (L)"}\n aria-label={locked ? "??" : "??"}',
|
||||
'title={locked ? "解锁画布 (L)" : "锁定画布 (L)"}\n aria-label={locked ? "解锁" : "锁定"}',
|
||||
),
|
||||
(
|
||||
'const text = side < 0 ? "????" : "????";',
|
||||
'const text = side < 0 ? "左侧子主题" : "右侧子主题";',
|
||||
),
|
||||
(
|
||||
'className="node-handle node-handle--left"\n style={{ left: cx - hw - 14, top: cy - 10 }}\n title="????????"',
|
||||
'className="node-handle node-handle--left"\n style={{ left: cx - hw - 14, top: cy - 10 }}\n title="添加左侧子主题"',
|
||||
),
|
||||
(
|
||||
'className="node-handle node-handle--right"\n style={{ left: cx + hw - 2, top: cy - 10 }}\n title="????????"',
|
||||
'className="node-handle node-handle--right"\n style={{ left: cx + hw - 2, top: cy - 10 }}\n title="添加右侧子主题"',
|
||||
),
|
||||
(
|
||||
'{locked\n ? "??? ? ???/??/????"\n : dragScope === "node"\n ? "??? ? ?????? ? Shift ???? ? Esc ??"\n : "??? ? ??? ? ???? ? ?????? ? N ??? ? Esc ??"}',
|
||||
'{locked\n ? "已锁定 · 仅浏览/缩放/平移"\n : dragScope === "node"\n ? "单点拖 · 只移动当前节点 · Shift 临时单点 · Esc 取消"\n : "整枝拖 · 空白钉住 · 落到节点换父 · N 单点 · Esc 取消"}',
|
||||
),
|
||||
]
|
||||
|
||||
miss = []
|
||||
for old, new in replacements:
|
||||
if old not in text:
|
||||
miss.append(old[:70])
|
||||
print("MISS:", old[:70].replace("\n", "\\n"))
|
||||
else:
|
||||
text = text.replace(old, new, 1)
|
||||
print("OK:", old[:50].replace("\n", "\\n"))
|
||||
|
||||
import re
|
||||
# leftover UI ?? (not ?? nullish coalescing)
|
||||
# count Chinese question-mark runs that are quoted UI strings
|
||||
ui_q = re.findall(r'"\?{2,}"', text)
|
||||
print("quoted ?? left:", ui_q)
|
||||
print("misses:", len(miss))
|
||||
if miss:
|
||||
raise SystemExit(1)
|
||||
p.write_text(text, encoding="utf-8", newline="\n")
|
||||
print("wrote", p)
|
||||
221
apps/web/scripts/fix-topology-cjk3.py
Normal file
221
apps/web/scripts/fix-topology-cjk3.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Restore all CJK in TopologyCanvas.tsx after encoding corruption."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
text = p.read_text(encoding="utf-8")
|
||||
misses: list[str] = []
|
||||
|
||||
|
||||
def sub(pattern: str, repl: str, *, flags: int = 0, count: int = 0, label: str = "") -> None:
|
||||
global text
|
||||
new, n = re.subn(pattern, repl, text, count=count, flags=flags)
|
||||
tag = label or pattern[:56]
|
||||
if n == 0:
|
||||
misses.append(tag)
|
||||
print("MISS:", tag)
|
||||
else:
|
||||
print(f"OK x{n}:", tag)
|
||||
text = new
|
||||
|
||||
|
||||
# comments
|
||||
sub(r"// iframe \?+ Tab \?+", "// iframe 内无法全屏时,通知父页 Tab 全屏", label="fullscreen comment")
|
||||
sub(r"// Free place .+?pin every dragged id", "// Free place → pin every dragged id", label="free place comment")
|
||||
|
||||
# placeholders used in draw / create
|
||||
sub(r'(wrapTextLines\(ctx, n\.text \|\| )"\?+"', r'\1"未命名"', label="unnamed wrap")
|
||||
sub(r'(n\.text \|\| )"\?+"', r'\1"未命名"', label="unnamed text")
|
||||
|
||||
# create defaults
|
||||
sub(
|
||||
r'text: "\?+", parentId: parent\b',
|
||||
'text: "新节点", parentId: parent',
|
||||
label="tab child",
|
||||
)
|
||||
sub(
|
||||
r': \{ type: "create_node", text: "\?+" \},',
|
||||
': { type: "create_node", text: "新节点" },',
|
||||
label="tab orphan / enter orphan",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: -1',
|
||||
'text: "左侧子主题",\n parentId: selectedIds[0],\n sidePref: -1',
|
||||
label="arrow left",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: 1',
|
||||
'text: "右侧子主题",\n parentId: selectedIds[0],\n sidePref: 1',
|
||||
label="arrow right",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: parentOfSel',
|
||||
'text: "兄弟节点",\n parentId: parentOfSel',
|
||||
label="enter sibling",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n \.\.\.\(selectedIds\[0\]',
|
||||
'text: "新节点",\n ...(selectedIds[0]',
|
||||
label="dblclick create",
|
||||
)
|
||||
sub(r'label: "\?+"', 'label: "关联"', label="relation label")
|
||||
|
||||
# toolbar: back
|
||||
sub(
|
||||
r'(className="canvas-tool-btn"\n title=")\?+("\n onClick=\{requestBackToRoster\}\n >\n )\?+(\n </button>)',
|
||||
r"\1返回画布列表\2返回\3",
|
||||
label="back to roster",
|
||||
)
|
||||
|
||||
# tool modes
|
||||
sub(r'\["select", "\?+", "V"\]', '["select", "选择", "V"]', label="select")
|
||||
sub(r'\["pan", "\?+", "H"\]', '["pan", "平移", "H"]', label="pan")
|
||||
sub(r'\["link", "\?+", "C"\]', '["link", "连线", "C"]', label="link")
|
||||
|
||||
# drag scope — title may contain latin (N)/(B)/Shift so match loosely
|
||||
sub(
|
||||
r'(aria-pressed=\{dragScope === "node"\}\n onClick=\{\(\) => setDragScope\("node"\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1单点\2",
|
||||
label="drag node label",
|
||||
)
|
||||
sub(
|
||||
r'(dragScope === "node" \? " is-active" : ""\}`\}\n title=")\?+(")',
|
||||
r"\1单点拖拽:只移动当前节点 (N);按住 Shift 也可临时单点\2",
|
||||
label="drag node title",
|
||||
)
|
||||
sub(
|
||||
r'(aria-pressed=\{dragScope === "branch"\}\n onClick=\{\(\) => setDragScope\("branch"\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1整枝\2",
|
||||
label="drag branch label",
|
||||
)
|
||||
sub(
|
||||
r'(dragScope === "branch" \? " is-active" : ""\}`\}\n title=")\?+(")',
|
||||
r"\1整枝拖拽:节点与子孙一起移动 (B)\2",
|
||||
label="drag branch title",
|
||||
)
|
||||
|
||||
# bubble / card / unpin
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? "" : " is-active"\}`\}\n title=")\?+("\n aria-pressed=\{!cardView\})',
|
||||
r"\1气泡视图:精简标题\2",
|
||||
label="bubble title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "bubble" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\})',
|
||||
r"\1气泡\2",
|
||||
label="bubble label",
|
||||
)
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\}\n title=")\?+("\n aria-pressed=\{cardView\})',
|
||||
r"\1卡片视图:展开属性面板\2",
|
||||
label="card title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "card" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+(")',
|
||||
r"\1卡片\2取消钉住:让选中节点重新参与自动布局\3",
|
||||
label="card label + unpin title",
|
||||
)
|
||||
sub(
|
||||
r'(pinned: false \},\n \]\);\n \}\}\n >\n )\?+(\n </button>\n </div>\n <div className="canvas-toolbar__sep" />\n <div className="canvas-toolbar__group">\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+("\n disabled=\{!lastChangeSetId\})',
|
||||
r"\1解钉\2撤销\3",
|
||||
label="unpin + undo titles/labels",
|
||||
)
|
||||
sub(
|
||||
r'(disabled=\{!lastChangeSetId\}\n onClick=\{\(\) => void undo\(\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1撤销\2",
|
||||
label="undo label",
|
||||
)
|
||||
|
||||
# aria-labels for toolbars
|
||||
sub(
|
||||
r'(className="canvas-toolbar"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r"\1画布工具\2",
|
||||
label="toolbar aria",
|
||||
)
|
||||
sub(
|
||||
r'(className="canvas-controls"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r"\1视图控制\2",
|
||||
label="controls aria",
|
||||
)
|
||||
|
||||
# controls titles
|
||||
sub(
|
||||
r'title=\{fullscreen \? "\?+" : "\?+"\}\n aria-label=\{fullscreen \? "\?+" : "\?+"\}',
|
||||
'title={fullscreen ? "退出全屏" : "全屏"}\n aria-label={fullscreen ? "退出全屏" : "全屏"}',
|
||||
label="fullscreen",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(\+\)"\n aria-label="\?+"',
|
||||
'title="放大 (+)"\n aria-label="放大"',
|
||||
label="zoom in",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(-\)"\n aria-label="\?+"',
|
||||
'title="缩小 (-)"\n aria-label="缩小"',
|
||||
label="zoom out",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(F\)"\n aria-label="\?+"',
|
||||
'title="适应画布 (F)"\n aria-label="适应画布"',
|
||||
label="fit",
|
||||
)
|
||||
sub(
|
||||
r'title=\{locked \? "\?+ \(L\)" : "\?+ \(L\)"\}\n aria-label=\{locked \? "\?+" : "\?+"\}',
|
||||
'title={locked ? "解锁画布 (L)" : "锁定画布 (L)"}\n aria-label={locked ? "解锁" : "锁定"}',
|
||||
label="lock",
|
||||
)
|
||||
|
||||
# handles
|
||||
sub(
|
||||
r'const text = side < 0 \? "\?+" : "\?+";',
|
||||
'const text = side < 0 ? "左侧子主题" : "右侧子主题";',
|
||||
label="handle text",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--left"\n style=\{\{ left: cx - hw - 14, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r"\1添加左侧子主题\2",
|
||||
label="handle left title",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--right"\n style=\{\{ left: cx \+ hw - 2, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r"\1添加右侧子主题\2",
|
||||
label="handle right title",
|
||||
)
|
||||
|
||||
# hint footer — allow · and mixed ?
|
||||
sub(
|
||||
r'''\{locked
|
||||
\? "[^"]*"
|
||||
: dragScope === "node"
|
||||
\? "[^"]*"
|
||||
: "[^"]*"\}''',
|
||||
'''{locked
|
||||
? "已锁定 · 仅浏览/缩放/平移"
|
||||
: dragScope === "node"
|
||||
? "单点拖 · 只移动当前节点 · Shift 临时单点 · Esc 取消"
|
||||
: "整枝拖 · 空白钉住 · 落到节点换父 · N 单点 · Esc 取消"}''',
|
||||
label="canvas hint",
|
||||
)
|
||||
|
||||
# draw-sheet placeholders already Chinese in other files
|
||||
|
||||
remaining_ui = re.findall(r'"\?{2,}"', text)
|
||||
# also button bodies that are only ??
|
||||
remaining_btn = re.findall(r">\n\s+\?{2,}\n\s+</button>", text)
|
||||
cjk = len(re.findall(r"[\u4e00-\u9fff]+", text))
|
||||
print("quoted ?? left:", remaining_ui)
|
||||
print("button ?? left:", len(remaining_btn))
|
||||
print("cjk tokens:", cjk)
|
||||
print("misses:", len(misses))
|
||||
for m in misses:
|
||||
print(" -", m)
|
||||
|
||||
# Always write — partial fix is better than none; report leftovers
|
||||
p.write_text(text, encoding="utf-8", newline="\n")
|
||||
print("wrote", p)
|
||||
|
||||
if remaining_ui or remaining_btn or misses:
|
||||
raise SystemExit(2)
|
||||
22
apps/web/scripts/fix-topology-cjk4.py
Normal file
22
apps/web/scripts/fix-topology-cjk4.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
t = p.read_text(encoding="utf-8")
|
||||
pairs = [
|
||||
(
|
||||
'title="???????????? (N)??? Shift ??????"',
|
||||
'title="单点拖拽:只移动当前节点 (N);按住 Shift 也可临时单点"',
|
||||
),
|
||||
(
|
||||
'title="?????????????? (B)"',
|
||||
'title="整枝拖拽:节点与子孙一起移动 (B)"',
|
||||
),
|
||||
]
|
||||
for old, new in pairs:
|
||||
if old not in t:
|
||||
raise SystemExit(f"missing: {old}")
|
||||
t = t.replace(old, new, 1)
|
||||
print("fixed:", new[:40])
|
||||
p.write_text(t, encoding="utf-8", newline="\n")
|
||||
print("done")
|
||||
214
apps/web/scripts/fix-topology-cjk5.py
Normal file
214
apps/web/scripts/fix-topology-cjk5.py
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Restore CJK in TopologyCanvas.tsx; tolerate latin1/broken middot encoding."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
raw = p.read_bytes()
|
||||
try:
|
||||
text = raw.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
text = raw.decode("latin1")
|
||||
# Normalize stray CP1252/latin1 middot into a matchable char
|
||||
text = text.replace("\xb7", "·")
|
||||
misses: list[str] = []
|
||||
|
||||
|
||||
def sub(pattern: str, repl: str, *, flags: int = 0, count: int = 0, label: str = "") -> None:
|
||||
global text
|
||||
new, n = re.subn(pattern, repl, text, count=count, flags=flags)
|
||||
tag = label or pattern[:56]
|
||||
if n == 0:
|
||||
misses.append(tag)
|
||||
print("MISS:", tag)
|
||||
else:
|
||||
print(f"OK x{n}:", tag)
|
||||
text = new
|
||||
|
||||
|
||||
sub(r"// iframe \?+ Tab \?+", "// iframe 内无法全屏时,通知父页 Tab 全屏", label="fullscreen comment")
|
||||
sub(r"// Free place .+?pin every dragged id", "// Free place → pin every dragged id", label="free place comment")
|
||||
sub(r"// Contain: \?+", "// Contain: 等比例完整放入框内,不裁切", label="contain comment")
|
||||
|
||||
sub(r'(wrapTextLines\(ctx, n\.text \|\| )"\?+"', r'\1"未命名"', label="unnamed wrap")
|
||||
sub(r'(n\.text \|\| )"\?+"', r'\1"未命名"', label="unnamed text")
|
||||
|
||||
sub(
|
||||
r'text: "\?+", parentId: parent\b',
|
||||
'text: "新节点", parentId: parent',
|
||||
label="tab child",
|
||||
)
|
||||
sub(
|
||||
r': \{ type: "create_node", text: "\?+" \},',
|
||||
': { type: "create_node", text: "新节点" },',
|
||||
label="tab orphan / enter orphan",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: -1',
|
||||
'text: "左侧子主题",\n parentId: selectedIds[0],\n sidePref: -1',
|
||||
label="arrow left",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: selectedIds\[0\],\n sidePref: 1',
|
||||
'text: "右侧子主题",\n parentId: selectedIds[0],\n sidePref: 1',
|
||||
label="arrow right",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n parentId: parentOfSel',
|
||||
'text: "兄弟节点",\n parentId: parentOfSel',
|
||||
label="enter sibling",
|
||||
)
|
||||
sub(
|
||||
r'text: "\?+",\n \.\.\.\(selectedIds\[0\]',
|
||||
'text: "新节点",\n ...(selectedIds[0]',
|
||||
label="dblclick create",
|
||||
)
|
||||
sub(r'label: "\?+"', 'label: "关联"', label="relation label")
|
||||
|
||||
sub(
|
||||
r'(className="canvas-tool-btn"\n title=")\?+("\n onClick=\{requestBackToRoster\}\n >\n )\?+(\n </button>)',
|
||||
r"\1返回画布列表\2返回\3",
|
||||
label="back to roster",
|
||||
)
|
||||
|
||||
sub(r'\["select", "\?+", "V"\]', '["select", "选择", "V"]', label="select")
|
||||
sub(r'\["pan", "\?+", "H"\]', '["pan", "平移", "H"]', label="pan")
|
||||
sub(r'\["link", "\?+", "C"\]', '["link", "连线", "C"]', label="link")
|
||||
|
||||
sub(
|
||||
r'(aria-pressed=\{dragScope === "node"\}\n onClick=\{\(\) => setDragScope\("node"\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1单点\2",
|
||||
label="drag node label",
|
||||
)
|
||||
sub(
|
||||
r'(dragScope === "node" \? " is-active" : ""\}`\}\n title=")\?+(")',
|
||||
r"\1单点拖拽:只移动当前节点 (N);按住 Shift 也可临时单点\2",
|
||||
label="drag node title",
|
||||
)
|
||||
sub(
|
||||
r'(aria-pressed=\{dragScope === "branch"\}\n onClick=\{\(\) => setDragScope\("branch"\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1整枝\2",
|
||||
label="drag branch label",
|
||||
)
|
||||
sub(
|
||||
r'(dragScope === "branch" \? " is-active" : ""\}`\}\n title=")\?+(")',
|
||||
r"\1整枝拖拽:节点与子孙一起移动 (B)\2",
|
||||
label="drag branch title",
|
||||
)
|
||||
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? "" : " is-active"\}`\}\n title=")\?+("\n aria-pressed=\{!cardView\})',
|
||||
r"\1气泡视图:精简标题\2",
|
||||
label="bubble title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "bubble" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\})',
|
||||
r"\1气泡\2",
|
||||
label="bubble label",
|
||||
)
|
||||
sub(
|
||||
r'(className=\{`canvas-tool-btn\$\{cardView \? " is-active" : ""\}`\}\n title=")\?+("\n aria-pressed=\{cardView\})',
|
||||
r"\1卡片视图:展开属性面板\2",
|
||||
label="card title",
|
||||
)
|
||||
sub(
|
||||
r'(void setPrefs\(\{ nodeViewMode: "card" \}\);\n \}\}\n >\n )\?+(\n </button>\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+(")',
|
||||
r"\1卡片\2取消钉住:让选中节点重新参与自动布局\3",
|
||||
label="card label + unpin title",
|
||||
)
|
||||
sub(
|
||||
r'(pinned: false \},\n \]\);\n \}\}\n >\n )\?+(\n </button>\n </div>\n <div className="canvas-toolbar__sep" />\n <div className="canvas-toolbar__group">\n <button\n type="button"\n className="canvas-tool-btn"\n title=")\?+("\n disabled=\{!lastChangeSetId\})',
|
||||
r"\1解钉\2撤销\3",
|
||||
label="unpin + undo titles/labels",
|
||||
)
|
||||
sub(
|
||||
r'(disabled=\{!lastChangeSetId\}\n onClick=\{\(\) => void undo\(\)\}\n >\n )\?+(\n </button>)',
|
||||
r"\1撤销\2",
|
||||
label="undo label",
|
||||
)
|
||||
|
||||
sub(
|
||||
r'(className="canvas-toolbar"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r"\1画布工具\2",
|
||||
label="toolbar aria",
|
||||
)
|
||||
sub(
|
||||
r'(className="canvas-controls"\n role="toolbar"\n aria-label=")\?+(")',
|
||||
r"\1视图控制\2",
|
||||
label="controls aria",
|
||||
)
|
||||
|
||||
sub(
|
||||
r'title=\{fullscreen \? "\?+" : "\?+"\}\n aria-label=\{fullscreen \? "\?+" : "\?+"\}',
|
||||
'title={fullscreen ? "退出全屏" : "全屏"}\n aria-label={fullscreen ? "退出全屏" : "全屏"}',
|
||||
label="fullscreen",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(\+\)"\n aria-label="\?+"',
|
||||
'title="放大 (+)"\n aria-label="放大"',
|
||||
label="zoom in",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(-\)"\n aria-label="\?+"',
|
||||
'title="缩小 (-)"\n aria-label="缩小"',
|
||||
label="zoom out",
|
||||
)
|
||||
sub(
|
||||
r'title="\?+ \(F\)"\n aria-label="\?+"',
|
||||
'title="适应画布 (F)"\n aria-label="适应画布"',
|
||||
label="fit",
|
||||
)
|
||||
sub(
|
||||
r'title=\{locked \? "\?+ \(L\)" : "\?+ \(L\)"\}\n aria-label=\{locked \? "\?+" : "\?+"\}',
|
||||
'title={locked ? "解锁画布 (L)" : "锁定画布 (L)"}\n aria-label={locked ? "解锁" : "锁定"}',
|
||||
label="lock",
|
||||
)
|
||||
|
||||
sub(
|
||||
r'const text = side < 0 \? "\?+" : "\?+";',
|
||||
'const text = side < 0 ? "左侧子主题" : "右侧子主题";',
|
||||
label="handle text",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--left"\n style=\{\{ left: cx - hw - 14, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r"\1添加左侧子主题\2",
|
||||
label="handle left title",
|
||||
)
|
||||
sub(
|
||||
r'(className="node-handle node-handle--right"\n style=\{\{ left: cx \+ hw - 2, top: cy - 10 \}\}\n title=")\?+(")',
|
||||
r"\1添加右侧子主题\2",
|
||||
label="handle right title",
|
||||
)
|
||||
|
||||
sub(
|
||||
r"""\{locked
|
||||
\? "[^"]*"
|
||||
: dragScope === "node"
|
||||
\? "[^"]*"
|
||||
: "[^"]*"\}""",
|
||||
"""{locked
|
||||
? "已锁定 · 仅浏览/缩放/平移"
|
||||
: dragScope === "node"
|
||||
? "单点拖 · 只移动当前节点 · Shift 临时单点 · Esc 取消"
|
||||
: "整枝拖 · 空白钉住 · 落到节点换父 · N 单点 · Esc 取消"}""",
|
||||
label="canvas hint",
|
||||
)
|
||||
|
||||
remaining_ui = re.findall(r'"\?{2,}"', text)
|
||||
remaining_btn = re.findall(r">\n\s+\?{2,}\n\s+</button>", text)
|
||||
cjk = len(re.findall(r"[\u4e00-\u9fff]+", text))
|
||||
print("quoted ?? left:", remaining_ui)
|
||||
print("button ?? left:", len(remaining_btn))
|
||||
print("cjk tokens:", cjk)
|
||||
print("misses:", len(misses))
|
||||
for m in misses:
|
||||
print(" -", m)
|
||||
|
||||
p.write_text(text, encoding="utf-8", newline="\n")
|
||||
print("wrote", p)
|
||||
|
||||
if remaining_ui or remaining_btn or misses:
|
||||
raise SystemExit(2)
|
||||
print("SUCCESS")
|
||||
10
apps/web/scripts/verify-dist-cjk.py
Normal file
10
apps/web/scripts/verify-dist-cjk.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
d = Path(__file__).resolve().parents[1] / "dist" / "assets"
|
||||
js_path = next(d.glob("index-*.js"))
|
||||
js = js_path.read_text(encoding="utf-8")
|
||||
needles = ["选择", "平移", "连线", "单点", "整枝", "气泡", "卡片", "解钉", "撤销"]
|
||||
print(js_path.name)
|
||||
print({n: (n in js) for n in needles})
|
||||
print("corrupt select:", '["select","??"' in js or '["select", "??"' in js)
|
||||
14
apps/web/scripts/verify-topology-cjk.py
Normal file
14
apps/web/scripts/verify-topology-cjk.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
p = Path(__file__).resolve().parents[1] / "src" / "TopologyCanvas.tsx"
|
||||
t = p.read_text(encoding="utf-8")
|
||||
print("utf8 ok, cjk tokens:", len(re.findall(r"[\u4e00-\u9fff]+", t)))
|
||||
print("quoted ??:", re.findall(r'"\?{2,}"', t))
|
||||
for i, line in enumerate(t.splitlines(), 1):
|
||||
if "title=" in line and ("(N)" in line or "(B)" in line or "选择" in line or "气泡" in line):
|
||||
print(f"{i}: {line.strip()}")
|
||||
# toolbar labels
|
||||
for needle in ("选择", "平移", "连线", "单点", "整枝", "气泡", "卡片", "解钉", "撤销"):
|
||||
print(needle, "OK" if needle in t else "MISSING")
|
||||
Loading…
Add table
Add a link
Reference in a new issue