@qnroa/qtype 0.0.1 → 0.0.3

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.
Files changed (468) hide show
  1. package/CHANGELOG.md +1 -3
  2. package/CHANGELOG.zh.md +1 -2
  3. package/README.md +88 -56
  4. package/README.zh.md +82 -62
  5. package/dist/cli/bootstrap.js +4 -115
  6. package/dist/cli/commands/check/index.js +11 -10
  7. package/dist/cli/commands/config/action.js +5 -9
  8. package/dist/cli/commands/content/lint/action.js +38 -44
  9. package/dist/cli/commands/content/search/action.js +44 -34
  10. package/dist/cli/commands/content/search/i18n.js +6 -0
  11. package/dist/cli/commands/content/search/index.js +19 -1
  12. package/dist/cli/commands/content/validate/action.js +28 -27
  13. package/dist/cli/commands/index.js +1 -3
  14. package/dist/cli/commands/pack/index.js +2 -1
  15. package/dist/cli/commands/publish/build/action.js +39 -11
  16. package/dist/cli/commands/repo/new/action.js +23 -4
  17. package/dist/cli/commands/repo/new/i18n.js +2 -2
  18. package/dist/cli/commands/repo/sync/action.js +24 -4
  19. package/dist/cli/commands/repo/templates.js +2 -1
  20. package/dist/cli/utils/material.js +49 -0
  21. package/dist/core/encrypt.js +24 -1
  22. package/dist/core/index.js +1 -0
  23. package/dist/core/parser/frontmatter.js +39 -34
  24. package/dist/core/parser/parseMaterial.js +174 -24
  25. package/dist/core/romanization/index.js +152 -0
  26. package/dist/core/schemas/both.js +52 -0
  27. package/dist/core/schemas/index.js +3 -0
  28. package/dist/core/schemas/repo.js +55 -0
  29. package/dist/core/schemas/user.js +19 -0
  30. package/dist/core/typing/engine/TypingInput.js +243 -0
  31. package/dist/core/typing/engine/chars.js +30 -0
  32. package/dist/core/typing/engine/feedback.js +15 -0
  33. package/dist/core/typing/engine/index.js +5 -0
  34. package/dist/core/typing/engine/settings.js +8 -0
  35. package/dist/core/typing/engine/stats.js +45 -0
  36. package/dist/core/typing/index.js +9 -1
  37. package/dist/view/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  38. package/dist/view/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  39. package/dist/view/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  40. package/dist/view/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  41. package/dist/view/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  42. package/dist/view/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  43. package/dist/view/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  44. package/dist/view/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  45. package/dist/view/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  46. package/dist/view/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  47. package/dist/view/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  48. package/dist/view/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  49. package/dist/view/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  50. package/dist/view/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  51. package/dist/view/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  52. package/dist/view/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  53. package/dist/view/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  54. package/dist/view/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  55. package/dist/view/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  56. package/dist/view/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  57. package/dist/view/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  58. package/dist/view/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  59. package/dist/view/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  60. package/dist/view/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  61. package/dist/view/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  62. package/dist/view/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  63. package/dist/view/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  64. package/dist/view/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  65. package/dist/view/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  66. package/dist/view/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  67. package/dist/view/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  68. package/dist/view/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  69. package/dist/view/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  70. package/dist/view/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  71. package/dist/view/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  72. package/dist/view/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  73. package/dist/view/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  74. package/dist/view/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  75. package/dist/view/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  76. package/dist/view/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  77. package/dist/view/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  78. package/dist/view/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  79. package/dist/view/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  80. package/dist/view/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  81. package/dist/view/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  82. package/dist/view/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  83. package/dist/view/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  84. package/dist/view/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  85. package/dist/view/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  86. package/dist/view/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  87. package/dist/view/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  88. package/dist/view/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  89. package/dist/view/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  90. package/dist/view/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  91. package/dist/view/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  92. package/dist/view/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  93. package/dist/view/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  94. package/dist/view/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  95. package/dist/view/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  96. package/dist/view/assets/abap-BdImnpbu.js +1 -0
  97. package/dist/view/assets/actionscript-3-B3316cI-.js +1 -0
  98. package/dist/view/assets/ada-bCR0ucgS.js +1 -0
  99. package/dist/view/assets/ahk-CsyLZFj1.js +1 -0
  100. package/dist/view/assets/ahk2-8Zs4aa1G.js +1 -0
  101. package/dist/view/assets/andromeeda-C4gqWexZ.js +1 -0
  102. package/dist/view/assets/angular-html-DA-rfuFy.js +1 -0
  103. package/dist/view/assets/angular-ts-BrjP3tb8.js +1 -0
  104. package/dist/view/assets/apache-Pmp26Uib.js +1 -0
  105. package/dist/view/assets/apex-DhZFqWV2.js +1 -0
  106. package/dist/view/assets/apl-CORt7UWP.js +1 -0
  107. package/dist/view/assets/applescript-Co6uUVPk.js +1 -0
  108. package/dist/view/assets/ara-BRHolxvo.js +1 -0
  109. package/dist/view/assets/asciidoc-CSVQ5wI8.js +1 -0
  110. package/dist/view/assets/asm-D_Q5rh1f.js +1 -0
  111. package/dist/view/assets/astro-HNnZUWAn.js +1 -0
  112. package/dist/view/assets/aurora-x-D-2ljcwZ.js +1 -0
  113. package/dist/view/assets/awk-DMzUqQB5.js +1 -0
  114. package/dist/view/assets/ayu-dark-DYE7WIF3.js +1 -0
  115. package/dist/view/assets/ayu-light-BA47KaF1.js +1 -0
  116. package/dist/view/assets/ayu-mirage-32ctXXKs.js +1 -0
  117. package/dist/view/assets/ballerina-BFfxhgS-.js +1 -0
  118. package/dist/view/assets/bat-CickPsom.js +1 -0
  119. package/dist/view/assets/beancount-k_qm7-4y.js +1 -0
  120. package/dist/view/assets/berry-uYugtg8r.js +1 -0
  121. package/dist/view/assets/bibtex-CHM0blh-.js +1 -0
  122. package/dist/view/assets/bicep-Bmn6On1c.js +1 -0
  123. package/dist/view/assets/bird2-Bx8U0n9b.js +1 -0
  124. package/dist/view/assets/blade-2xfisSek.js +1 -0
  125. package/dist/view/assets/bsl-DlhNcFeZ.js +1 -0
  126. package/dist/view/assets/c-BIGW1oBm.js +1 -0
  127. package/dist/view/assets/c3-Dp5svz6Z.js +1 -0
  128. package/dist/view/assets/cadence-Bv_4Rxtq.js +1 -0
  129. package/dist/view/assets/cairo-KRGpt6FW.js +1 -0
  130. package/dist/view/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
  131. package/dist/view/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
  132. package/dist/view/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
  133. package/dist/view/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
  134. package/dist/view/assets/chapel-DTp_pixX.js +1 -0
  135. package/dist/view/assets/clarity-Dn5IMItf.js +1 -0
  136. package/dist/view/assets/clojure-P80f7IUj.js +1 -0
  137. package/dist/view/assets/cmake-D1j8_8rp.js +1 -0
  138. package/dist/view/assets/cobol-nBiQ_Alo.js +1 -0
  139. package/dist/view/assets/codeowners-Bp6g37R7.js +1 -0
  140. package/dist/view/assets/codeql-DsOJ9woJ.js +1 -0
  141. package/dist/view/assets/coffee-Ch7k5sss.js +1 -0
  142. package/dist/view/assets/common-lisp-Cg-RD9OK.js +1 -0
  143. package/dist/view/assets/coq-C7JzOVbR.js +1 -0
  144. package/dist/view/assets/cpp-BMRokrvK.js +1 -0
  145. package/dist/view/assets/crystal-DGywbUpC.js +1 -0
  146. package/dist/view/assets/csharp-DSvCPggb.js +1 -0
  147. package/dist/view/assets/css-CLj8gQPS.js +1 -0
  148. package/dist/view/assets/csv-fuZLfV_i.js +1 -0
  149. package/dist/view/assets/cue-D82EKSYY.js +1 -0
  150. package/dist/view/assets/cypher-COkxafJQ.js +1 -0
  151. package/dist/view/assets/d-85-TOEBH.js +1 -0
  152. package/dist/view/assets/dark-plus-C3mMm8J8.js +1 -0
  153. package/dist/view/assets/dart-bE4Kk8sk.js +1 -0
  154. package/dist/view/assets/dax-CEL-wOlO.js +1 -0
  155. package/dist/view/assets/desktop-BmXAJ9_W.js +1 -0
  156. package/dist/view/assets/diff-D97Zzqfu.js +1 -0
  157. package/dist/view/assets/docker-BcOcwvcX.js +1 -0
  158. package/dist/view/assets/dotenv-Da5cRb03.js +1 -0
  159. package/dist/view/assets/dracula-BzJJZx-M.js +1 -0
  160. package/dist/view/assets/dracula-soft-BXkSAIEj.js +1 -0
  161. package/dist/view/assets/dream-maker-BtqSS_iP.js +1 -0
  162. package/dist/view/assets/edge-FbVlp4U3.js +1 -0
  163. package/dist/view/assets/elixir-CkH2-t6x.js +1 -0
  164. package/dist/view/assets/elm-DbKCFpqz.js +1 -0
  165. package/dist/view/assets/emacs-lisp-C_m_b--Z.js +1 -0
  166. package/dist/view/assets/erb-DXfck5VN.js +1 -0
  167. package/dist/view/assets/erlang-DsQrWhSR.js +1 -0
  168. package/dist/view/assets/everforest-dark-BgDCqdQA.js +1 -0
  169. package/dist/view/assets/everforest-light-C8M2exoo.js +1 -0
  170. package/dist/view/assets/fennel-BYunw83y.js +1 -0
  171. package/dist/view/assets/fish-BvzEVeQv.js +1 -0
  172. package/dist/view/assets/fluent-C4IJs8-o.js +1 -0
  173. package/dist/view/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  174. package/dist/view/assets/fortran-free-form-BxgE0vQu.js +1 -0
  175. package/dist/view/assets/fsharp-CXgrBDvD.js +1 -0
  176. package/dist/view/assets/gdresource-TyuKm33G.js +1 -0
  177. package/dist/view/assets/gdscript-DqcFQ5yU.js +1 -0
  178. package/dist/view/assets/gdshader-DkwncUOv.js +1 -0
  179. package/dist/view/assets/genie-D0YGMca9.js +1 -0
  180. package/dist/view/assets/gherkin-DyxjwDmM.js +1 -0
  181. package/dist/view/assets/git-commit-F4YmCXRG.js +1 -0
  182. package/dist/view/assets/git-rebase-r7XF79zn.js +1 -0
  183. package/dist/view/assets/github-dark-DHJKELXO.js +1 -0
  184. package/dist/view/assets/github-dark-default-Cuk6v7N8.js +1 -0
  185. package/dist/view/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  186. package/dist/view/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  187. package/dist/view/assets/github-light-DAi9KRSo.js +1 -0
  188. package/dist/view/assets/github-light-default-D7oLnXFd.js +1 -0
  189. package/dist/view/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  190. package/dist/view/assets/gleam-BspZqrRM.js +1 -0
  191. package/dist/view/assets/glimmer-js-ByusRIyA.js +1 -0
  192. package/dist/view/assets/glimmer-ts-BfAWNZQY.js +1 -0
  193. package/dist/view/assets/glsl-DplSGwfg.js +1 -0
  194. package/dist/view/assets/gn-n2N0HUVH.js +1 -0
  195. package/dist/view/assets/gnuplot-DdkO51Og.js +1 -0
  196. package/dist/view/assets/go-C27-OAKa.js +1 -0
  197. package/dist/view/assets/graphql-ChdNCCLP.js +1 -0
  198. package/dist/view/assets/groovy-gcz8RCvz.js +1 -0
  199. package/dist/view/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  200. package/dist/view/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  201. package/dist/view/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  202. package/dist/view/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  203. package/dist/view/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  204. package/dist/view/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  205. package/dist/view/assets/hack-BWmVpMyf.js +1 -0
  206. package/dist/view/assets/haml-D5jkg6IW.js +1 -0
  207. package/dist/view/assets/handlebars-BpdQsYii.js +1 -0
  208. package/dist/view/assets/haskell-Df6bDoY_.js +1 -0
  209. package/dist/view/assets/haxe-CfZj7gIn.js +1 -0
  210. package/dist/view/assets/hcl-BWvSN4gD.js +1 -0
  211. package/dist/view/assets/hjson-D5-asLiD.js +1 -0
  212. package/dist/view/assets/hlsl-D3lLCCz7.js +1 -0
  213. package/dist/view/assets/horizon-BUw7H-hv.js +1 -0
  214. package/dist/view/assets/horizon-bright-CUuTKBJd.js +1 -0
  215. package/dist/view/assets/houston-DnULxvSX.js +1 -0
  216. package/dist/view/assets/html-derivative-DlHx6ybY.js +1 -0
  217. package/dist/view/assets/html-pp8916En.js +1 -0
  218. package/dist/view/assets/http-jrhK8wxY.js +1 -0
  219. package/dist/view/assets/hurl-irOxFIW8.js +1 -0
  220. package/dist/view/assets/hxml-2-FPmUDs.js +1 -0
  221. package/dist/view/assets/hy-DFXneXwc.js +1 -0
  222. package/dist/view/assets/imba-DGztddWO.js +1 -0
  223. package/dist/view/assets/index-BLryk3W4.css +1 -0
  224. package/dist/view/assets/index-ByMJp0a5.js +3 -0
  225. package/dist/view/assets/index-CRh4USfC.js +189 -0
  226. package/dist/view/assets/index-JNx7Ohoj.js +153 -0
  227. package/dist/view/assets/ini-BEwlwnbL.js +1 -0
  228. package/dist/view/assets/java-CylS5w8V.js +1 -0
  229. package/dist/view/assets/javascript-wDzz0qaB.js +1 -0
  230. package/dist/view/assets/jinja-f2NsQr07.js +1 -0
  231. package/dist/view/assets/jison-wvAkD_A8.js +1 -0
  232. package/dist/view/assets/json-Cp-IABpG.js +1 -0
  233. package/dist/view/assets/json5-C9tS-k6U.js +1 -0
  234. package/dist/view/assets/jsonc-Des-eS-w.js +1 -0
  235. package/dist/view/assets/jsonl-DcaNXYhu.js +1 -0
  236. package/dist/view/assets/jsonnet-DFQXde-d.js +1 -0
  237. package/dist/view/assets/jssm-C2t-YnRu.js +1 -0
  238. package/dist/view/assets/jsx-g9-lgVsj.js +1 -0
  239. package/dist/view/assets/julia-5Bft2YPA.js +1 -0
  240. package/dist/view/assets/just-Cwhn7H3k.js +1 -0
  241. package/dist/view/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
  242. package/dist/view/assets/kanagawa-lotus-C3DzagqV.js +1 -0
  243. package/dist/view/assets/kanagawa-wave-DFGoQZhC.js +1 -0
  244. package/dist/view/assets/katex-BYZTj3zW.css +1 -0
  245. package/dist/view/assets/katex-C5jXJg4s.js +257 -0
  246. package/dist/view/assets/kdl-DV7GczEv.js +1 -0
  247. package/dist/view/assets/kotlin-BdnUsdx6.js +1 -0
  248. package/dist/view/assets/kusto-wEQ09or8.js +1 -0
  249. package/dist/view/assets/laserwave-DUszq2jm.js +1 -0
  250. package/dist/view/assets/latex-D5pSuvFb.js +1 -0
  251. package/dist/view/assets/lean-BZvkOJ9d.js +1 -0
  252. package/dist/view/assets/less-B1dDrJ26.js +1 -0
  253. package/dist/view/assets/light-plus-B7mTdjB0.js +1 -0
  254. package/dist/view/assets/liquid-C0sCDyMI.js +1 -0
  255. package/dist/view/assets/llvm-BZoOZj88.js +1 -0
  256. package/dist/view/assets/log-2UxHyX5q.js +1 -0
  257. package/dist/view/assets/logo-BtOb2qkB.js +1 -0
  258. package/dist/view/assets/lua-BaeVxFsk.js +1 -0
  259. package/dist/view/assets/luau-BnpPk5vE.js +1 -0
  260. package/dist/view/assets/make-CHLpvVh8.js +1 -0
  261. package/dist/view/assets/markdown-Cvjx9yec.js +1 -0
  262. package/dist/view/assets/marko-DjSrsDqO.js +1 -0
  263. package/dist/view/assets/material-theme-D5KoaKCx.js +1 -0
  264. package/dist/view/assets/material-theme-darker-BfHTSMKl.js +1 -0
  265. package/dist/view/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  266. package/dist/view/assets/material-theme-ocean-CyktbL80.js +1 -0
  267. package/dist/view/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  268. package/dist/view/assets/matlab-D7o27uSR.js +1 -0
  269. package/dist/view/assets/mdc-D1_yUvq7.js +1 -0
  270. package/dist/view/assets/mdx-Cmh6b_Ma.js +1 -0
  271. package/dist/view/assets/mermaid-CQcHuHx7.js +1 -0
  272. package/dist/view/assets/min-dark-CafNBF8u.js +1 -0
  273. package/dist/view/assets/min-light-CTRr51gU.js +1 -0
  274. package/dist/view/assets/mipsasm-CKIfxQSi.js +1 -0
  275. package/dist/view/assets/mojo-DJz3ZmWd.js +1 -0
  276. package/dist/view/assets/monokai-BRVnQi9A.js +1 -0
  277. package/dist/view/assets/moonbit-CHtswR0a.js +1 -0
  278. package/dist/view/assets/move-el3G9tDJ.js +1 -0
  279. package/dist/view/assets/narrat-DRg8JJMk.js +1 -0
  280. package/dist/view/assets/nextflow-C-mBbutL.js +1 -0
  281. package/dist/view/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  282. package/dist/view/assets/nginx-BpAMiNFr.js +1 -0
  283. package/dist/view/assets/night-owl-C39BiMTA.js +1 -0
  284. package/dist/view/assets/night-owl-light-CMTm3GFP.js +1 -0
  285. package/dist/view/assets/nim-BIad80T-.js +1 -0
  286. package/dist/view/assets/nix-CwoSXNpI.js +1 -0
  287. package/dist/view/assets/nord-Ddv68eIx.js +1 -0
  288. package/dist/view/assets/nsis-BlV79W_Q.js +1 -0
  289. package/dist/view/assets/nushell-D3jzshHO.js +1 -0
  290. package/dist/view/assets/objective-c-DXmwc3jG.js +1 -0
  291. package/dist/view/assets/objective-cpp-CLxacb5B.js +1 -0
  292. package/dist/view/assets/ocaml-C0hk2d4L.js +1 -0
  293. package/dist/view/assets/odin-BBf5iR-q.js +1 -0
  294. package/dist/view/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  295. package/dist/view/assets/one-light-C3Wv6jpd.js +1 -0
  296. package/dist/view/assets/openscad-C4EeE6gA.js +1 -0
  297. package/dist/view/assets/org-DM6o9KBp.js +1 -0
  298. package/dist/view/assets/pascal-D93ZcfNL.js +1 -0
  299. package/dist/view/assets/perl-B9cMNwum.js +1 -0
  300. package/dist/view/assets/php-Csjmro_R.js +1 -0
  301. package/dist/view/assets/pkl-u5AG7uiY.js +1 -0
  302. package/dist/view/assets/plastic-3e1v2bzS.js +1 -0
  303. package/dist/view/assets/plsql-ChMvpjG-.js +1 -0
  304. package/dist/view/assets/po-BTJTHyun.js +1 -0
  305. package/dist/view/assets/poimandres-CS3Unz2-.js +1 -0
  306. package/dist/view/assets/polar-C0HS_06l.js +1 -0
  307. package/dist/view/assets/postcss-CXtECtnM.js +1 -0
  308. package/dist/view/assets/powerquery-CEu0bR-o.js +1 -0
  309. package/dist/view/assets/powershell-BmBUJMz7.js +1 -0
  310. package/dist/view/assets/prisma-Vru482bI.js +1 -0
  311. package/dist/view/assets/prolog-CbFg5uaA.js +1 -0
  312. package/dist/view/assets/proto-C7zT0LnQ.js +1 -0
  313. package/dist/view/assets/pug-DKIMFp6K.js +1 -0
  314. package/dist/view/assets/puppet-BMWR74SV.js +1 -0
  315. package/dist/view/assets/purescript-CklMAg4u.js +1 -0
  316. package/dist/view/assets/python-B6aJPvgy.js +1 -0
  317. package/dist/view/assets/qml-3beO22l8.js +1 -0
  318. package/dist/view/assets/qmldir-C8lEn-DE.js +1 -0
  319. package/dist/view/assets/qss-IeuSbFQv.js +1 -0
  320. package/dist/view/assets/r-Cf5RLm7j.js +1 -0
  321. package/dist/view/assets/racket-BqYA7rlc.js +1 -0
  322. package/dist/view/assets/raku-DXvB9xmW.js +1 -0
  323. package/dist/view/assets/razor-BjBPvh-w.js +1 -0
  324. package/dist/view/assets/rbs-CpoqiR4B.js +1 -0
  325. package/dist/view/assets/red-hvxz__6c.js +1 -0
  326. package/dist/view/assets/reg-C-SQnVFl.js +1 -0
  327. package/dist/view/assets/regexp-CDVJQ6XC.js +1 -0
  328. package/dist/view/assets/rel-C3B-1QV4.js +1 -0
  329. package/dist/view/assets/riscv-BM1_JUlF.js +1 -0
  330. package/dist/view/assets/ron-D8l8udqQ.js +1 -0
  331. package/dist/view/assets/rose-pine-5qJOZa0Y.js +1 -0
  332. package/dist/view/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
  333. package/dist/view/assets/rose-pine-moon-C9pEdX9L.js +1 -0
  334. package/dist/view/assets/rosmsg-BJDFO7_C.js +1 -0
  335. package/dist/view/assets/rst-bs7f0vWN.js +1 -0
  336. package/dist/view/assets/ruby-C0TQ7zu5.js +1 -0
  337. package/dist/view/assets/rust-B1yitclQ.js +1 -0
  338. package/dist/view/assets/sas-DEy46yEz.js +1 -0
  339. package/dist/view/assets/sass-Cj5Yp3dK.js +1 -0
  340. package/dist/view/assets/scala-CqE71os6.js +1 -0
  341. package/dist/view/assets/scheme-C98Dy4si.js +1 -0
  342. package/dist/view/assets/scss-D5BDwBP9.js +1 -0
  343. package/dist/view/assets/sdbl-DVxCFoDh.js +1 -0
  344. package/dist/view/assets/shaderlab-Dg9Lc6iA.js +1 -0
  345. package/dist/view/assets/shellscript-Yzrsuije.js +1 -0
  346. package/dist/view/assets/shellsession-BADoaaVG.js +1 -0
  347. package/dist/view/assets/slack-dark-BthQWCQV.js +1 -0
  348. package/dist/view/assets/slack-ochin-DqwNpetd.js +1 -0
  349. package/dist/view/assets/smalltalk-BOQMe2GC.js +1 -0
  350. package/dist/view/assets/smithy-cds9vsN8.js +1 -0
  351. package/dist/view/assets/snazzy-light-Bw305WKR.js +1 -0
  352. package/dist/view/assets/solarized-dark-CpvCGNkr.js +1 -0
  353. package/dist/view/assets/solarized-light-Dlz6yCKv.js +1 -0
  354. package/dist/view/assets/solidity-DijEV5ha.js +1 -0
  355. package/dist/view/assets/soy-8wufbnw4.js +1 -0
  356. package/dist/view/assets/sparql-rVzFXLq3.js +1 -0
  357. package/dist/view/assets/splunk-BtCnVYZw.js +1 -0
  358. package/dist/view/assets/sql-CRqJ_cUM.js +1 -0
  359. package/dist/view/assets/ssh-config-_ykCGR6B.js +1 -0
  360. package/dist/view/assets/stata-DI20mbqo.js +1 -0
  361. package/dist/view/assets/stylus-BEDo0Tqx.js +1 -0
  362. package/dist/view/assets/surrealql-Cjom0U5J.js +1 -0
  363. package/dist/view/assets/svelte-Cy7k_4gC.js +1 -0
  364. package/dist/view/assets/swift-C2oV4EkX.js +1 -0
  365. package/dist/view/assets/synthwave-84-CbfX1IO0.js +1 -0
  366. package/dist/view/assets/system-verilog-0hqHdDBg.js +1 -0
  367. package/dist/view/assets/systemd-4A_iFExJ.js +1 -0
  368. package/dist/view/assets/talonscript-CkByrt1z.js +1 -0
  369. package/dist/view/assets/tasl-QIJgUcNo.js +1 -0
  370. package/dist/view/assets/tcl-dwOrl1Do.js +1 -0
  371. package/dist/view/assets/templ-DhtptRzy.js +1 -0
  372. package/dist/view/assets/terraform-BETggiCN.js +1 -0
  373. package/dist/view/assets/tex-D96PA37w.js +1 -0
  374. package/dist/view/assets/tokyo-night-hegEt444.js +1 -0
  375. package/dist/view/assets/toml-vGWfd6FD.js +1 -0
  376. package/dist/view/assets/ts-tags-D351s5mN.js +1 -0
  377. package/dist/view/assets/tsv-B_m7g4N7.js +1 -0
  378. package/dist/view/assets/tsx-COt5Ahok.js +1 -0
  379. package/dist/view/assets/turtle-BsS91CYL.js +1 -0
  380. package/dist/view/assets/twig-27uCiNez.js +1 -0
  381. package/dist/view/assets/typescript-BPQ3VLAy.js +1 -0
  382. package/dist/view/assets/typespec-CAFt9gP4.js +1 -0
  383. package/dist/view/assets/typst-BUadGCkm.js +1 -0
  384. package/dist/view/assets/v-BGw2Nkan.js +1 -0
  385. package/dist/view/assets/vala-CsfeWuGM.js +1 -0
  386. package/dist/view/assets/vb-Cu-pLBUe.js +1 -0
  387. package/dist/view/assets/verilog-nZwndyjY.js +1 -0
  388. package/dist/view/assets/vesper-DRje8inN.js +1 -0
  389. package/dist/view/assets/vhdl-CeAyd5Ju.js +1 -0
  390. package/dist/view/assets/viml-CJc9bBzg.js +1 -0
  391. package/dist/view/assets/vitesse-black-Bkuqu6BP.js +1 -0
  392. package/dist/view/assets/vitesse-dark-D0r3Knsf.js +1 -0
  393. package/dist/view/assets/vitesse-light-CVO1_9PV.js +1 -0
  394. package/dist/view/assets/vue-BqiEGhQt.js +1 -0
  395. package/dist/view/assets/vue-html-AaS7Mt5G.js +1 -0
  396. package/dist/view/assets/vue-vine-BoDAl6tE.js +1 -0
  397. package/dist/view/assets/vyper-CDx5xZoG.js +1 -0
  398. package/dist/view/assets/wasm-CG6Dc4jp.js +1 -0
  399. package/dist/view/assets/wasm-MzD3tlZU.js +1 -0
  400. package/dist/view/assets/wenyan-BV7otONQ.js +1 -0
  401. package/dist/view/assets/wgsl-Dx-B1_4e.js +1 -0
  402. package/dist/view/assets/wikitext-BhOHFoWU.js +1 -0
  403. package/dist/view/assets/wit-5i3qLPDT.js +1 -0
  404. package/dist/view/assets/wolfram-lXgVvXCa.js +1 -0
  405. package/dist/view/assets/xml-sdJ4AIDG.js +1 -0
  406. package/dist/view/assets/xsl-CtQFsRM5.js +1 -0
  407. package/dist/view/assets/yaml-Buea-lGh.js +1 -0
  408. package/dist/view/assets/zenscript-DVFEvuxE.js +1 -0
  409. package/dist/view/assets/zig-VOosw3JB.js +1 -0
  410. package/dist/view/index.html +10 -2
  411. package/docs/README.md +21 -15
  412. package/docs/README.zh.md +15 -9
  413. package/docs/en/authoring.md +85 -80
  414. package/docs/en/cli-reference.md +110 -72
  415. package/docs/en/deployment.md +183 -0
  416. package/docs/en/getting-started.md +112 -0
  417. package/docs/en/material-types.md +241 -0
  418. package/docs/en/settings.md +133 -0
  419. package/docs/en/shortcuts.md +89 -0
  420. package/docs/en/typing-rules.md +208 -0
  421. package/docs/zh/authoring.md +74 -69
  422. package/docs/zh/cli-reference.md +92 -58
  423. package/docs/zh/deployment.md +179 -0
  424. package/docs/zh/getting-started.md +104 -0
  425. package/docs/zh/material-types.md +769 -0
  426. package/docs/zh/settings.md +120 -0
  427. package/docs/zh/shortcuts.md +83 -0
  428. package/docs/zh/typing-rules.md +191 -0
  429. package/package.json +13 -11
  430. package/templates/en/QTYPE.md +155 -495
  431. package/templates/en/README.md +11 -11
  432. package/templates/en/TODO.md +1 -1
  433. package/templates/en/material/article.md +20 -0
  434. package/templates/en/material/assets/article/sunrise.svg +12 -0
  435. package/templates/en/material/assets/code/tree.svg +19 -0
  436. package/templates/en/material/assets/qa/login-flow.svg +17 -0
  437. package/templates/en/material/assets/sentence/pangram.svg +6 -0
  438. package/templates/en/material/assets/snippet/moon.svg +8 -0
  439. package/templates/en/material/assets/word/apple.svg +5 -0
  440. package/templates/en/material/code.md +55 -0
  441. package/templates/en/material/qa.md +33 -0
  442. package/templates/en/material/sentence.md +18 -0
  443. package/templates/en/material/snippet.md +17 -0
  444. package/templates/en/material/word.md +20 -0
  445. package/templates/zh/QTYPE.md +191 -473
  446. package/templates/zh/README.md +6 -6
  447. package/templates/zh/index.md +1 -1
  448. package/templates/zh/material/article.md +18 -0
  449. package/templates/zh/material/assets/article/sunrise.svg +12 -0
  450. package/templates/zh/material/assets/code/tree.svg +19 -0
  451. package/templates/zh/material/assets/qa/login-flow.svg +17 -0
  452. package/templates/zh/material/assets/sentence/pangram.svg +6 -0
  453. package/templates/zh/material/assets/snippet/moon.svg +8 -0
  454. package/templates/zh/material/assets/word/apple.svg +5 -0
  455. package/templates/zh/material/code.md +55 -0
  456. package/templates/zh/material/qa.md +33 -0
  457. package/templates/zh/material/sentence.md +19 -0
  458. package/templates/zh/material/snippet.md +20 -0
  459. package/templates/zh/material/word.md +20 -0
  460. package/dist/cli/commands/check/action.js +0 -56
  461. package/dist/cli/commands/check/i18n.js +0 -26
  462. package/dist/cli/commands/plugin/i18n.js +0 -28
  463. package/dist/cli/commands/plugin/index.js +0 -41
  464. package/dist/core/typing/engine.js +0 -112
  465. package/dist/view/assets/index-DBABH3dG.js +0 -111
  466. package/dist/view/assets/index-RTLZ-zOC.css +0 -1
  467. package/templates/en/material/example.md +0 -47
  468. package/templates/zh/material/example.md +0 -47
@@ -0,0 +1,769 @@
1
+ # 资料类型详解
2
+
3
+ qtype 支持 6 种资料类型 —— `word` / `sentence` / `article` / `snippet` / `qa` / `code`。每种类型有自己的 Markdown 结构、解析规则、渲染器和交互布局。本文档从**文件格式** → **解析器行为** → **UI 布局** → **交互细节**四个层面逐一讲清楚。
4
+
5
+ ---
6
+
7
+ ## 目录
8
+
9
+ - [公共约定](#公共约定)
10
+ - [word — 单词卡](#word--单词卡)
11
+ - [sentence — 句子卡](#sentence--句子卡)
12
+ - [article — 篇章 (长篇)](#article--篇章-长篇)
13
+ - [snippet — 短章 (诗句/格言)](#snippet--短章-诗句格言)
14
+ - [qa — 问答](#qa--问答)
15
+ - [code — 代码题](#code--代码题)
16
+ - [调度与兜底](#调度与兜底)
17
+
18
+ ---
19
+
20
+ ## 公共约定
21
+
22
+ ### 标题(H1)显示规则
23
+
24
+ 所有类型的卡片都以 H1 作为 title 显示在卡片顶部。统一处理:
25
+
26
+ - **word / sentence**:H1 本身就是打字目标,居中大字号
27
+ - **snippet**:H1 是作品名,小字居中,muted 色
28
+ - **article / qa / code**:H1 是章节名/题目名,居中,**最多显示 2 行**,超出用省略号,鼠标 hover 显示完整标题(浏览器原生 `title` tooltip)
29
+
30
+ ### 文件顶部 frontmatter
31
+
32
+ 每个 `.md` 文件顶部有 YAML frontmatter,声明整个文件属于哪种类型:
33
+
34
+ ```yaml
35
+ ---
36
+ type: word # 5 种之一,决定整个文件的卡片形状
37
+ title: 高频词 500 # 可选,资料展示名
38
+ tags: [english, cet4]
39
+ created: 2026-01-15
40
+ updated: 2026-08-20
41
+ ---
42
+ ```
43
+
44
+ - **`type` 决定整个文件**:一个 `.md` 只允许一种 type,所有卡片形状相同
45
+ - **缺 `type` 时默认按 `article` 解析**(向后兼容旧文件)
46
+ - 未知的 `type` 值(比如 `type: 自定义`)也会 fallback 到 `article` 解析
47
+
48
+ ### 卡片切分
49
+
50
+ frontmatter 之后的正文按 **H1**(`# ...`)切分卡片:
51
+
52
+ - 每个 H1 起一张新卡
53
+ - H1 之前的散文/说明会被**丢弃**(材料级别的前言,渲染器看不到)
54
+ - 每个 chunk 包含它自己的 H1 行 + 之后所有行,直到下一个 H1 或文件结尾
55
+
56
+ ### 打字文本的规范化
57
+
58
+ 不管什么类型,最终作为"打字目标"的字符串会走 `validateTypingText`:
59
+
60
+ - 智能引号 `“ ” ‘ ’` → 直引号 `" '`
61
+ - 破折号 `— –` → 常规连字符 `-`
62
+ - Emoji / 零宽字符被剥离
63
+ - 无法映射到键盘的字符会收进 `unknownChars`,UI 会在卡片上方红字提示"该卡不可玩"
64
+
65
+ ### 每张卡的通用字段(runtime)
66
+
67
+ 解析完的 `Card` 对象都有:
68
+
69
+ ```ts
70
+ {
71
+ id: string // 形如 "path/to/file.md#3"
72
+ title: string // 显示用的标题(不一定是打字目标)
73
+ answer: string // 实际打字目标(已规范化)
74
+ charPlans: CharPlan[] // 每字符的按键计划(用于键盘可视化)
75
+ unknownChars: string[] // 打不出来的字符,非空则卡片不可玩
76
+ // ↓ 特定类型才有:
77
+ phonetic?: string // word / sentence 的音标
78
+ gloss?: string // word / sentence 的释义
79
+ image?: string // word / sentence 的单张配图 markdown
80
+ question?: string // article / qa / code 的题干原文
81
+ }
82
+ ```
83
+
84
+ ### 富渲染(展示侧,不参与打字)
85
+
86
+ **非打字的展示位置**(题干 `## Q`、article/snippet 正文、word/sentence 的 gloss)会经过一个轻量 markdown 渲染器,支持以下富内容:
87
+
88
+ | 特性 | 语法 | 说明 |
89
+ |---|---|---|
90
+ | **粗体 / 斜体 / 行内代码** | `**text**` / `*text*` / `` `code` `` | 标准 markdown |
91
+ | **代码块** | ` ```lang ` fence | `code` 类型的答案侧走另一条路径(参见 code 类型章节);其他位置只做等宽渲染 |
92
+ | **Mermaid 流程图** | ` ```mermaid ` fence | `publish build` 时**预渲染成 SVG**(dark + light 两版),浏览器只查表换 SVG,零 runtime 开销 |
93
+ | **数学公式(LaTeX)** | 行内 `$...$`,块级 `$$...$$` | 用 **KaTeX** 懒加载渲染(~280KB),仅在有公式的页面加载。可在设置里关闭 |
94
+
95
+ **只在展示侧渲染,不参与打字**。用户看到的是漂亮的公式或流程图,敲的还是键盘能打得出来的字符。举例:
96
+
97
+ ```md
98
+ ---
99
+ type: qa
100
+ ---
101
+
102
+ # 高斯积分
103
+
104
+ ## Q
105
+ 求下面这个积分的值:
106
+
107
+ $$\int_{-\infty}^{\infty} e^{-x^2}\,dx = ?$$
108
+
109
+ ## A
110
+ sqrt(pi)
111
+ ```
112
+
113
+ 题干里的 `$$...$$` 用 KaTeX 渲染成一行漂亮公式;`## A` 里 `sqrt(pi)` 就是键盘能打的普通字符,这是用户实际要敲的内容。
114
+
115
+ ### 通用交互(所有类型共享)
116
+
117
+ 所有卡片核心都是 `<TypingArena>`,行为一致:
118
+
119
+ - 键入正确字符 → 光标前进,该字符标绿
120
+ - 键入错误字符 → 该字符标红,光标不前进(除非开了 `ignorePunctuation` 之类的规则)
121
+ - Backspace 回退(除非 `allowBackspace: false`)
122
+ - 打完最后一个字符 → 触发 `onFinish` → 路由决定是否 gate 通过,过则进下一张
123
+ - **暂停**由 4 个显式触发点控制:HUD ⏸ / Alt+Space / AFK 达阈值 / 窗口失焦或切 tab
124
+ - 暂停时 arena 覆盖一层遮罩,提示当前快捷键
125
+
126
+ ---
127
+
128
+ ## word — 单词卡
129
+
130
+ ### 用途
131
+
132
+ 单词记忆,类似 qwerty-learner 的核心场景。适合背四六级、雅思、编程英语词汇。
133
+
134
+ ### Markdown 格式
135
+
136
+ ```md
137
+ ---
138
+ type: word
139
+ title: 常用单词
140
+ tags: [english]
141
+ ---
142
+
143
+ # apple
144
+ ## /ˈæp.əl/
145
+ ### 苹果
146
+ #### ![](https://example.com/apple.jpg)
147
+
148
+ # abandon
149
+ ## /əˈbæn.dən/
150
+ ### 抛弃; 放弃
151
+
152
+ # code
153
+ ## /koʊd/
154
+ ### 代码
155
+ ```
156
+
157
+ **H 层级映射**:
158
+
159
+ | Heading | 含义 | 是否可选 |
160
+ |---|---|---|
161
+ | `# 目标词` | H1 就是要打的单词本身 | 必需 |
162
+ | `## 音标/拼音` | 中文可用拼音,如 `## dài mǎ` | 可选 |
163
+ | `### 释义` | 中英释义,单行 | 可选 |
164
+ | `#### ![](url)` | 单张配图,只识别第一张 | 可选 |
165
+
166
+ **注意**:所有 H2/H3/H4 只取**同一行**内容,多行会被忽略。
167
+
168
+ ### 解析器行为(`buildAnnotatedCard`)
169
+
170
+ 1. 提取 H1 文本 → `title = answer`(同一个字符串既是显示标题,也是打字目标)
171
+ 2. 用 regex `^##\s+(.*)$` 从 body 里抽首个 H2 → `phonetic`
172
+ 3. 同样抽 H3 → `gloss`,H4 → `image`(保留原始 markdown 如 `![alt](url)`)
173
+ 4. `charPlans` 按 `answer` 生成
174
+ 5. answer 空字符串或全是不可打字符 → 该卡片被丢弃,不进 material.cards
175
+
176
+ ### UI 布局(`WordCard.tsx`)
177
+
178
+ ```
179
+ ┌────────────────────────────────────┐
180
+ │ │
181
+ │ apple 🔊 │ ← 目标词大字(3rem, weight 500,
182
+ │ ↑ 居中 ↑ 绝对定位 │ letter-spacing 0.05em)
183
+ │ │
184
+ │ /ˈæp.əl/ │ ← 音标(等宽字体,muted 颜色)
185
+ │ 苹果 │ ← 释义
186
+ │ ┌──────┐ │
187
+ │ │ img │ │ ← 配图(max-width 320px)
188
+ │ └──────┘ │
189
+ │ │
190
+ └────────────────────────────────────┘
191
+ ```
192
+
193
+ **关键 CSS 技巧**:发音按钮**不参与 flex 布局**,而是 `position: absolute; left: 100%; top: 50%; translateY(-50%); margin-left: 8px`。这样目标词无论多长,都保持整体居中,按钮悬浮在词的右侧 —— 与 qwerty-learner 同源。
194
+
195
+ ### 交互细节
196
+
197
+ - **自动朗读**:切换到新卡片且 `paused=false` 时,调用 `speak()` 播放 `card.answer`。有 `spokenForRef` 去重,同一张卡不会重复朗读
198
+ - **朗读次数**:受 `autoSpeakRepeat` 控制(1/2/3),内部通过 `.ended` 事件链式重播
199
+ - **朗读音量/语速**:受 `ttsVolume` / `ttsRate` 控制
200
+ - **音标/释义可见性**:`showPhonetic` / `showGloss` 两个开关,可在 HUD More 菜单里切换,也可在设置页里改默认值
201
+ - **发音钮**:点击也能手动朗读,与 auto-speak 用同一个 provider(网易/Google/浏览器)
202
+
203
+ > **朗读与音标解耦**:朗读只依赖 `card.answer` 和 TTS provider。**卡片没有音标 (H2 缺省) 也照样有发音按钮和自动朗读** —— 音标是给眼睛看的辅助信息,朗读是给耳朵听的独立通道,两者互不依赖。
204
+
205
+ ### 邻卡预览(NeighborPreview)
206
+
207
+ 左右两侧显示上一张/下一张卡片时:
208
+
209
+ - **title**:目标词(`card.answer`)
210
+ - **subtitle**:释义(`card.gloss`,可选)
211
+ - 不显示音标(单词卡的音标属于当前卡片视图,不打扰预览)
212
+ - 不显示配图(预览要小要一眼看到,图太占地方)
213
+
214
+ ---
215
+
216
+ ## sentence — 句子卡
217
+
218
+ ### 用途
219
+
220
+ 整句练习,适合背例句、口语句型、TOEFL/IELTS 常用句、日语例文。
221
+
222
+ ### Markdown 格式
223
+
224
+ 结构与 word **完全相同**,只是 H1 通常更长:
225
+
226
+ ```md
227
+ ---
228
+ type: sentence
229
+ title: 日常表达
230
+ ---
231
+
232
+ # I'm going home.
233
+ ## /aɪm ˈɡoʊ.ɪŋ hoʊm/
234
+ ### 我要回家了。
235
+
236
+ # The quick brown fox jumps over the lazy dog.
237
+ ## /ðə kwɪk braʊn fɑks dʒʌmps ˈoʊvɚ ðə ˈleɪ.zi dɔɡ/
238
+ ### 敏捷的棕狐狸跳过懒狗。
239
+ ```
240
+
241
+ ### 解析器行为
242
+
243
+ **与 word 共用 `buildAnnotatedCard`**,一样的字段结构。区别只在渲染。
244
+
245
+ ### UI 布局(`SentenceCard.tsx`)
246
+
247
+ ```
248
+ ┌───────────────────────────────────────────────────┐
249
+ │ │
250
+ │ I'm going home. 🔊 │ ← 目标句(1.6rem, line-height 1.6)
251
+ │ ↑ 整体水平居中,发音钮悬浮右侧 │ text-align: center
252
+ │ │
253
+ │ /aɪm ˈɡoʊ.ɪŋ hoʊm/ │ ← 音标(0.95rem, muted)
254
+ │ 我要回家了。 │ ← 翻译
255
+ │ ┌────────────┐ │
256
+ │ │ image │ │ ← 配图(max-width 400px)
257
+ │ └────────────┘ │
258
+ └───────────────────────────────────────────────────┘
259
+ ```
260
+
261
+ **与 word 的差别**:
262
+
263
+ - 字号更小(1.6rem vs 3rem),因为句子多字符
264
+ - max-width 更宽(900px vs 720px)
265
+ - 图片更大(400px vs 320px)
266
+ - 发音钮同款绝对定位
267
+ - **arena 里的文字 `text-align: center`**(word 只有一个词天然居中,不需要;sentence 多字符必须显式居中,否则会靠左)
268
+
269
+ > **朗读与音标解耦**:与 word 相同,朗读只看 `card.answer`。**没有音标的句子照样能听发音** —— 尤其是外语听写练习场景,音标很少给出,但朗读必须有。
270
+
271
+ ### 邻卡预览(NeighborPreview)
272
+
273
+ - **title**:整句(`card.answer`)
274
+ - **subtitle**:翻译(`card.gloss`,可选)
275
+ - 不显示音标 / 图片
276
+
277
+ ---
278
+
279
+ ## article — 篇章 (长篇)
280
+
281
+ ### 用途
282
+
283
+ 段落/文章练习,适合打新闻、散文、书摘、多段说明文。
284
+
285
+ ### Markdown 格式
286
+
287
+ ```md
288
+ ---
289
+ type: article
290
+ title: 精读段落
291
+ ---
292
+
293
+ # Autumn Morning
294
+
295
+ The mist hung low over the valley, softening every edge.
296
+ Somewhere below, a river murmured its ancient tune.
297
+ Yellow leaves drifted down without a sound.
298
+
299
+ ![秋日晨景](https://example.com/autumn.jpg)
300
+
301
+ # Winter Silence
302
+
303
+ Snow had erased the road overnight...
304
+ ```
305
+
306
+ **层级映射**:
307
+
308
+ | Heading / 内容 | 含义 |
309
+ |---|---|
310
+ | `# 章节标题` | 只是显示标题,**不参与打字** |
311
+ | H1 下的所有正文 | 全部作为打字目标(图片会被剥离) |
312
+ | 正文里的图片 | 保留在 `card.question`,渲染在 arena 上方作为视觉辅助 |
313
+
314
+ ### 解析器行为(`buildArticleCard`)
315
+
316
+ 1. `title` = H1 文本
317
+ 2. 从 H1 下的 body 里**去掉所有 `![alt](url)` 图片语法** → 得到 `rawTarget`
318
+ 3. 规范化(智能引号、破折号、剥离 emoji) → `answer`(打字目标)
319
+ 4. `question` = **原始未处理的 body**(含图片语法),这样渲染器可以只挑出图片显示,不重复渲染文字
320
+
321
+ ### UI 布局(`ArticleCard.tsx`)
322
+
323
+ ```
324
+ ┌────────────────────────────────────────────────┐
325
+ │ Autumn Morning │ ← 标题(1.3rem, 600, 居中)
326
+ │ │
327
+ │ [autumn.jpg] ← 只显示图片 │
328
+ │ │
329
+ │ ┌──────────────────────────────────────────┐ │
330
+ │ │ The mist hung low over the valley, │ │
331
+ │ │ softening every edge. Somewhere below, │ │ ← arena
332
+ │ │ a river murmured its ancient tune. │ │ (1.15rem, line-height 1.7, 靠左)
333
+ │ │ Yellow leaves drifted down without a... │ │
334
+ │ └──────────────────────────────────────────┘ │
335
+ └────────────────────────────────────────────────┘
336
+ ```
337
+
338
+ **标题居中 + 正文靠左**是主流文档排版(书籍 / 论文 / Wikipedia / Medium)。
339
+
340
+ **渲染细节**:
341
+
342
+ - 用了自定义 `imageOnly()` 函数,把 body 里非图片的行全部丢弃,只留 `![](url)` 那些行
343
+ - 这样图片在文字上方 render,而**同样的文字不会被渲染两次**(一次作 md,一次作打字目标)
344
+ - container 是 `flex-column` + `overflow-y: auto`,长文能滚动
345
+ - arena 里的文字用 `white-space` 默认(不会 pre-wrap,段落之间的空行会折叠)
346
+
347
+ ### 交互细节
348
+
349
+ - **无自动朗读**(段落太长,朗读体验差)
350
+ - **无音标/释义**(不适用)
351
+ - **发音钮**?没有 —— 长段落不需要,如果想听某句话可以复制到别处
352
+ - 段落之间用**空行**分隔(md 标准),打字时空行会体现为 arena 里的换行
353
+
354
+ ### 是否居中?
355
+
356
+ **不居中,靠左**。这是刻意的设计:
357
+
358
+ - 段落文字居中反而破坏阅读流(主流打字应用 monkeytype / typing.io / typeracer 全部靠左)
359
+ - 阅读长文本时,人眼靠"稳定的左边界"来快速定位下一行
360
+ - CSS 上 `.qt-article` 是 `flex-direction: column` + 无 `align-items`,arena 撑满宽度,文字自然靠左
361
+
362
+ ### 邻卡预览
363
+
364
+ - **title**:章节标题(`card.title`)
365
+ - **无 subtitle**
366
+ - 不预览正文(太长)
367
+
368
+ ### article vs snippet:怎么选?
369
+
370
+ 同样的 md 结构可以用 `article` 或 `snippet`。判断标准就一句话:
371
+
372
+ - 内容像**文章** → `article`(小说段、新闻、说明文、书摘、多段散文)
373
+ - 内容像**作品** → `snippet`(古诗、词、俳句、格言、slogan、菜单、清单、对联)
374
+
375
+ 技术上两者**解析完全相同**(共用 `buildArticleCard`),差别只在渲染器 —— `article` 靠左阅读,`snippet` 居中呈现。参见下一节。
376
+
377
+ ---
378
+
379
+ ## snippet — 短章 (诗句/格言)
380
+
381
+ ### 用途
382
+
383
+ 古诗词、俳句、格言警句、slogan、清单、对联、菜单 —— 任何**短小、有节奏、值得视觉性呈现**的文本。
384
+
385
+ `article` 和 `snippet` 数据结构完全一样,分开只为渲染时对齐方式不同:`article` 靠左适合阅读长文,`snippet` 居中适合视觉呈现短文。
386
+
387
+ ### Markdown 格式
388
+
389
+ ```md
390
+ ---
391
+ type: snippet
392
+ title: 唐诗三百首
393
+ tags: [poetry, tang]
394
+ ---
395
+
396
+ # 静夜思
397
+
398
+ 床前明月光,
399
+ 疑是地上霜。
400
+ 举头望明月,
401
+ 低头思故乡。
402
+
403
+ # 春晓
404
+
405
+ 春眠不觉晓,
406
+ 处处闻啼鸟。
407
+ 夜来风雨声,
408
+ 花落知多少。
409
+ ```
410
+
411
+ **层级映射**:与 `article` **完全相同** ——
412
+
413
+ | Heading / 内容 | 含义 |
414
+ |---|---|
415
+ | `# 章节/作品标题` | 显示标题,**不参与打字** |
416
+ | H1 下的所有正文 | 全部作为打字目标(图片剥离) |
417
+
418
+ ### 解析器行为
419
+
420
+ **沿用 `buildArticleCard`**,与 article 完全一样的字段结构。切分类型只影响渲染。
421
+
422
+ ### UI 布局(`SnippetCard.tsx`)
423
+
424
+ ```
425
+ ┌────────────────────────────────────────────┐
426
+ │ │
427
+ │ │
428
+ │ 静夜思 │ ← 标题(1.15rem, muted)
429
+ │ ↑ 居中 │ letter-spacing 0.05em
430
+ │ │
431
+ │ 床前明月光, │
432
+ │ 疑是地上霜。 │ ← arena(1.4rem, line-height 2)
433
+ │ 举头望明月, │ text-align: center
434
+ │ 低头思故乡。 │
435
+ │ │
436
+ │ │
437
+ └────────────────────────────────────────────┘
438
+ ```
439
+
440
+ **关键 CSS 差异(对比 article)**:
441
+
442
+ | 维度 | article | snippet |
443
+ |---|---|---|
444
+ | container | `flex-column` 从上排,无居中 | `align-items: center; justify-content: center` 整体上下水平都居中 |
445
+ | 标题 | 1.3rem, 深色, 靠左 | 1.15rem, muted, 居中, 字母间距略宽 |
446
+ | arena 文字对齐 | 默认(靠左) | `text-align: center` |
447
+ | 字号 | 1.15rem | 1.4rem (略大,配合诗歌节奏) |
448
+ | 行距 | 1.7 | 2.0 (更宽,格律感) |
449
+
450
+ ### 交互细节
451
+
452
+ - 无自动朗读、无音标、无发音钮(承袭 article)
453
+ - 每行结尾的**标点必须打**(`,` `。` `;` 等)—— 打字目标里保留了这些字符
454
+ - **换行**要打回车吗?不用 —— 打字目标里换行是文本的一部分,arena 会在光标碰到换行时自动前进,不需要按 Enter
455
+ - 空行(诗节之间)同理,arena 自动跳过
456
+
457
+ ### 是否居中?
458
+
459
+ **必须居中**。这是 snippet 存在的**唯一理由** —— 如果不需要居中,就该用 article。
460
+
461
+ ### 邻卡预览
462
+
463
+ - **title**:作品标题(`card.title`,如"静夜思")
464
+ - **无 subtitle**
465
+ - 不预览正文
466
+
467
+ ---
468
+
469
+ ## qa — 问答
470
+
471
+ ### 用途
472
+
473
+ 题目 + 解答的形式,适合面试题、知识点问答、外语听力 dictation。
474
+
475
+ ### Markdown 格式
476
+
477
+ ```md
478
+ ---
479
+ type: qa
480
+ title: 常见面试题
481
+ ---
482
+
483
+ # 什么是闭包?
484
+
485
+ ## Q
486
+ 请解释 JavaScript 中的闭包,以及它的常见应用场景。
487
+
488
+ ## A
489
+ 闭包是指有权访问另一个函数作用域中变量的函数。
490
+ 常见用途:
491
+ - 数据私有化
492
+ - 函数柯里化
493
+ - 回调函数保持状态
494
+
495
+ # HTTP 和 HTTPS 的区别
496
+
497
+ ## Q
498
+ 从**协议层面**说说 HTTP 和 HTTPS 的区别。
499
+
500
+ 参考图:
501
+ ![握手示意](https://example.com/tls.png)
502
+
503
+ ## A
504
+ HTTPS 在 HTTP 之上加了 TLS/SSL 加密层...
505
+ ```
506
+
507
+ **层级映射**:
508
+
509
+ | 部分 | 含义 |
510
+ |---|---|
511
+ | `# 卡片标题` | 显示标题,不参与打字 |
512
+ | `## Q` 之后到 `## A` 之前 | 题干,支持任意 markdown(列表、图片、代码块、粗体等) |
513
+ | `## A` 之后到卡片结尾 | 打字目标(**图片被剥离**,其他 markdown 语法**保留原文**) |
514
+
515
+ ### 解析器行为(`buildQACard`)
516
+
517
+ 1. 用 regex `##\s*Q\b\s*\n([\s\S]*?)(?=\n##\s*A\b|$)` 抓 Q 块
518
+ 2. 用 regex `##\s*A\b\s*\n([\s\S]*)$` 抓 A 块
519
+ 3. **A 块必须存在**,否则该卡片被丢弃(returns null)
520
+ 4. Q 块可选,不存在则 `question = ''`
521
+ 5. A 块的原始文本 → `extractTypingText` → 剥离图片语法 + 剥离 fenced code 的 ``` 边界(留下代码内容)
522
+ 6. 规范化 → `answer`
523
+
524
+ **注意**:A 里如果有 ``` fenced code,**只剥离前后的 ``` 标记**,里面的代码保留(因为要打)。这也是 code 类型能沿用这套解析器的原因。
525
+
526
+ ### UI 布局(`QaCard.tsx`)
527
+
528
+ ```
529
+ ┌────────────────────────────────────────────────┐
530
+ │ 什么是闭包? │ ← 标题(1.3rem, 600, 居中)
531
+ │ │
532
+ │ 请解释 JavaScript 中的闭包,以及它的常见应用场景。 │ ← Q(muted, 0.95rem)
533
+ │ │ ← 用 MdRenderer 完整渲染
534
+ │ │ (列表/图片/代码都会显示)
535
+ │ ┌──────────────────────────────────────────┐ │
536
+ │ │ 闭包是指有权访问另一个函数作用域中变量的... │ │
537
+ │ │ 常见用途: │ ← arena(1.1rem, line-height 1.7, 靠左)
538
+ │ │ - 数据私有化 │ │ A 的**原始文本**(含 markdown 语法)
539
+ │ │ - 函数柯里化 │ │
540
+ │ │ - 回调函数保持状态 │ │
541
+ │ └──────────────────────────────────────────┘ │
542
+ └────────────────────────────────────────────────┘
543
+ ```
544
+
545
+ **关键点**:
546
+
547
+ - **Q 是"给人看的"**,所以用 `<MdRenderer>` 完整渲染:粗体变粗体,列表变项目符号,图片显示,代码块高亮
548
+ - **A 是"给人打的"**,arena 里的是**原始 md 文本**。用户会把 `- ` `**` 反引号等符号一字一字打出来
549
+ - 这不是 bug —— 是设计目标:让用户练打那些平时打不好的符号
550
+ - container `flex-column` + `overflow-y: auto`,题干长了能滚
551
+
552
+ ### 交互细节
553
+
554
+ - **无自动朗读**(问答场景不朗读)
555
+ - **无音标/释义/图片专门渲染**(图片直接在 Q 里显示)
556
+ - 打字目标里的换行、缩进、特殊符号都要**逐字符打准**
557
+
558
+ ### 邻卡预览
559
+
560
+ - **title**:卡片标题(`card.title`,也就是 H1)
561
+ - **无 subtitle**
562
+ - 不预览 Q(问答的 Q 通常包含 md 格式,预览里不好看,而且 preview 应该"扫一眼",不该读完整段问题)
563
+
564
+ ---
565
+
566
+ ## code — 代码题
567
+
568
+ ### 用途
569
+
570
+ 编程题练习,LeetCode / 常见算法题 / API 用法 / 语法背诵。
571
+
572
+ ### Markdown 格式
573
+
574
+ **结构与 qa 完全一致**(共用 `buildQACard`),但约定 A 里放 fenced code block:
575
+
576
+ ````md
577
+ ---
578
+ type: code
579
+ title: 常见算法
580
+ tags: [algorithm, javascript]
581
+ ---
582
+
583
+ # Two Sum
584
+
585
+ ## Q
586
+ 给一个数组 `nums` 和一个目标值 `target`,返回两个下标使得两数之和等于 target。
587
+
588
+ **约束**:同一元素不能用两次,答案唯一。
589
+
590
+ ## A
591
+ ```ts
592
+ function twoSum(nums: number[], target: number): number[] {
593
+ const map = new Map<number, number>()
594
+ for (let i = 0; i < nums.length; i++) {
595
+ const rest = target - nums[i]
596
+ if (map.has(rest)) return [map.get(rest)!, i]
597
+ map.set(nums[i], i)
598
+ }
599
+ return []
600
+ }
601
+ ```
602
+
603
+ # Reverse Linked List
604
+
605
+ ## Q
606
+ 反转一个单链表,返回新头结点。
607
+
608
+ ## A
609
+ ```python
610
+ def reverse(head):
611
+ prev = None
612
+ while head:
613
+ next_node = head.next
614
+ head.next = prev
615
+ prev = head
616
+ head = next_node
617
+ return prev
618
+ ```
619
+ ````
620
+
621
+ ### 解析器行为
622
+
623
+ **沿用 `buildQACard`**,与 qa 完全相同。差别只在渲染端:
624
+
625
+ - `card.answer` = **代码本身**(``` 边界已剥离,代码内容原封不动)
626
+ - `card.question` = Q 块的完整 markdown
627
+
628
+ ### 语言识别(`CodeCard.tsx`)
629
+
630
+ 语言用于 Shiki 语法高亮(**不显示在 UI 上**),识别顺序:
631
+
632
+ 1. 首先在 `card.question` 里搜 `` ```lang `` fence 标签(比如 `` ```ts ``、`` ```python ``)—— 找到就用它
633
+ 2. 找不到就从文件路径猜(`typescript.md` → typescript,`python.md` → python,含 `cpp/c++` → cpp,等等)
634
+ 3. 都不中就默认 `javascript`
635
+
636
+ > **不显示语言 tag 的原因**:作者已经把语言写进了 material 的 frontmatter `tags`(如 `tags: [algorithm, typescript]`),资料列表页会展示。卡片视图再显示一次是冗余,而且抢标题的视觉焦点。语言只用于代码染色,靠 Shiki 输出的颜色自然就知道是啥语言。
637
+
638
+ ### 语法高亮
639
+
640
+ 用 [Shiki](https://shiki.style/) 做 tokenize:
641
+
642
+ 1. `useShiki(lang)` 懒加载对应语言的语法包
643
+ 2. 主题跟随全局:`data-theme` 含 `latte` → `github-light`,否则 `github-dark`
644
+ 3. `codeToTokens` 遍历返回的 token,把每个字符对应的**颜色**塞进 `charColors: (string | null)[]`
645
+ 4. `TypingArena` 接受 `charColors` 参数,给未打字符添加 `color: <shiki color>` 的行内 style
646
+ 5. 打对/打错的 CSS 类通过 specificity 覆盖 shiki 颜色 —— 打对了变绿,打错了变红
647
+
648
+ ### UI 布局
649
+
650
+ ```
651
+ ┌────────────────────────────────────────────────────────┐
652
+ │ Two Sum │ ← 标题(1.15rem, 600, 居中, ≤ 2 行)
653
+ │ │
654
+ │ 给一个数组 nums 和一个目标值 target,返回两个下标使得... │ ← Q(muted, 完整 md 渲染)
655
+ │ │ max-height 30vh,超出滚动
656
+ │ │
657
+ │ ┌──────────────────────────────────────────────────┐ │
658
+ │ │ function twoSum(nums: number[], ...) { │ │
659
+ │ │ const map = new Map<number, number>() │ │ ← arena
660
+ │ │ for (let i = 0; i < nums.length; i++) { │ │ 等宽字体
661
+ │ │ ... │ │ white-space: pre-wrap
662
+ │ │ } │ │ text-align: left
663
+ │ │ } │ │ 带 shiki 颜色
664
+ │ └──────────────────────────────────────────────────┘ │
665
+ └────────────────────────────────────────────────────────┘
666
+ ```
667
+
668
+ **关键 CSS 差异**:
669
+
670
+ - 整体 `font-family: var(--font-mono)` 等宽
671
+ - arena 的文字 `white-space: pre-wrap`(保留代码里的换行和空格)+ `text-align: left`(代码永远靠左)
672
+ - Q 区域 `max-height: 30vh` + `overflow-y: auto`,长题干不会挤掉代码
673
+ - **不显示语言 tag** —— 语言(`ts` / `python`)约定作者写进 material 的 `tags` 里,在资料列表页展示;卡片视图内只保留 Shiki 高亮效果,不重复显示语言标签
674
+
675
+ ### 交互细节
676
+
677
+ - **无自动朗读**
678
+ - **必须打准所有缩进、括号、分号、大小写** —— 代码打字的核心训练目标
679
+ - Tab 键行为受 `tabBehavior` 设置控制:`input`(推荐,把 Tab 当字符打入)/ `reset`(重打当前卡)/ `disabled`
680
+ - 打字时**语法高亮实时保留**(打对的字变绿,但底色仍是 shiki 的颜色)
681
+ - **`code` 是懒加载的**:整个 CodeCard 组件通过 `React.lazy` 引入,Shiki 依赖大约 1MB,不打代码就不下
682
+
683
+ ### 邻卡预览
684
+
685
+ - **title**:题目名(`card.title`)
686
+ - **无 subtitle**
687
+ - 不预览代码本身(占地方,而且预览里没高亮反而看着乱)
688
+
689
+ ---
690
+
691
+ ## 调度与兜底
692
+
693
+ ### 分发路由(`CardView.tsx`)
694
+
695
+ ```tsx
696
+ switch (material.meta.type) {
697
+ case 'word': return <WordCard {...props} />
698
+ case 'sentence': return <SentenceCard {...props} />
699
+ case 'article': return <ArticleCard {...props} />
700
+ case 'snippet': return <SnippetCard {...props} />
701
+ case 'qa': return <QaCard {...props} />
702
+ case 'code':
703
+ return (
704
+ <Suspense fallback={<QaCard {...props} />}>
705
+ <CodeCard {...props} />
706
+ </Suspense>
707
+ )
708
+ default: return <CardBase {...props} />
709
+ }
710
+ ```
711
+
712
+ - `code` 用 `<Suspense>` 包裹,fallback 是 `<QaCard>` —— Shiki 还没加载好之前先按普通 qa 显示,加载完自动切
713
+ - 未知 type / frontmatter 里 type 是自定义字符串时 → `<CardBase>` 兜底:只显示 `question` + arena
714
+
715
+ ### CardBase 兜底渲染器
716
+
717
+ 极简结构:如果有 `question` 就显示它(纯文本),下面接 arena。没什么样式,防止全空。
718
+
719
+ ```tsx
720
+ <div className="qt-cardbase">
721
+ {card.question && <div className="qt-cardbase__prompt">{card.question}</div>}
722
+ <div className="qt-cardbase__arena">
723
+ <TypingArena key={card.id} {...arenaProps(props)} />
724
+ </div>
725
+ </div>
726
+ ```
727
+
728
+ ### arenaProps 转发
729
+
730
+ 所有 5 个渲染器都用 `arenaProps(props)` 把公共字段转发给 `<TypingArena>`:
731
+
732
+ - `target` = `card.answer`(打字目标)
733
+ - `charPlans` = `card.charPlans`(键盘可视化)
734
+ - `hint` / `readOnly` / `targetWpm` / `tabBehavior` / `retryOnFail` / `completionMode` / `maxRetries`
735
+ - `paused` + `onAutoPause` / `onFinish` / `onSkip` / `onStateChange` 回调
736
+
737
+ 这样各渲染器专心搞外围壳,不用重复写 arena 的接线。
738
+
739
+ ---
740
+
741
+ ## 一张对照表
742
+
743
+ | type | Markdown 结构 | 打字目标 | 显示辅助 | 朗读 | 图片 | 语法高亮 | 文字居中 | 邻卡预览 |
744
+ |---|---|---|---|---|---|---|---|---|
745
+ | **word** | H1/H2/H3/H4 | H1 单词本身 | 音标(H2)+ 释义(H3)| ✅ 自动+手动 | H4 单张 | ✗ | ✅ | 单词 + 释义 |
746
+ | **sentence** | H1/H2/H3/H4 | H1 整句 | 音标(H2)+ 翻译(H3)| ✅ 自动+手动 | H4 单张 | ✗ | ✅ | 整句 + 翻译 |
747
+ | **article** | H1 + 段落 | H1 下的正文 | 只保留正文中的图片 | ✗ | 保留 | ✗ | 标题居中 / 正文靠左 | 章节标题 |
748
+ | **snippet** | H1 + 短文 | H1 下的正文 | 只保留正文中的图片 | ✗ | 保留 | ✗ | 全部居中 | 作品标题 |
749
+ | **qa** | H1 + ## Q + ## A | ## A 原文 | ## Q 完整 md 渲染 | ✗ | Q 里显示 | ✗ | 标题居中 / 正文靠左 | 卡片标题 |
750
+ | **code** | H1 + ## Q + ## A(fenced) | ## A 里的代码 | ## Q 完整 md 渲染 | ✗ | Q 里显示 | ✅ Shiki (不显 lang tag) | 标题居中 / 正文靠左 | 题目名 |
751
+
752
+ ---
753
+
754
+ ## 常见问题
755
+
756
+ **Q1: 我能不能在一个文件里混两种 type?**
757
+ 不能。`type` 是 material 级别,决定整个文件的解析规则。想混就拆两个文件。
758
+
759
+ **Q2: word/sentence 的 H2 能不能多行?**
760
+ 不能。regex 只匹配 `^##\s+(.*)$` 单行,后续行会被忽略。
761
+
762
+ **Q3: article 里我想让用户练打图片链接怎么办?**
763
+ 不行。图片语法(`![](url)`)在解析阶段被无条件剥离。把它写在 fenced code 里也不行 —— article 没有 fence 剥离逻辑,那就成了普通字面量。要么改用 qa,把图片链接放 A 里。
764
+
765
+ **Q4: qa 的 A 我在开头写了列表 `-`,是不是也要打这个 `-`?**
766
+ 对,`-` 也要打。qa 的 A 保留全部 markdown 语法原文,包括 `- ` `**` `##` ``` ` `` 等。这是设计目标。
767
+
768
+ **Q5: code 的 fence 语言标签写错了会怎样?**
769
+ Shiki 找不到该语言就 fallback 到"无高亮"(charColors 全 null),arena 用默认文本色渲染,打字功能正常。不会崩。