@qnroa/qtype 0.0.3 → 0.0.5
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 +61 -0
- package/CHANGELOG.zh.md +51 -0
- package/dist/cli/commands/publish/build/action.js +49 -0
- package/dist/cli/commands/publish/build/mermaidPrerender.js +30 -8
- package/dist/view/assets/{index-CRh4USfC.js → index-1XXyxiQO.js} +28 -28
- package/dist/view/assets/{index-ByMJp0a5.js → index-DIBSxFYl.js} +2 -2
- package/dist/view/assets/{index-JNx7Ohoj.js → index-DgHvnMV9.js} +1 -1
- package/dist/view/index.html +2 -2
- package/docs/en/settings.md +2 -2
- package/docs/zh/settings.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,67 @@ can land in any minor bump (`0.x.0`).
|
|
|
13
13
|
|
|
14
14
|
## [Unreleased]
|
|
15
15
|
|
|
16
|
+
## [0.0.5] - 2026-08-22
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Mermaid pre-render no longer fails under `npx` / hosted CI.**
|
|
21
|
+
`mermaidPrerender.init()` now uses `createRequire(import.meta.url).resolve('mermaid/…')`
|
|
22
|
+
to locate the mermaid ESM, so Node's own module resolver walks the
|
|
23
|
+
actual `node_modules` chain regardless of where qtype was installed
|
|
24
|
+
— user repo, npx cache, Cloudflare Pages build root, monorepo hoist.
|
|
25
|
+
The previous two hard-coded lookup paths remain as fallbacks but
|
|
26
|
+
are no longer the primary strategy. This fixes the
|
|
27
|
+
`mermaid ESM not found in node_modules` error on Cloudflare Pages
|
|
28
|
+
when a deck actually contains ` ```mermaid ` code blocks.
|
|
29
|
+
- **`publish build` now copies `material/assets/**`** into
|
|
30
|
+
`dist/material/assets/` verbatim. Previously the CLI only walked
|
|
31
|
+
`.md` files, so any image the cards referenced via
|
|
32
|
+
`` returned 404 on the deployed site.
|
|
33
|
+
|
|
34
|
+
## [0.0.4] - 2026-08-22
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Site name in browser tab + header** — `qtype publish build` now
|
|
39
|
+
writes the site's name into `dist/index.html` (both the `<title>`
|
|
40
|
+
and a `data-qtype-repo-name` attribute on `<html>`). The view reads
|
|
41
|
+
it via `readBootConfig()` so the Header brand label reflects the
|
|
42
|
+
actual deck instead of a hardcoded "qtype". Precedence:
|
|
43
|
+
`repo.name` in `.qtype/config.json` wins, otherwise the repo
|
|
44
|
+
directory basename.
|
|
45
|
+
- **HUD is present from t=0** — the pause / hint / keyboard toggles
|
|
46
|
+
are now reachable the moment the typing page mounts, not after the
|
|
47
|
+
first keystroke. `useHudBridge` derives an initial `TypingState` +
|
|
48
|
+
`TypingMetrics` from the engine on mount; arena's `onStateChange`
|
|
49
|
+
push still overrides once real events fire.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Default sound settings** — `qtype.keySoundEnabled` and
|
|
54
|
+
`qtype.errorSoundEnabled` now default to `true` for new users. Both
|
|
55
|
+
were `false` before; existing users' saved preferences are
|
|
56
|
+
untouched.
|
|
57
|
+
- **Default AFK threshold** raised from 15 s to 60 s so a short pause
|
|
58
|
+
to think doesn't cost the flow. Also clarified the settings
|
|
59
|
+
description (mouse hover without movement counts as AFK; tab hidden
|
|
60
|
+
/ window blur pauses immediately).
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **`typing.$file.tsx` no longer synthesises HUD placeholder state**
|
|
65
|
+
in-line — the fix lives in `useHudBridge` where it belongs. Removes
|
|
66
|
+
the risk of the placeholder drifting from arena's real derivation.
|
|
67
|
+
- **`deriveAesKey` returns an extractable AES key** so the view can
|
|
68
|
+
cache the derived bytes in sessionStorage — a same-tab reload no
|
|
69
|
+
longer re-prompts for the password. Regression test added under
|
|
70
|
+
`__tests__/core/encrypt.test.ts`.
|
|
71
|
+
- **Docs link cleanup** — all internal `.md` links in the npm-shipped
|
|
72
|
+
files now go through `https://www.npmjs.com/package/@qnroa/qtype?activeTab=code&filepath=...`
|
|
73
|
+
URLs so users on npm can click through even when the source repo
|
|
74
|
+
is private. Removed dead links to `CONTRIBUTING.md` /
|
|
75
|
+
`architecture.md` / `TODO.md` since those aren't shipped.
|
|
76
|
+
|
|
16
77
|
## [0.0.1] - 2026-08-16
|
|
17
78
|
|
|
18
79
|
Initial development release. Everything below is the baseline shipped
|
package/CHANGELOG.zh.md
CHANGED
|
@@ -12,6 +12,57 @@ English: [CHANGELOG.md](https://www.npmjs.com/package/@qnroa/qtype?activeTab=cod
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.0.5] - 2026-08-22
|
|
16
|
+
|
|
17
|
+
### 修复
|
|
18
|
+
|
|
19
|
+
- **Mermaid 预渲染在 `npx` / 托管 CI 环境不再失败**。
|
|
20
|
+
`mermaidPrerender.init()` 改用
|
|
21
|
+
`createRequire(import.meta.url).resolve('mermaid/…')`,让 Node
|
|
22
|
+
自己顺着 `node_modules` 链找 mermaid,不管 qtype 是装到用户仓库、
|
|
23
|
+
npx 缓存、Cloudflare Pages build root 还是 monorepo hoist 都能
|
|
24
|
+
找到。之前的两条硬编码查找路径保留作为兜底,但不再是主要策略。
|
|
25
|
+
修复了 deck 里真的有 ` ```mermaid ` 代码块时,Cloudflare Pages 上
|
|
26
|
+
`mermaid ESM not found in node_modules` 报错。
|
|
27
|
+
- **`publish build` 现在会把 `material/assets/**`** 原样拷进
|
|
28
|
+
`dist/material/assets/`。之前 CLI 只遍历 `.md` 文件,导致卡片
|
|
29
|
+
引用的 `` 图片在部署后 404。
|
|
30
|
+
|
|
31
|
+
## [0.0.4] - 2026-08-22
|
|
32
|
+
|
|
33
|
+
### 新增
|
|
34
|
+
|
|
35
|
+
- **浏览器 tab 标题 + 左上角显示站点名** —— `qtype publish build`
|
|
36
|
+
会把站点名写进 `dist/index.html`(同时改 `<title>` 和 `<html>` 上的
|
|
37
|
+
`data-qtype-repo-name` 属性)。view 通过 `readBootConfig()` 读它,
|
|
38
|
+
Header 的品牌位显示当前 deck 名而不是硬编码的 "qtype"。取值优先级:
|
|
39
|
+
`.qtype/config.json` 里的 `repo.name` > 仓库目录名。
|
|
40
|
+
- **HUD 一进打字页就显示** —— 暂停 / 提示 / 键盘切换按钮从打字页
|
|
41
|
+
挂载起就可用,不再需要等用户敲第一下。`useHudBridge` 在 mount 时
|
|
42
|
+
就从 engine 派生初始 `TypingState` + `TypingMetrics`;arena 的
|
|
43
|
+
`onStateChange` 推送开始后无缝覆盖。
|
|
44
|
+
|
|
45
|
+
### 变更
|
|
46
|
+
|
|
47
|
+
- **默认打开按键音和错误音** —— `qtype.keySoundEnabled` 和
|
|
48
|
+
`qtype.errorSoundEnabled` 对新用户默认改为 `true`(之前都是
|
|
49
|
+
`false`)。已有用户的设置不受影响。
|
|
50
|
+
- **AFK 默认阈值** 从 15 秒调到 60 秒,给思考一点空间。同时把设置
|
|
51
|
+
说明写清楚:鼠标悬停不动也算 AFK;切 tab 或窗口失焦会立即暂停。
|
|
52
|
+
|
|
53
|
+
### 修复
|
|
54
|
+
|
|
55
|
+
- **`typing.$file.tsx` 不再手写 HUD 初始状态** —— 修复搬到该在的
|
|
56
|
+
`useHudBridge`。避免手写值和 arena 真实派生逻辑漂移。
|
|
57
|
+
- **`deriveAesKey` 返回可导出的 AES 密钥**,view 端才能把派生结果
|
|
58
|
+
存进 sessionStorage —— 同 tab 刷新不再重问密码。回归测试见
|
|
59
|
+
`__tests__/core/encrypt.test.ts`。
|
|
60
|
+
- **文档链接整改** —— npm 发布的文件里所有内部 `.md` 链接都改成
|
|
61
|
+
`https://www.npmjs.com/package/@qnroa/qtype?activeTab=code&filepath=...`
|
|
62
|
+
形式的 npm 链接,私仓状态下 npm 用户点了也能看到内容。删掉了指向
|
|
63
|
+
`CONTRIBUTING.md` / `architecture.md` / `TODO.md` 这些不发布文件
|
|
64
|
+
的死链。
|
|
65
|
+
|
|
15
66
|
## [0.0.1] - 2026-08-16
|
|
16
67
|
|
|
17
68
|
首个开发版。以下是这个版本的基线内容。
|
|
@@ -42,6 +42,10 @@ export async function publishBuildAction(opts = {}) {
|
|
|
42
42
|
const encryptCtx = await resolveEncryptCtx(cwd);
|
|
43
43
|
const mermaidMode = await resolveMermaidMode(cwd);
|
|
44
44
|
const repoLang = await resolveRepoLang(cwd);
|
|
45
|
+
// Wiki display name: repo.name from config wins over the directory
|
|
46
|
+
// basename, so users can `qtype config set repo.name "..."` to
|
|
47
|
+
// override how the site labels itself in the browser tab.
|
|
48
|
+
const name = await resolveRepoName(cwd);
|
|
45
49
|
const materials = [];
|
|
46
50
|
for (const file of walkMaterial(cwd)) {
|
|
47
51
|
const body = readMaterial(file);
|
|
@@ -64,6 +68,11 @@ export async function publishBuildAction(opts = {}) {
|
|
|
64
68
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
65
69
|
fs.mkdirSync(outDir, { recursive: true });
|
|
66
70
|
copyDir(viewBundle, outDir);
|
|
71
|
+
// Rewrite dist/index.html to carry the site's name — both in the
|
|
72
|
+
// <title> (browser tab) and as a `data-qtype-repo-name` attribute on
|
|
73
|
+
// <html> (read by the view's `readBootConfig()` for the header
|
|
74
|
+
// brand label and anywhere else the repo name is shown).
|
|
75
|
+
rewriteIndexHtml(path.join(outDir, 'index.html'), name);
|
|
67
76
|
// Defensive: clear any stale material/ that might have snuck into the
|
|
68
77
|
// view bundle. Since dev-seed content lives at repo root's examples/
|
|
69
78
|
// (served only by the vite dev middleware, not the bundle), this is a
|
|
@@ -107,6 +116,14 @@ export async function publishBuildAction(opts = {}) {
|
|
|
107
116
|
fs.writeFileSync(path.join(targetMaterial, 'mermaid.css'), mermaid.css);
|
|
108
117
|
}
|
|
109
118
|
}
|
|
119
|
+
// Copy `material/assets/**` verbatim (images referenced by cards
|
|
120
|
+
// via `` need to actually resolve at the
|
|
121
|
+
// deployed URL). Assets are NOT encrypted — they're image bytes,
|
|
122
|
+
// and the encryption pass only wraps `.md` content anyway.
|
|
123
|
+
const srcAssets = path.join(cwd, MATERIAL_DIR, 'assets');
|
|
124
|
+
if (fs.existsSync(srcAssets)) {
|
|
125
|
+
copyDir(srcAssets, path.join(targetMaterial, 'assets'));
|
|
126
|
+
}
|
|
110
127
|
// Manifest: lists materials + optional encryption envelope + mermaid mode.
|
|
111
128
|
const manifest = { materials: entries };
|
|
112
129
|
if (encryptCtx) {
|
|
@@ -186,6 +203,38 @@ async function resolveRepoLang(cwd) {
|
|
|
186
203
|
return 'zh-CN';
|
|
187
204
|
return 'en-US';
|
|
188
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Repo display name: `repo.name` from `.qtype/config.json` wins over
|
|
208
|
+
* the directory basename, so users can override without renaming the
|
|
209
|
+
* folder. Same rule as qrism's `loadPublishConfig`.
|
|
210
|
+
*/
|
|
211
|
+
async function resolveRepoName(cwd) {
|
|
212
|
+
const kv = createRepoKv(cwd);
|
|
213
|
+
if (await kv.available()) {
|
|
214
|
+
const v = await kv.get('repo.name');
|
|
215
|
+
if (typeof v === 'string' && v.trim())
|
|
216
|
+
return v.trim();
|
|
217
|
+
}
|
|
218
|
+
return path.basename(cwd);
|
|
219
|
+
}
|
|
220
|
+
function rewriteIndexHtml(indexPath, name) {
|
|
221
|
+
if (!fs.existsSync(indexPath))
|
|
222
|
+
return;
|
|
223
|
+
const html = fs.readFileSync(indexPath, 'utf8');
|
|
224
|
+
const escaped = name
|
|
225
|
+
.replace(/&/g, '&')
|
|
226
|
+
.replace(/</g, '<')
|
|
227
|
+
.replace(/>/g, '>')
|
|
228
|
+
.replace(/"/g, '"');
|
|
229
|
+
const withTitle = html.replace(/<title>[^<]*<\/title>/, `<title>${escaped}</title>`);
|
|
230
|
+
// Add or replace data-qtype-repo-name on the root <html> tag.
|
|
231
|
+
// Two branches so a re-run cleanly overwrites an existing attribute.
|
|
232
|
+
const rewritten = /<html\b[^>]*\bdata-qtype-repo-name=/.test(withTitle)
|
|
233
|
+
? withTitle.replace(/(<html\b[^>]*\bdata-qtype-repo-name=)"[^"]*"/, `$1"${escaped}"`)
|
|
234
|
+
: withTitle.replace(/<html\b/, `<html data-qtype-repo-name="${escaped}"`);
|
|
235
|
+
if (rewritten !== html)
|
|
236
|
+
fs.writeFileSync(indexPath, rewritten);
|
|
237
|
+
}
|
|
189
238
|
async function obtainPassword() {
|
|
190
239
|
const envPwd = process.env.QTYPE_PUBLISH_PASSWORD;
|
|
191
240
|
if (envPwd)
|
|
@@ -38,15 +38,37 @@ export class MermaidRenderer {
|
|
|
38
38
|
pageDark = null;
|
|
39
39
|
pageLight = null;
|
|
40
40
|
async init() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
// Ask Node's own module resolver where mermaid actually lives. It
|
|
42
|
+
// walks the `node_modules` chain from this file up, so it works
|
|
43
|
+
// regardless of where the caller installed qtype:
|
|
44
|
+
//
|
|
45
|
+
// * user repo with `npm install @qnroa/qtype` → resolves in
|
|
46
|
+
// `<user>/node_modules/mermaid`
|
|
47
|
+
// * `npx @qnroa/qtype` on any host (Cloudflare, → resolves in
|
|
48
|
+
// Vercel, GitHub Pages) that hoists deps into the npx cache
|
|
49
|
+
// an npx cache
|
|
50
|
+
// * monorepo with hoisted deps → resolves in
|
|
51
|
+
// the hoisted root
|
|
52
|
+
//
|
|
53
|
+
// The two hard-coded fallbacks below only fire if Node's resolver
|
|
54
|
+
// fails entirely (older Node, exotic packaging), which shouldn't
|
|
55
|
+
// happen in any supported deployment.
|
|
45
56
|
let mermaidEsm = null;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
try {
|
|
58
|
+
const { createRequire } = await import('node:module');
|
|
59
|
+
const req = createRequire(import.meta.url);
|
|
60
|
+
mermaidEsm = req.resolve('mermaid/dist/mermaid.esm.min.mjs');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
const candidates = [
|
|
64
|
+
path.resolve(__dirname, '..', '..', '..', '..', '..', 'node_modules', 'mermaid', 'dist', 'mermaid.esm.min.mjs'),
|
|
65
|
+
path.resolve(process.cwd(), 'node_modules', 'mermaid', 'dist', 'mermaid.esm.min.mjs'),
|
|
66
|
+
];
|
|
67
|
+
for (const c of candidates) {
|
|
68
|
+
if (fs.existsSync(c)) {
|
|
69
|
+
mermaidEsm = c;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
if (!mermaidEsm) {
|