@qnroa/qtype 0.0.2 → 0.0.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.
Files changed (468) hide show
  1. package/CHANGELOG.md +44 -3
  2. package/CHANGELOG.zh.md +36 -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 +88 -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-1XXyxiQO.js +189 -0
  224. package/dist/view/assets/index-BLryk3W4.css +1 -0
  225. package/dist/view/assets/index-DIBSxFYl.js +3 -0
  226. package/dist/view/assets/index-DgHvnMV9.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 +11 -3
  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
@@ -1,20 +1,21 @@
1
1
  import { t } from '../../i18n/index.js';
2
- import { messages } from './i18n.js';
3
- import { checkAction } from './action.js';
2
+ import { messages as lintMessages } from '../content/lint/i18n.js';
3
+ import { contentLintAction } from '../content/lint/action.js';
4
4
  export const checkCmd = {
5
5
  name: 'check',
6
- messages,
7
- summary: () => t('check.summary'),
8
- describe: () => t('check.description'),
6
+ messages: lintMessages,
7
+ summary: () => t('content.lint.summary'),
8
+ describe: () => t('content.lint.description'),
9
9
  options: [
10
- { flags: '--repo <path>', describe: () => t('check.opt.repo') },
10
+ { flags: '--repo <path>', describe: () => t('content.lint.arg.path') },
11
+ { flags: '--json', describe: () => t('content.lint.opt.json') },
11
12
  ],
12
13
  examples: () => [
13
- { command: 'qtype check', describe: () => t('check.example.basic') },
14
- { command: 'qtype check --repo ./my-material', describe: () => t('check.example.repo') },
14
+ { command: 'qtype check', describe: () => t('content.lint.example.basic') },
15
+ { command: 'qtype check --json', describe: () => t('content.lint.example.json') },
15
16
  ],
16
- seeAlso: ['publish build', 'content lint'],
17
+ seeAlso: ['content lint', 'content validate', 'publish build'],
17
18
  action: async (opts) => {
18
- await checkAction({ repo: opts.repo });
19
+ await contentLintAction({ path: opts.repo, json: opts.json });
19
20
  },
20
21
  };
@@ -1,6 +1,6 @@
1
1
  import { KvError } from '../../../store/index.js';
2
2
  import { userKv, repoKv } from '../../bootstrap.js';
3
- import { info, success, dim, error } from '../../utils/output.js';
3
+ import { info, success, dim, error, fatal } from '../../utils/output.js';
4
4
  import { t } from '../../i18n/index.js';
5
5
  /** --global / --local are mutually exclusive; returns the explicitly-specified scope (neither given → undefined). */
6
6
  function pickScope(opts) {
@@ -61,8 +61,7 @@ export async function configGetAction(key, opts) {
61
61
  if (!has) {
62
62
  const def = kv.getSchema(key)?.default;
63
63
  if (def === undefined) {
64
- error(t('config.get.msg.notSetScoped', { key, scope }));
65
- process.exit(1);
64
+ fatal(t('config.get.msg.notSetScoped', { key, scope }));
66
65
  }
67
66
  console.log(formatValue(def));
68
67
  return;
@@ -85,8 +84,7 @@ export async function configGetAction(key, opts) {
85
84
  // Fall back to default: repo schema first, then user
86
85
  const def = repoKv.getSchema(key)?.default ?? userKv.getSchema(key)?.default;
87
86
  if (def === undefined) {
88
- error(t('config.get.msg.notSet', { key }));
89
- process.exit(1);
87
+ fatal(t('config.get.msg.notSet', { key }));
90
88
  }
91
89
  console.log(formatValue(def));
92
90
  }
@@ -95,8 +93,7 @@ async function showOrigin(key, scope) {
95
93
  const inUser = await userKv.has(key);
96
94
  if (scope === 'user') {
97
95
  if (!inUser) {
98
- error(t('config.get.msg.notSetScoped', { key, scope: 'user' }));
99
- process.exit(1);
96
+ fatal(t('config.get.msg.notSetScoped', { key, scope: 'user' }));
100
97
  }
101
98
  console.log(formatValue(await userKv.get(key)));
102
99
  console.error(` ${dim('from:')} ${userKv.origin} (user)`);
@@ -104,8 +101,7 @@ async function showOrigin(key, scope) {
104
101
  }
105
102
  if (scope === 'repo') {
106
103
  if (!inRepo) {
107
- error(t('config.get.msg.notSetScoped', { key, scope: 'repo' }));
108
- process.exit(1);
104
+ fatal(t('config.get.msg.notSetScoped', { key, scope: 'repo' }));
109
105
  }
110
106
  console.log(formatValue(await repoKv.get(key)));
111
107
  console.error(` ${dim('from:')} ${repoKv.origin} (repo)`);
@@ -6,67 +6,61 @@
6
6
  * - Each file parses to at least one Q/A card (error)
7
7
  * - No card contains characters unmapped in the keystroke plan (error)
8
8
  */
9
- import fs from 'node:fs';
10
- import path from 'node:path';
11
9
  import { info, success, error, warn } from '../../../utils/output.js';
12
10
  import { ensureQtypeRepo } from '../../../utils/repoGuard.js';
13
11
  import { t } from '../../../i18n/index.js';
14
12
  import { parseMaterial } from '../../../../core/parser/parseMaterial.js';
13
+ import { walkMaterial, readMaterial, MATERIAL_DIR } from '../../../utils/material.js';
15
14
  const TITLE_RE = /^title:\s*(.+)$/m;
16
15
  const HEADING_RE = /^#\s+.+$/m;
17
16
  export async function contentLintAction(opts = {}) {
18
17
  const root = ensureQtypeRepo(opts.path ?? process.cwd());
19
- const materialDir = path.join(root, 'material');
20
18
  if (!opts.json)
21
19
  info(t('content.lint.msg.scanning', { path: root }));
22
20
  const issues = [];
23
21
  let cardTotal = 0;
24
22
  let materialCount = 0;
25
- if (fs.existsSync(materialDir)) {
26
- for (const name of fs.readdirSync(materialDir).sort()) {
27
- if (!name.endsWith('.md'))
28
- continue;
29
- materialCount++;
30
- const rel = path.posix.join('material', name);
31
- const src = fs.readFileSync(path.join(materialDir, name), 'utf8');
32
- // File-level title (soft): warn if neither frontmatter title nor
33
- // a top-level heading is present — the browser view has nothing to
34
- // show as the material's name.
35
- const head = src.slice(0, 2048);
36
- if (!TITLE_RE.test(head) && !HEADING_RE.test(head)) {
37
- issues.push({
38
- code: 'material.emptyTitle',
39
- path: rel,
40
- message: t('content.lint.issue.emptyTitle', { path: rel }),
41
- });
42
- }
43
- const material = parseMaterial(src, rel);
44
- cardTotal += material.cards.length;
45
- if (material.cards.length === 0) {
23
+ for (const file of walkMaterial(root)) {
24
+ materialCount++;
25
+ const rel = `${MATERIAL_DIR}/${file.rel}`;
26
+ const src = readMaterial(file);
27
+ // File-level title (soft): warn if neither frontmatter title nor
28
+ // a top-level heading is present — the browser view has nothing to
29
+ // show as the material's name.
30
+ const head = src.slice(0, 2048);
31
+ if (!TITLE_RE.test(head) && !HEADING_RE.test(head)) {
32
+ issues.push({
33
+ code: 'material.emptyTitle',
34
+ path: rel,
35
+ message: t('content.lint.issue.emptyTitle', { path: rel }),
36
+ });
37
+ }
38
+ const material = parseMaterial(src, rel);
39
+ cardTotal += material.cards.length;
40
+ if (material.cards.length === 0) {
41
+ issues.push({
42
+ code: 'material.noCards',
43
+ path: rel,
44
+ message: t('content.lint.issue.noCards', { path: rel }),
45
+ });
46
+ continue;
47
+ }
48
+ material.cards.forEach((c, i) => {
49
+ if (c.unknownChars.length > 0) {
50
+ const chars = [...new Set(c.unknownChars)];
46
51
  issues.push({
47
- code: 'material.noCards',
52
+ code: 'material.unknownChars',
48
53
  path: rel,
49
- message: t('content.lint.issue.noCards', { path: rel }),
50
- });
51
- continue;
52
- }
53
- material.cards.forEach((c, i) => {
54
- if (c.unknownChars.length > 0) {
55
- const chars = [...new Set(c.unknownChars)];
56
- issues.push({
57
- code: 'material.unknownChars',
54
+ card: i + 1,
55
+ chars,
56
+ message: t('content.lint.issue.unknownChars', {
58
57
  path: rel,
59
58
  card: i + 1,
60
- chars,
61
- message: t('content.lint.issue.unknownChars', {
62
- path: rel,
63
- card: i + 1,
64
- chars: chars.join(' '),
65
- }),
66
- });
67
- }
68
- });
69
- }
59
+ chars: chars.join(' '),
60
+ }),
61
+ });
62
+ }
63
+ });
70
64
  }
71
65
  // `emptyTitle` is treated as a warning (informational). Anything else is an error.
72
66
  const errorIssues = issues.filter((i) => i.code !== 'material.emptyTitle');
@@ -1,56 +1,66 @@
1
1
  /**
2
2
  * `qtype content search <query>` action — grep-style search over material/*.md.
3
3
  *
4
- * Simple case-insensitive substring match on card questions + answers. Not
5
- * ranked just returns first-N hits so a user can pipe grep-style output
6
- * into vim/less. Deliberately dumb: fits the "types of cards, not a knowledge
7
- * graph" scope of qtype.
4
+ * Simple case-insensitive substring match on card questions + answers,
5
+ * with optional tag / type / limit filters and a --json output mode for
6
+ * scripting. Not ranked just returns first-N hits, insertion order.
8
7
  */
9
- import fs from 'node:fs';
10
- import path from 'node:path';
11
8
  import { info, dim } from '../../../utils/output.js';
12
9
  import { ensureQtypeRepo } from '../../../utils/repoGuard.js';
13
10
  import { t } from '../../../i18n/index.js';
14
11
  import { parseMaterial } from '../../../../core/parser/parseMaterial.js';
12
+ import { walkMaterial, readMaterial, MATERIAL_DIR } from '../../../utils/material.js';
15
13
  export async function contentSearchAction(query, opts = {}) {
16
14
  const root = ensureQtypeRepo(opts.path ?? process.cwd());
17
- const materialDir = path.join(root, 'material');
18
15
  const limit = opts.limit ?? 20;
16
+ const tagFilter = opts.tag?.map((t) => t.toLowerCase()) ?? [];
17
+ const typeFilter = opts.type?.map((t) => t.toLowerCase()) ?? [];
19
18
  const q = query.toLowerCase();
20
19
  const hits = [];
21
- if (fs.existsSync(materialDir)) {
22
- outer: for (const name of fs.readdirSync(materialDir).sort()) {
23
- if (!name.endsWith('.md'))
20
+ outer: for (const file of walkMaterial(root)) {
21
+ const rel = `${MATERIAL_DIR}/${file.rel}`;
22
+ const src = readMaterial(file);
23
+ const material = parseMaterial(src, rel);
24
+ const materialType = material.meta.type?.toLowerCase() ?? 'article';
25
+ if (typeFilter.length > 0 && !typeFilter.includes(materialType))
26
+ continue;
27
+ const materialTags = (material.meta.tags ?? []).map((t) => t.toLowerCase());
28
+ if (tagFilter.length > 0 &&
29
+ !tagFilter.every((wanted) => materialTags.includes(wanted))) {
30
+ continue;
31
+ }
32
+ for (let i = 0; i < material.cards.length; i++) {
33
+ const card = material.cards[i];
34
+ const question = card.question ?? card.title;
35
+ const qLc = question.toLowerCase();
36
+ const aLc = card.answer.toLowerCase();
37
+ if (!qLc.includes(q) && !aLc.includes(q))
24
38
  continue;
25
- const rel = path.posix.join('material', name);
26
- const src = fs.readFileSync(path.join(materialDir, name), 'utf8');
27
- const material = parseMaterial(src, rel);
28
- material.cards.forEach((card, i) => {
29
- const qLc = card.question.toLowerCase();
30
- const aLc = card.answer.toLowerCase();
31
- if (!qLc.includes(q) && !aLc.includes(q))
32
- return;
33
- const snippetSrc = qLc.includes(q) ? card.question : card.answer;
34
- const idx = snippetSrc.toLowerCase().indexOf(q);
35
- const start = Math.max(0, idx - 20);
36
- const end = Math.min(snippetSrc.length, idx + q.length + 40);
37
- const snippet = (start > 0 ? '…' : '') +
38
- snippetSrc.slice(start, end).replace(/\n+/g, ' ') +
39
- (end < snippetSrc.length ? '…' : '');
40
- hits.push({
41
- file: rel,
42
- card: i + 1,
43
- question: card.question,
44
- answer: card.answer,
45
- snippet,
46
- });
47
- if (hits.length >= limit)
48
- return;
39
+ const snippetSrc = qLc.includes(q) ? question : card.answer;
40
+ const idx = snippetSrc.toLowerCase().indexOf(q);
41
+ const start = Math.max(0, idx - 20);
42
+ const end = Math.min(snippetSrc.length, idx + q.length + 40);
43
+ const snippet = (start > 0 ? '…' : '') +
44
+ snippetSrc.slice(start, end).replace(/\n+/g, ' ') +
45
+ (end < snippetSrc.length ? '…' : '');
46
+ hits.push({
47
+ file: rel,
48
+ card: i + 1,
49
+ title: card.title,
50
+ question,
51
+ answer: card.answer,
52
+ snippet,
53
+ type: material.meta.type,
54
+ tags: material.meta.tags ?? [],
49
55
  });
50
56
  if (hits.length >= limit)
51
57
  break outer;
52
58
  }
53
59
  }
60
+ if (opts.json) {
61
+ console.log(JSON.stringify({ query, count: hits.length, hits }, null, 2));
62
+ return;
63
+ }
54
64
  if (hits.length === 0) {
55
65
  info(t('content.search.msg.noResults'));
56
66
  return;
@@ -5,6 +5,9 @@ export const messages = {
5
5
  'content.search.arg.query': '搜索关键词',
6
6
  'content.search.arg.path': '仓库路径(默认当前目录)',
7
7
  'content.search.opt.limit': '最多返回结果数(默认 20)',
8
+ 'content.search.opt.tag': '按 tag 过滤 material(可多次;需全部匹配)',
9
+ 'content.search.opt.type': '按 material 类型过滤(可多次指定)',
10
+ 'content.search.opt.json': '以 JSON 输出结果,便于脚本处理',
8
11
  'content.search.msg.noResults': '没有找到匹配的结果',
9
12
  'content.search.msg.results': '找到 {count} 个结果',
10
13
  },
@@ -14,6 +17,9 @@ export const messages = {
14
17
  'content.search.arg.query': 'Search query',
15
18
  'content.search.arg.path': 'Repo path (default: current directory)',
16
19
  'content.search.opt.limit': 'Max results (default: 20)',
20
+ 'content.search.opt.tag': 'Filter by tag (repeatable; all must match)',
21
+ 'content.search.opt.type': 'Filter by material type (repeatable)',
22
+ 'content.search.opt.json': 'Emit results as JSON for scripting',
17
23
  'content.search.msg.noResults': 'No results found',
18
24
  'content.search.msg.results': 'Found {count} results',
19
25
  },
@@ -17,8 +17,26 @@ export const contentSearchCmd = {
17
17
  parser: (v) => parseInt(v, 10),
18
18
  defaultValue: 20,
19
19
  },
20
+ {
21
+ flags: '--tag <tag...>',
22
+ describe: () => t('content.search.opt.tag'),
23
+ },
24
+ {
25
+ flags: '--type <type...>',
26
+ describe: () => t('content.search.opt.type'),
27
+ },
28
+ {
29
+ flags: '--json',
30
+ describe: () => t('content.search.opt.json'),
31
+ },
20
32
  ],
21
33
  action: async (query, pathArg, opts) => {
22
- await contentSearchAction(query, { path: pathArg, limit: opts.limit });
34
+ await contentSearchAction(query, {
35
+ path: pathArg,
36
+ limit: opts.limit,
37
+ tag: opts.tag,
38
+ type: opts.type,
39
+ json: opts.json,
40
+ });
23
41
  },
24
42
  };
@@ -11,49 +11,47 @@
11
11
  * - Tag consistency: warns when the same tag is used with different
12
12
  * casing (`react` vs `React`)
13
13
  */
14
- import fs from 'node:fs';
15
- import path from 'node:path';
16
14
  import { info, success, error, warn } from '../../../utils/output.js';
17
15
  import { ensureQtypeRepo } from '../../../utils/repoGuard.js';
18
16
  import { t } from '../../../i18n/index.js';
19
17
  import { parseMaterial } from '../../../../core/parser/parseMaterial.js';
18
+ import { walkMaterial, readMaterial, MATERIAL_DIR } from '../../../utils/material.js';
20
19
  export async function contentValidateAction(opts = {}) {
21
20
  const root = ensureQtypeRepo(opts.path ?? process.cwd());
22
- const materialDir = path.join(root, 'material');
23
21
  if (!opts.json)
24
22
  info(t('content.validate.msg.validating', { path: root }));
25
23
  const errors = [];
26
24
  const warnings = [];
27
25
  let cardTotal = 0;
28
- const files = [];
26
+ const materials = walkMaterial(root);
29
27
  // ── Filename uniqueness (case-insensitive) ──────────────
30
- if (fs.existsSync(materialDir)) {
28
+ // Compares by the full relative path — a file at en/a.md and one
29
+ // at zh/a.md are NOT a collision (different folders), whereas
30
+ // a.md and A.md at the same level are.
31
+ {
31
32
  const seen = new Map();
32
- for (const name of fs.readdirSync(materialDir).sort()) {
33
- if (!name.endsWith('.md'))
34
- continue;
35
- files.push(name);
36
- const key = name.toLowerCase();
33
+ for (const file of materials) {
34
+ const key = file.rel.toLowerCase();
37
35
  const prior = seen.get(key);
38
- if (prior && prior !== name) {
36
+ if (prior && prior !== file.rel) {
39
37
  errors.push({
40
38
  code: 'duplicate.file',
41
- path: name,
42
- message: t('content.validate.err.duplicateFile', { file: name }),
39
+ path: file.rel,
40
+ message: t('content.validate.err.duplicateFile', { file: file.rel }),
43
41
  extra: { collidesWith: prior },
44
42
  });
45
43
  }
46
44
  else {
47
- seen.set(key, name);
45
+ seen.set(key, file.rel);
48
46
  }
49
47
  }
50
48
  }
51
49
  // ── Parse every material, then run cross-file checks ────
52
50
  const questionIndex = new Map(); // normalized Q → "path#card"
53
51
  const tagVariants = new Map(); // lower → { original casings }
54
- for (const name of files) {
55
- const rel = path.posix.join('material', name);
56
- const src = fs.readFileSync(path.join(materialDir, name), 'utf8');
52
+ for (const file of materials) {
53
+ const rel = `${MATERIAL_DIR}/${file.rel}`;
54
+ const src = readMaterial(file);
57
55
  const material = parseMaterial(src, rel);
58
56
  cardTotal += material.cards.length;
59
57
  material.cards.forEach((card, i) => {
@@ -72,8 +70,9 @@ export async function contentValidateAction(opts = {}) {
72
70
  extra: { chars },
73
71
  });
74
72
  }
75
- // Duplicate card questions across the corpus.
76
- const key = card.question.trim().toLowerCase();
73
+ // Duplicate card titles across the corpus (title stands in as the
74
+ // card identity — the H1 text uniquely names each card).
75
+ const key = card.title.trim().toLowerCase();
77
76
  if (key.length > 0) {
78
77
  const prior = questionIndex.get(key);
79
78
  const here = `${rel}#${cardIdx}`;
@@ -93,14 +92,16 @@ export async function contentValidateAction(opts = {}) {
93
92
  questionIndex.set(key, here);
94
93
  }
95
94
  }
96
- // Tag casing consistency.
97
- for (const tag of card.meta.tags) {
98
- const lower = tag.toLowerCase();
99
- const set = tagVariants.get(lower) ?? new Set();
100
- set.add(tag);
101
- tagVariants.set(lower, set);
102
- }
95
+ // Card-level tag consistency was removed with the frontmatter
96
+ // simplification only material-level tags exist now, handled below.
103
97
  });
98
+ // Material-level tag casing consistency.
99
+ for (const tag of material.meta.tags) {
100
+ const lower = tag.toLowerCase();
101
+ const set = tagVariants.get(lower) ?? new Set();
102
+ set.add(tag);
103
+ tagVariants.set(lower, set);
104
+ }
104
105
  }
105
106
  for (const [lower, variants] of tagVariants) {
106
107
  if (variants.size > 1) {
@@ -116,7 +117,7 @@ export async function contentValidateAction(opts = {}) {
116
117
  }
117
118
  const result = {
118
119
  ok: errors.length === 0,
119
- stats: { materials: files.length, cards: cardTotal, warnings: warnings.length },
120
+ stats: { materials: materials.length, cards: cardTotal, warnings: warnings.length },
120
121
  errors,
121
122
  warnings,
122
123
  };
@@ -4,7 +4,6 @@ import { publishCmd } from './publish/index.js';
4
4
  import { checkCmd } from './check/index.js';
5
5
  import { contentCmd } from './content/index.js';
6
6
  import { configCmd } from './config/index.js';
7
- import { pluginCmd } from './plugin/index.js';
8
7
  import { packCmd } from './pack/index.js';
9
8
  export const allCommands = [
10
9
  initCmd,
@@ -13,7 +12,6 @@ export const allCommands = [
13
12
  checkCmd,
14
13
  contentCmd,
15
14
  packCmd,
16
- pluginCmd,
17
15
  configCmd,
18
16
  ];
19
- export { initCmd, repoCmd, publishCmd, checkCmd, contentCmd, packCmd, pluginCmd, configCmd, };
17
+ export { initCmd, repoCmd, publishCmd, checkCmd, contentCmd, packCmd, configCmd, };
@@ -9,6 +9,7 @@ import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { t } from '../../i18n/index.js';
11
11
  import { error, info, success } from '../../utils/output.js';
12
+ import { MATERIAL_DIR } from '../../utils/material.js';
12
13
  import { messages } from './i18n.js';
13
14
  const IGNORE = new Set(['dist', 'node_modules', '.git']);
14
15
  export const packCmd = {
@@ -21,7 +22,7 @@ export const packCmd = {
21
22
  ],
22
23
  action: async (opts) => {
23
24
  const cwd = process.cwd();
24
- if (!fs.existsSync(path.join(cwd, 'material'))) {
25
+ if (!fs.existsSync(path.join(cwd, MATERIAL_DIR))) {
25
26
  error(t('pack.err.noMaterial'));
26
27
  process.exitCode = 1;
27
28
  return;
@@ -20,11 +20,13 @@ import { createRepoKv } from '../../../bootstrap.js';
20
20
  import { deriveAesKey, encryptString, DEFAULT_ARGON2_PARAMS, } from '../../../../core/encrypt.js';
21
21
  import { verifyPassword } from '../password/action.js';
22
22
  import { prerenderMermaid, emptyMermaidPass } from './mermaidPass.js';
23
+ import { parseMaterialFrontmatter } from '../../../../core/parser/frontmatter.js';
24
+ import { walkMaterial, readMaterial, MATERIAL_DIR } from '../../../utils/material.js';
23
25
  export async function publishBuildAction(opts = {}) {
24
26
  const cwd = opts.path ? path.resolve(opts.path) : process.cwd();
25
27
  const outDir = path.resolve(cwd, opts.out ?? 'dist');
26
28
  const viewBundle = resolveBundle('dist/view');
27
- const materialDir = path.join(cwd, 'material');
29
+ const materialDir = path.join(cwd, MATERIAL_DIR);
28
30
  info(t('publish.build.msg.parsing', { path: cwd }));
29
31
  if (!fs.existsSync(viewBundle) ||
30
32
  !fs.existsSync(path.join(viewBundle, 'index.html'))) {
@@ -40,13 +42,20 @@ export async function publishBuildAction(opts = {}) {
40
42
  const encryptCtx = await resolveEncryptCtx(cwd);
41
43
  const mermaidMode = await resolveMermaidMode(cwd);
42
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);
43
49
  const materials = [];
44
- for (const name of fs.readdirSync(materialDir)) {
45
- if (!name.endsWith('.md'))
46
- continue;
47
- const body = fs.readFileSync(path.join(materialDir, name), 'utf8');
48
- const title = readTitle(body) ?? name.replace(/\.md$/, '');
49
- materials.push({ name, body, title });
50
+ for (const file of walkMaterial(cwd)) {
51
+ const body = readMaterial(file);
52
+ const filename = file.rel.split('/').pop() ?? file.rel;
53
+ const title = readTitle(body) ?? filename.replace(/\.md$/, '');
54
+ const fm = parseMaterialFrontmatter(body).meta;
55
+ const fallbackType = file.rel.includes('/') ? file.rel.split('/')[0] : '';
56
+ const type = fm.type ?? fallbackType;
57
+ const cardCount = countH1(body);
58
+ materials.push({ name: file.rel, body, title, type, tags: fm.tags, cardCount });
50
59
  }
51
60
  // Mermaid pre-render — scan all bodies, produce svg + css maps.
52
61
  const mermaid = mermaidMode === 'prerender'
@@ -59,23 +68,38 @@ export async function publishBuildAction(opts = {}) {
59
68
  fs.rmSync(outDir, { recursive: true, force: true });
60
69
  fs.mkdirSync(outDir, { recursive: true });
61
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);
62
76
  // Defensive: clear any stale material/ that might have snuck into the
63
77
  // view bundle. Since dev-seed content lives at repo root's examples/
64
78
  // (served only by the vite dev middleware, not the bundle), this is a
65
79
  // no-op today — kept in case a future build tool ships anything here.
66
- const targetMaterial = path.join(outDir, 'material');
80
+ const targetMaterial = path.join(outDir, MATERIAL_DIR);
67
81
  fs.rmSync(targetMaterial, { recursive: true, force: true });
68
82
  fs.mkdirSync(targetMaterial, { recursive: true });
69
83
  const entries = [];
70
84
  for (const m of materials) {
85
+ const outPath = encryptCtx
86
+ ? path.join(targetMaterial, `${m.name}.enc.json`)
87
+ : path.join(targetMaterial, m.name);
88
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
71
89
  if (encryptCtx) {
72
90
  const blob = await encryptString(encryptCtx.key, m.body);
73
- fs.writeFileSync(path.join(targetMaterial, `${m.name}.enc.json`), JSON.stringify(blob));
91
+ fs.writeFileSync(outPath, JSON.stringify(blob));
74
92
  }
75
93
  else {
76
- fs.writeFileSync(path.join(targetMaterial, m.name), m.body);
94
+ fs.writeFileSync(outPath, m.body);
77
95
  }
78
- entries.push({ file: m.name, title: m.title });
96
+ entries.push({
97
+ file: m.name,
98
+ title: m.title,
99
+ type: m.type,
100
+ tags: m.tags,
101
+ cardCount: m.cardCount,
102
+ });
79
103
  }
80
104
  // Mermaid SVG map + CSS: the view fetches these alongside index.json and
81
105
  // looks up each fence by its trimmed source when rendering material bodies.
@@ -92,6 +116,14 @@ export async function publishBuildAction(opts = {}) {
92
116
  fs.writeFileSync(path.join(targetMaterial, 'mermaid.css'), mermaid.css);
93
117
  }
94
118
  }
119
+ // Copy `material/assets/**` verbatim (images referenced by cards
120
+ // via `![](/material/assets/…)` 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
+ }
95
127
  // Manifest: lists materials + optional encryption envelope + mermaid mode.
96
128
  const manifest = { materials: entries };
97
129
  if (encryptCtx) {
@@ -171,6 +203,38 @@ async function resolveRepoLang(cwd) {
171
203
  return 'zh-CN';
172
204
  return 'en-US';
173
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, '&amp;')
226
+ .replace(/</g, '&lt;')
227
+ .replace(/>/g, '&gt;')
228
+ .replace(/"/g, '&quot;');
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
+ }
174
238
  async function obtainPassword() {
175
239
  const envPwd = process.env.QTYPE_PUBLISH_PASSWORD;
176
240
  if (envPwd)
@@ -196,6 +260,19 @@ function readTitle(source) {
196
260
  const m = head.match(/^title:\s*(.+)$/m);
197
261
  return m ? m[1].trim().replace(/^["']|["']$/g, '') : null;
198
262
  }
263
+ /**
264
+ * Cheap card-count estimator — counts top-level `#` H1 lines in the
265
+ * body. Enough for the library's "N cards" chip without full markdown
266
+ * parse. Matches the parser's `splitByH1` semantics.
267
+ */
268
+ function countH1(source) {
269
+ let n = 0;
270
+ for (const line of source.split('\n')) {
271
+ if (/^#\s+/.test(line))
272
+ n++;
273
+ }
274
+ return n;
275
+ }
199
276
  function countCards(materialDir) {
200
277
  let n = 0;
201
278
  for (const name of fs.readdirSync(materialDir)) {