@qnroa/qtype 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/CHANGELOG.zh.md +68 -0
- package/dist/core/typing/engine/TypingInput.js +72 -68
- package/dist/view/assets/index-Cve31o1m.js +219 -0
- package/dist/view/assets/{index-Dd26zdqJ.js → index-D2_Nabek.js} +1 -1
- package/dist/view/assets/index-VPadwWzO.css +1 -0
- package/dist/view/assets/index-qklN7dNP.js +3 -0
- package/dist/view/index.html +2 -2
- package/package.json +1 -1
- package/dist/view/assets/index-BEGTg0_e.js +0 -3
- package/dist/view/assets/index-C59VhMlk.css +0 -1
- package/dist/view/assets/index-Dzj2d3KO.js +0 -194
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,93 @@ can land in any minor bump (`0.x.0`).
|
|
|
13
13
|
|
|
14
14
|
## [Unreleased]
|
|
15
15
|
|
|
16
|
+
## [0.2.4] — 2026-09-08
|
|
17
|
+
|
|
18
|
+
Bug-fix + refactor pass around the ignore-punctuation setting and the
|
|
19
|
+
plumbing that surfaces settings in the command palette and HUD menu.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Ignore-punctuation used to eat operators.** `+`, `-`, `*`, `/`,
|
|
23
|
+
`=`, `<`, `>`, `(`, `)` and other code / math symbols were treated
|
|
24
|
+
as punctuation, so turning the setting on rewrote whole code
|
|
25
|
+
passages into runs of blanks. The punctuation set is now the same
|
|
26
|
+
9 ASCII marks keybr / ptype / Monkeytype recognise
|
|
27
|
+
(`. , ! ? ; : ' " -` — sentence and clause marks only) plus the
|
|
28
|
+
corresponding CJK sentence marks, smart quotes, and Chinese book /
|
|
29
|
+
corner-bracket pairs. Operators and brackets pass through
|
|
30
|
+
unchanged.
|
|
31
|
+
- **Arena stale-render after toggling ignore-punctuation.** Switching
|
|
32
|
+
the setting mid-card left the arena rendered against the previous
|
|
33
|
+
engine's target for a tick, so punctuation slots appeared to
|
|
34
|
+
linger until the next event. The arena is now keyed on
|
|
35
|
+
`${card.id}::${engine.target}`, forcing a full remount whenever
|
|
36
|
+
the engine's effective target changes — no stale
|
|
37
|
+
`useSyncExternalStore` subscription, no leftover DOM.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **HUD ⋮ menu is now registry-driven.** Every settings entry that
|
|
41
|
+
opts in via `hud: { icon, order? }` shows up as a quick toggle
|
|
42
|
+
during typing. `hint`, `ignoreCase`, `ignorePunctuation`,
|
|
43
|
+
`allowBackspace`, `showKeyboard`, `showHands` land in the menu by
|
|
44
|
+
default; `showPhonetic`, `showGloss`, `autoSpeak` show up on word
|
|
45
|
+
/ sentence cards. Adding a new HUD toggle is now one registry
|
|
46
|
+
line.
|
|
47
|
+
- **Command palette lists every setting.** The palette walks the
|
|
48
|
+
settings registry and emits a `setting.<id>` command per entry —
|
|
49
|
+
bool settings toggle inline; enum / number / action settings open
|
|
50
|
+
the settings dialog scrolled to the row. No more manually adding
|
|
51
|
+
palette entries for each new setting.
|
|
52
|
+
- **`useHint()` extracted.** Three call sites were inlining the
|
|
53
|
+
same `usePersistedState<HintMode>('settings', 'hint', ...)` block;
|
|
54
|
+
they all use the shared hook now.
|
|
55
|
+
|
|
56
|
+
## [0.2.3] — 2026-09-07
|
|
57
|
+
|
|
58
|
+
Fixes long-standing bugs around Chinese IME input on the typing arena
|
|
59
|
+
and cleans up how "ignore punctuation" behaves. The invisible editor
|
|
60
|
+
is now a `<input>` instead of a contenteditable div, matching the
|
|
61
|
+
pattern every mainstream Chinese typing site uses; character capture
|
|
62
|
+
reads `el.value` on the `input` event, so IME commits arrive as the
|
|
63
|
+
final glyph — no more races between keydown and beforeinput.
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- **Miss-slot display setting** (Appearance → Miss display). Default
|
|
67
|
+
keeps the target glyph visible in red (matches Monkeytype / keybr
|
|
68
|
+
/ ohMyType / ptype conventions — user sees *what should have been
|
|
69
|
+
there*). Flip to "Typed" to render the user's actual keystroke in
|
|
70
|
+
red at the miss slot instead — useful when reviewing exactly what
|
|
71
|
+
went wrong.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- **Editor element is now a hidden `<input>`, not `<div
|
|
75
|
+
contentEditable>`.** IME commits and macOS smart-punctuation
|
|
76
|
+
substitutions land in `el.value` as the final glyph, which the
|
|
77
|
+
`input` event reads and emits to the engine. Composition (pinyin
|
|
78
|
+
preedit) is gated on the event's own `isComposing` /
|
|
79
|
+
`inputType === 'insertCompositionText'` signals rather than the
|
|
80
|
+
hook's state machine, so browsers that fire `input` before
|
|
81
|
+
`compositionstart` can't leak preedit letters through as
|
|
82
|
+
characters.
|
|
83
|
+
- **`Ignore punctuation` now rewrites punctuation to space at engine
|
|
84
|
+
construction.** Previously the engine fast-forwarded through
|
|
85
|
+
punctuation slots inside `onKeyDown`, which left the arena still
|
|
86
|
+
showing punctuation and the on-screen keyboard still hinting the
|
|
87
|
+
punctuation key. Now every punctuation codepoint in the target
|
|
88
|
+
becomes a regular space up-front — the arena, the keyboard hint
|
|
89
|
+
and the engine all see the same target sequence, no divergence.
|
|
90
|
+
Toggling the setting mid-card rebuilds the engine via the
|
|
91
|
+
`useTypingEngine` memo.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
- **Chinese full-width punctuation shown as ASCII.** The old
|
|
95
|
+
keydown-based capture path picked up whichever of `keydown.key` /
|
|
96
|
+
`beforeinput.data` fired first and could substitute a half-width
|
|
97
|
+
glyph. Moving to `<input>` + `input` event and always rendering
|
|
98
|
+
the target glyph (default) puts an end to this class of bug.
|
|
99
|
+
- **Dead `softInsertText` handler removed** from `INPUT_TYPE_HANDLERS`
|
|
100
|
+
and the `handleBeforeInput` fast-path; character emission has a
|
|
101
|
+
single owner now (the `input` event).
|
|
102
|
+
|
|
16
103
|
## [0.2.2] — 2026-09-01
|
|
17
104
|
|
|
18
105
|
A responsive footer, a convention-based way to swap the site logo
|
package/CHANGELOG.zh.md
CHANGED
|
@@ -12,6 +12,74 @@ English: [CHANGELOG.md](https://www.npmjs.com/package/@qnroa/qtype?activeTab=cod
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.2.4] — 2026-09-08
|
|
16
|
+
|
|
17
|
+
围绕"忽略标点"设置和"命令面板 / HUD 菜单如何呈现设置"两件事,
|
|
18
|
+
做了一轮修复和小重构。
|
|
19
|
+
|
|
20
|
+
### 修复
|
|
21
|
+
- **忽略标点误伤运算符。** 之前 `+ - * / = < > ( )` 等代码 / 数学
|
|
22
|
+
符号都被当成标点,一开设置就把整段代码替换成空格。改成和
|
|
23
|
+
keybr / ptype / Monkeytype 一致的 9 个 ASCII 句读
|
|
24
|
+
(`. , ! ? ; : ' " -`),加上对应的中文句号 / 逗号 / 冒号 / 分号 /
|
|
25
|
+
问号 / 感叹号 / 顿号 + 中文引号 / 书名号 / 角括号。运算符和括号
|
|
26
|
+
一律不动。
|
|
27
|
+
- **切换忽略标点后 arena 一瞬间残留旧渲染。** 卡片打字中途切设置,
|
|
28
|
+
引擎重建了但 arena 的 fiber 被复用,`useSyncExternalStore`
|
|
29
|
+
订阅还挂在旧 engine 上,标点位置视觉错乱,得刷新才好。arena
|
|
30
|
+
的 React key 改成 `${card.id}::${engine.target}`,engine 目标
|
|
31
|
+
一变就整棵重挂,零残留。
|
|
32
|
+
|
|
33
|
+
### 变更
|
|
34
|
+
- **HUD ⋮ 菜单改为注册表驱动。** 设置条目里加 `hud: { icon, order? }`
|
|
35
|
+
就自动出现在打字时的快捷菜单。默认加入的:显示提示、忽略大小写、
|
|
36
|
+
忽略标点、允许退格、显示键盘、显示手位;word / sentence 卡片
|
|
37
|
+
额外多显示音标 / 释义 / 自动朗读。加一个 HUD 快捷开关现在只需
|
|
38
|
+
改注册表一行。
|
|
39
|
+
- **命令面板列出所有设置。** 面板遍历设置注册表,给每个条目生成一条
|
|
40
|
+
`setting.<id>` 命令。bool 类型直接就地翻转;枚举 / 数字 / action
|
|
41
|
+
打开设置对话框并高亮那一行。新增设置不再需要单独往命令面板加
|
|
42
|
+
条目。
|
|
43
|
+
- **抽出 `useHint()` hook。** 之前 3 个地方内联同样的
|
|
44
|
+
`usePersistedState<HintMode>('settings', 'hint', ...)`,现在统一用
|
|
45
|
+
这个 hook。
|
|
46
|
+
|
|
47
|
+
## [0.2.3] — 2026-09-07
|
|
48
|
+
|
|
49
|
+
修复打字界面在中文输入法下的一系列老问题,并把"忽略标点"的行为
|
|
50
|
+
理顺。隐藏的输入编辑器从 contenteditable 换成 `<input>`,和主流
|
|
51
|
+
中文打字站的做法一致;字符捕获改为在 `input` 事件里读 `el.value`,
|
|
52
|
+
IME 提交后的最终字符直接进引擎 —— 不再有 keydown 和 beforeinput
|
|
53
|
+
的竞态。
|
|
54
|
+
|
|
55
|
+
### 新增
|
|
56
|
+
- **错误显示设置**(外观 → 错误显示)。默认保持目标字符可见(红色),
|
|
57
|
+
和 Monkeytype / keybr / ohMyType / ptype 一致 —— 用户看到的是
|
|
58
|
+
"本来应该是什么"。切到"实际输入"则在错误位置显示用户实际敲入
|
|
59
|
+
的字符(仍然红色),方便复盘到底敲错在哪。
|
|
60
|
+
|
|
61
|
+
### 变更
|
|
62
|
+
- **编辑器元素从 `<div contentEditable>` 改为隐藏 `<input>`。** IME
|
|
63
|
+
提交和 macOS 智能标点替换都会把最终字符写进 `el.value`,由
|
|
64
|
+
`input` 事件读到再喂给引擎。组词状态(拼音预输入)靠事件自身的
|
|
65
|
+
`isComposing` / `inputType === 'insertCompositionText'` 判断,
|
|
66
|
+
不再依赖 hook 状态机的时序;这样即便浏览器先 `input` 后
|
|
67
|
+
`compositionstart`,预输入的拼音字母也不会以字符形式泄漏出去。
|
|
68
|
+
- **`忽略标点` 改为在引擎构造时把标点重写成空格。** 之前是引擎在
|
|
69
|
+
`onKeyDown` 里 fast-forward 跳过标点位置,但界面上标点还在显示,
|
|
70
|
+
键盘还在提示打标点键。现在所有标点 codepoint 上来就变空格 ——
|
|
71
|
+
arena、键盘提示、引擎看到的目标序列完全一致,不会跑偏。中途切换
|
|
72
|
+
设置时通过 `useTypingEngine` 的 memo 重建引擎。
|
|
73
|
+
|
|
74
|
+
### 修复
|
|
75
|
+
- **中文全角标点显示成 ASCII。** 老的 keydown 捕获路径会挑
|
|
76
|
+
`keydown.key` 或 `beforeinput.data` 里先来的那个,可能替换成半角。
|
|
77
|
+
改用 `<input>` + `input` 事件、并默认在打错位置显示目标字符,
|
|
78
|
+
这类问题一次性解决。
|
|
79
|
+
- **删除死代码 `softInsertText`。** `INPUT_TYPE_HANDLERS` 和
|
|
80
|
+
`handleBeforeInput` 里的 `insertText` 特殊分支一起删掉,字符发射
|
|
81
|
+
只由 `input` 事件负责。
|
|
82
|
+
|
|
15
83
|
## [0.2.2] — 2026-09-01
|
|
16
84
|
|
|
17
85
|
响应式页脚,约定式自定义 logo 和 favicon(不用改代码),以及把 view
|
|
@@ -31,10 +31,28 @@ export class TypingInput {
|
|
|
31
31
|
#position;
|
|
32
32
|
#listeners = new Set();
|
|
33
33
|
constructor(text, settings) {
|
|
34
|
-
this.#target = text;
|
|
35
|
-
this.#targetCodepoints = Array.from(text, (ch) => ch.codePointAt(0) ?? 0);
|
|
36
|
-
this.#length = this.#targetCodepoints.length;
|
|
37
34
|
this.settings = { ...defaultTypingSettings, ...settings };
|
|
35
|
+
// ignorePunctuation replaces every punctuation codepoint with a
|
|
36
|
+
// regular space in the target sequence. The engine, the arena
|
|
37
|
+
// renderer, and the keyboard hint all just see a space — no
|
|
38
|
+
// fast-forward machinery, no special-case display, no divergence
|
|
39
|
+
// between what the user sees and what they need to type.
|
|
40
|
+
const raw = Array.from(text, (ch) => ch.codePointAt(0) ?? 0);
|
|
41
|
+
this.#targetCodepoints = this.settings.ignorePunctuation
|
|
42
|
+
? raw.map((cp) => (isPunctuation(cp) ? 0x20 : cp))
|
|
43
|
+
: raw;
|
|
44
|
+
// The effective target string: rewritten if ignorePunctuation is
|
|
45
|
+
// on, else identical to `text`. Anything that consumes
|
|
46
|
+
// `engine.target` (keyboard-hint plans, mini-arena mirrors, etc.)
|
|
47
|
+
// sees exactly the sequence the engine expects the user to type.
|
|
48
|
+
// We avoid `String.fromCodePoint(...arr)` here — spreading a large
|
|
49
|
+
// codepoint array can blow the JS engine's argument-count ceiling
|
|
50
|
+
// on unusually long targets. Building char-by-char is O(n) with no
|
|
51
|
+
// fixed upper bound.
|
|
52
|
+
this.#target = this.settings.ignorePunctuation
|
|
53
|
+
? this.#targetCodepoints.map((cp) => String.fromCodePoint(cp)).join('')
|
|
54
|
+
: text;
|
|
55
|
+
this.#length = this.#targetCodepoints.length;
|
|
38
56
|
this.#chars = this.#buildInitialChars();
|
|
39
57
|
this.#position = 0;
|
|
40
58
|
}
|
|
@@ -60,20 +78,7 @@ export class TypingInput {
|
|
|
60
78
|
if (this.#position >= this.#length) {
|
|
61
79
|
return Feedback.Correct;
|
|
62
80
|
}
|
|
63
|
-
|
|
64
|
-
// ignorePunctuation: fast-forward through any punctuation slots
|
|
65
|
-
// before matching the user's keystroke against the next
|
|
66
|
-
// typable character. Uses a broad Unicode range so it works
|
|
67
|
-
// for both ASCII and CJK punctuation.
|
|
68
|
-
if (this.settings.ignorePunctuation) {
|
|
69
|
-
while (this.#position < this.#length &&
|
|
70
|
-
isPunctuation(this.#targetCodepoints[this.#position])) {
|
|
71
|
-
this.#autoAdvance(timestamp);
|
|
72
|
-
}
|
|
73
|
-
if (this.#position >= this.#length)
|
|
74
|
-
return Feedback.Correct;
|
|
75
|
-
expected = this.#targetCodepoints[this.#position];
|
|
76
|
-
}
|
|
81
|
+
const expected = this.#targetCodepoints[this.#position];
|
|
77
82
|
// Normalize both sides through the same equivalence table so
|
|
78
83
|
// fullwidth/half-width / smart-quote / CJK-punctuation variants
|
|
79
84
|
// don't cause false misses. Display remains untouched.
|
|
@@ -195,28 +200,6 @@ export class TypingInput {
|
|
|
195
200
|
for (const l of this.#listeners)
|
|
196
201
|
l();
|
|
197
202
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Mark the current slot as auto-advanced Hit (used by
|
|
200
|
-
* `ignorePunctuation`). Records the timestamp so stats include the
|
|
201
|
-
* auto-advance in `startedAt` timing, but doesn't count it toward
|
|
202
|
-
* user keystroke totals (still shows as Hit — the punctuation was
|
|
203
|
-
* "correctly typed" via the auto-advance rule).
|
|
204
|
-
*/
|
|
205
|
-
#autoAdvance(timestamp) {
|
|
206
|
-
if (this.#position >= this.#length)
|
|
207
|
-
return;
|
|
208
|
-
const expected = this.#targetCodepoints[this.#position];
|
|
209
|
-
const next = this.#chars.slice();
|
|
210
|
-
const prev = next[this.#position];
|
|
211
|
-
next[this.#position] = {
|
|
212
|
-
target: expected,
|
|
213
|
-
typed: expected,
|
|
214
|
-
attrs: (prev.attrs & ~(CharAttr.Miss | CharAttr.Cursor)) | CharAttr.Hit,
|
|
215
|
-
timestamp,
|
|
216
|
-
};
|
|
217
|
-
this.#chars = next;
|
|
218
|
-
this.#position++;
|
|
219
|
-
}
|
|
220
203
|
}
|
|
221
204
|
/**
|
|
222
205
|
* ASCII case-insensitive comparison. Non-ASCII (CJK, accented Latin,
|
|
@@ -233,35 +216,56 @@ function sameLetter(a, b) {
|
|
|
233
216
|
return false;
|
|
234
217
|
}
|
|
235
218
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* (
|
|
219
|
+
* Sentence / clause punctuation, per keybr + ptype + Monkeytype
|
|
220
|
+
* conventions. Explicitly excludes math operators (`+ - * / = < >`),
|
|
221
|
+
* brackets (`( ) [ ] { }`), and code-symbol characters (`_ | \ @ # $
|
|
222
|
+
* % & ^ ~ \``) — those show up in real prose and code and should be
|
|
223
|
+
* typed as-is, not silently rewritten to space.
|
|
224
|
+
*
|
|
225
|
+
* Uses an inline set of codepoints instead of `\p{P}` — the Unicode
|
|
226
|
+
* punctuation category also matches math / brackets / dashes we want
|
|
227
|
+
* to leave alone, which is exactly why every mainstream typing app
|
|
228
|
+
* hard-codes its own list.
|
|
239
229
|
*/
|
|
240
230
|
function isPunctuation(cp) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
231
|
+
switch (cp) {
|
|
232
|
+
// ASCII sentence / clause marks. . , ! ? ; : ' "
|
|
233
|
+
case 0x2e:
|
|
234
|
+
case 0x2c:
|
|
235
|
+
case 0x21:
|
|
236
|
+
case 0x3f:
|
|
237
|
+
case 0x3b:
|
|
238
|
+
case 0x3a:
|
|
239
|
+
case 0x27:
|
|
240
|
+
case 0x22:
|
|
241
|
+
// CJK sentence marks: 。 、
|
|
242
|
+
case 0x3001:
|
|
243
|
+
case 0x3002:
|
|
244
|
+
// Fullwidth forms: ! ? , : ; ' "
|
|
245
|
+
case 0xff01:
|
|
246
|
+
case 0xff1f:
|
|
247
|
+
case 0xff0c:
|
|
248
|
+
case 0xff1a:
|
|
249
|
+
case 0xff1b:
|
|
250
|
+
case 0xff07:
|
|
251
|
+
case 0xff02:
|
|
252
|
+
// Smart quotes: " " ' '
|
|
253
|
+
case 0x201c:
|
|
254
|
+
case 0x201d:
|
|
255
|
+
case 0x2018:
|
|
256
|
+
case 0x2019:
|
|
257
|
+
// Book title marks: 《 》 〈 〉
|
|
258
|
+
case 0x300a:
|
|
259
|
+
case 0x300b:
|
|
260
|
+
case 0x3008:
|
|
261
|
+
case 0x3009:
|
|
262
|
+
// CJK corner brackets: 「 」 『 』
|
|
263
|
+
case 0x300c:
|
|
264
|
+
case 0x300d:
|
|
265
|
+
case 0x300e:
|
|
266
|
+
case 0x300f:
|
|
267
|
+
return true;
|
|
268
|
+
default:
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
267
271
|
}
|