@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.
- package/CHANGELOG.md +44 -3
- package/CHANGELOG.zh.md +36 -2
- package/README.md +88 -56
- package/README.zh.md +82 -62
- package/dist/cli/bootstrap.js +4 -115
- package/dist/cli/commands/check/index.js +11 -10
- package/dist/cli/commands/config/action.js +5 -9
- package/dist/cli/commands/content/lint/action.js +38 -44
- package/dist/cli/commands/content/search/action.js +44 -34
- package/dist/cli/commands/content/search/i18n.js +6 -0
- package/dist/cli/commands/content/search/index.js +19 -1
- package/dist/cli/commands/content/validate/action.js +28 -27
- package/dist/cli/commands/index.js +1 -3
- package/dist/cli/commands/pack/index.js +2 -1
- package/dist/cli/commands/publish/build/action.js +88 -11
- package/dist/cli/commands/repo/new/action.js +23 -4
- package/dist/cli/commands/repo/new/i18n.js +2 -2
- package/dist/cli/commands/repo/sync/action.js +24 -4
- package/dist/cli/commands/repo/templates.js +2 -1
- package/dist/cli/utils/material.js +49 -0
- package/dist/core/encrypt.js +24 -1
- package/dist/core/index.js +1 -0
- package/dist/core/parser/frontmatter.js +39 -34
- package/dist/core/parser/parseMaterial.js +174 -24
- package/dist/core/romanization/index.js +152 -0
- package/dist/core/schemas/both.js +52 -0
- package/dist/core/schemas/index.js +3 -0
- package/dist/core/schemas/repo.js +55 -0
- package/dist/core/schemas/user.js +19 -0
- package/dist/core/typing/engine/TypingInput.js +243 -0
- package/dist/core/typing/engine/chars.js +30 -0
- package/dist/core/typing/engine/feedback.js +15 -0
- package/dist/core/typing/engine/index.js +5 -0
- package/dist/core/typing/engine/settings.js +8 -0
- package/dist/core/typing/engine/stats.js +45 -0
- package/dist/core/typing/index.js +9 -1
- package/dist/view/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/view/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/view/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/view/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/view/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/view/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/view/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/view/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/view/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/view/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/view/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/view/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/view/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/view/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/view/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/view/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/view/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/view/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/view/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/view/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/view/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/view/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/view/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/view/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/view/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/view/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/view/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/view/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/view/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/view/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/view/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/view/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/view/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/view/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/view/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/view/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/view/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/view/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/view/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/view/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/view/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/view/assets/abap-BdImnpbu.js +1 -0
- package/dist/view/assets/actionscript-3-B3316cI-.js +1 -0
- package/dist/view/assets/ada-bCR0ucgS.js +1 -0
- package/dist/view/assets/ahk-CsyLZFj1.js +1 -0
- package/dist/view/assets/ahk2-8Zs4aa1G.js +1 -0
- package/dist/view/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/view/assets/angular-html-DA-rfuFy.js +1 -0
- package/dist/view/assets/angular-ts-BrjP3tb8.js +1 -0
- package/dist/view/assets/apache-Pmp26Uib.js +1 -0
- package/dist/view/assets/apex-DhZFqWV2.js +1 -0
- package/dist/view/assets/apl-CORt7UWP.js +1 -0
- package/dist/view/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/view/assets/ara-BRHolxvo.js +1 -0
- package/dist/view/assets/asciidoc-CSVQ5wI8.js +1 -0
- package/dist/view/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/view/assets/astro-HNnZUWAn.js +1 -0
- package/dist/view/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/view/assets/awk-DMzUqQB5.js +1 -0
- package/dist/view/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/view/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/view/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/view/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/view/assets/bat-CickPsom.js +1 -0
- package/dist/view/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/view/assets/berry-uYugtg8r.js +1 -0
- package/dist/view/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/view/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/view/assets/bird2-Bx8U0n9b.js +1 -0
- package/dist/view/assets/blade-2xfisSek.js +1 -0
- package/dist/view/assets/bsl-DlhNcFeZ.js +1 -0
- package/dist/view/assets/c-BIGW1oBm.js +1 -0
- package/dist/view/assets/c3-Dp5svz6Z.js +1 -0
- package/dist/view/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/view/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/view/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
- package/dist/view/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
- package/dist/view/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
- package/dist/view/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
- package/dist/view/assets/chapel-DTp_pixX.js +1 -0
- package/dist/view/assets/clarity-Dn5IMItf.js +1 -0
- package/dist/view/assets/clojure-P80f7IUj.js +1 -0
- package/dist/view/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/view/assets/cobol-nBiQ_Alo.js +1 -0
- package/dist/view/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/view/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/view/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/view/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/view/assets/coq-C7JzOVbR.js +1 -0
- package/dist/view/assets/cpp-BMRokrvK.js +1 -0
- package/dist/view/assets/crystal-DGywbUpC.js +1 -0
- package/dist/view/assets/csharp-DSvCPggb.js +1 -0
- package/dist/view/assets/css-CLj8gQPS.js +1 -0
- package/dist/view/assets/csv-fuZLfV_i.js +1 -0
- package/dist/view/assets/cue-D82EKSYY.js +1 -0
- package/dist/view/assets/cypher-COkxafJQ.js +1 -0
- package/dist/view/assets/d-85-TOEBH.js +1 -0
- package/dist/view/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/view/assets/dart-bE4Kk8sk.js +1 -0
- package/dist/view/assets/dax-CEL-wOlO.js +1 -0
- package/dist/view/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/view/assets/diff-D97Zzqfu.js +1 -0
- package/dist/view/assets/docker-BcOcwvcX.js +1 -0
- package/dist/view/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/view/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/view/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/view/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/view/assets/edge-FbVlp4U3.js +1 -0
- package/dist/view/assets/elixir-CkH2-t6x.js +1 -0
- package/dist/view/assets/elm-DbKCFpqz.js +1 -0
- package/dist/view/assets/emacs-lisp-C_m_b--Z.js +1 -0
- package/dist/view/assets/erb-DXfck5VN.js +1 -0
- package/dist/view/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/view/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/view/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/view/assets/fennel-BYunw83y.js +1 -0
- package/dist/view/assets/fish-BvzEVeQv.js +1 -0
- package/dist/view/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/view/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/view/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/view/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/view/assets/gdresource-TyuKm33G.js +1 -0
- package/dist/view/assets/gdscript-DqcFQ5yU.js +1 -0
- package/dist/view/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/view/assets/genie-D0YGMca9.js +1 -0
- package/dist/view/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/view/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/view/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/view/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/view/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/view/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/view/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/view/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/view/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/view/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/view/assets/gleam-BspZqrRM.js +1 -0
- package/dist/view/assets/glimmer-js-ByusRIyA.js +1 -0
- package/dist/view/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/dist/view/assets/glsl-DplSGwfg.js +1 -0
- package/dist/view/assets/gn-n2N0HUVH.js +1 -0
- package/dist/view/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/view/assets/go-C27-OAKa.js +1 -0
- package/dist/view/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/view/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/view/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/view/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/view/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/view/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/view/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/view/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/view/assets/hack-BWmVpMyf.js +1 -0
- package/dist/view/assets/haml-D5jkg6IW.js +1 -0
- package/dist/view/assets/handlebars-BpdQsYii.js +1 -0
- package/dist/view/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/view/assets/haxe-CfZj7gIn.js +1 -0
- package/dist/view/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/view/assets/hjson-D5-asLiD.js +1 -0
- package/dist/view/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/view/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/view/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/dist/view/assets/houston-DnULxvSX.js +1 -0
- package/dist/view/assets/html-derivative-DlHx6ybY.js +1 -0
- package/dist/view/assets/html-pp8916En.js +1 -0
- package/dist/view/assets/http-jrhK8wxY.js +1 -0
- package/dist/view/assets/hurl-irOxFIW8.js +1 -0
- package/dist/view/assets/hxml-2-FPmUDs.js +1 -0
- package/dist/view/assets/hy-DFXneXwc.js +1 -0
- package/dist/view/assets/imba-DGztddWO.js +1 -0
- package/dist/view/assets/index-1XXyxiQO.js +189 -0
- package/dist/view/assets/index-BLryk3W4.css +1 -0
- package/dist/view/assets/index-DIBSxFYl.js +3 -0
- package/dist/view/assets/index-DgHvnMV9.js +153 -0
- package/dist/view/assets/ini-BEwlwnbL.js +1 -0
- package/dist/view/assets/java-CylS5w8V.js +1 -0
- package/dist/view/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/view/assets/jinja-f2NsQr07.js +1 -0
- package/dist/view/assets/jison-wvAkD_A8.js +1 -0
- package/dist/view/assets/json-Cp-IABpG.js +1 -0
- package/dist/view/assets/json5-C9tS-k6U.js +1 -0
- package/dist/view/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/view/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/view/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/view/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/view/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/view/assets/julia-5Bft2YPA.js +1 -0
- package/dist/view/assets/just-Cwhn7H3k.js +1 -0
- package/dist/view/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
- package/dist/view/assets/kanagawa-lotus-C3DzagqV.js +1 -0
- package/dist/view/assets/kanagawa-wave-DFGoQZhC.js +1 -0
- package/dist/view/assets/katex-BYZTj3zW.css +1 -0
- package/dist/view/assets/katex-C5jXJg4s.js +257 -0
- package/dist/view/assets/kdl-DV7GczEv.js +1 -0
- package/dist/view/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/view/assets/kusto-wEQ09or8.js +1 -0
- package/dist/view/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/view/assets/latex-D5pSuvFb.js +1 -0
- package/dist/view/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/view/assets/less-B1dDrJ26.js +1 -0
- package/dist/view/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/view/assets/liquid-C0sCDyMI.js +1 -0
- package/dist/view/assets/llvm-BZoOZj88.js +1 -0
- package/dist/view/assets/log-2UxHyX5q.js +1 -0
- package/dist/view/assets/logo-BtOb2qkB.js +1 -0
- package/dist/view/assets/lua-BaeVxFsk.js +1 -0
- package/dist/view/assets/luau-BnpPk5vE.js +1 -0
- package/dist/view/assets/make-CHLpvVh8.js +1 -0
- package/dist/view/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/view/assets/marko-DjSrsDqO.js +1 -0
- package/dist/view/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/view/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/view/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/view/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/view/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/view/assets/matlab-D7o27uSR.js +1 -0
- package/dist/view/assets/mdc-D1_yUvq7.js +1 -0
- package/dist/view/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/view/assets/mermaid-CQcHuHx7.js +1 -0
- package/dist/view/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/view/assets/min-light-CTRr51gU.js +1 -0
- package/dist/view/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/view/assets/mojo-DJz3ZmWd.js +1 -0
- package/dist/view/assets/monokai-BRVnQi9A.js +1 -0
- package/dist/view/assets/moonbit-CHtswR0a.js +1 -0
- package/dist/view/assets/move-el3G9tDJ.js +1 -0
- package/dist/view/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/view/assets/nextflow-C-mBbutL.js +1 -0
- package/dist/view/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/dist/view/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/view/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/view/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/view/assets/nim-BIad80T-.js +1 -0
- package/dist/view/assets/nix-CwoSXNpI.js +1 -0
- package/dist/view/assets/nord-Ddv68eIx.js +1 -0
- package/dist/view/assets/nsis-BlV79W_Q.js +1 -0
- package/dist/view/assets/nushell-D3jzshHO.js +1 -0
- package/dist/view/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/view/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/view/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/view/assets/odin-BBf5iR-q.js +1 -0
- package/dist/view/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/view/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/view/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/view/assets/org-DM6o9KBp.js +1 -0
- package/dist/view/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/view/assets/perl-B9cMNwum.js +1 -0
- package/dist/view/assets/php-Csjmro_R.js +1 -0
- package/dist/view/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/view/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/view/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/view/assets/po-BTJTHyun.js +1 -0
- package/dist/view/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/view/assets/polar-C0HS_06l.js +1 -0
- package/dist/view/assets/postcss-CXtECtnM.js +1 -0
- package/dist/view/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/view/assets/powershell-BmBUJMz7.js +1 -0
- package/dist/view/assets/prisma-Vru482bI.js +1 -0
- package/dist/view/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/view/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/view/assets/pug-DKIMFp6K.js +1 -0
- package/dist/view/assets/puppet-BMWR74SV.js +1 -0
- package/dist/view/assets/purescript-CklMAg4u.js +1 -0
- package/dist/view/assets/python-B6aJPvgy.js +1 -0
- package/dist/view/assets/qml-3beO22l8.js +1 -0
- package/dist/view/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/view/assets/qss-IeuSbFQv.js +1 -0
- package/dist/view/assets/r-Cf5RLm7j.js +1 -0
- package/dist/view/assets/racket-BqYA7rlc.js +1 -0
- package/dist/view/assets/raku-DXvB9xmW.js +1 -0
- package/dist/view/assets/razor-BjBPvh-w.js +1 -0
- package/dist/view/assets/rbs-CpoqiR4B.js +1 -0
- package/dist/view/assets/red-hvxz__6c.js +1 -0
- package/dist/view/assets/reg-C-SQnVFl.js +1 -0
- package/dist/view/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/view/assets/rel-C3B-1QV4.js +1 -0
- package/dist/view/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/view/assets/ron-D8l8udqQ.js +1 -0
- package/dist/view/assets/rose-pine-5qJOZa0Y.js +1 -0
- package/dist/view/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
- package/dist/view/assets/rose-pine-moon-C9pEdX9L.js +1 -0
- package/dist/view/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/view/assets/rst-bs7f0vWN.js +1 -0
- package/dist/view/assets/ruby-C0TQ7zu5.js +1 -0
- package/dist/view/assets/rust-B1yitclQ.js +1 -0
- package/dist/view/assets/sas-DEy46yEz.js +1 -0
- package/dist/view/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/view/assets/scala-CqE71os6.js +1 -0
- package/dist/view/assets/scheme-C98Dy4si.js +1 -0
- package/dist/view/assets/scss-D5BDwBP9.js +1 -0
- package/dist/view/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/view/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/view/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/view/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/view/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/view/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/view/assets/smalltalk-BOQMe2GC.js +1 -0
- package/dist/view/assets/smithy-cds9vsN8.js +1 -0
- package/dist/view/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/view/assets/solarized-dark-CpvCGNkr.js +1 -0
- package/dist/view/assets/solarized-light-Dlz6yCKv.js +1 -0
- package/dist/view/assets/solidity-DijEV5ha.js +1 -0
- package/dist/view/assets/soy-8wufbnw4.js +1 -0
- package/dist/view/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/view/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/view/assets/sql-CRqJ_cUM.js +1 -0
- package/dist/view/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/view/assets/stata-DI20mbqo.js +1 -0
- package/dist/view/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/view/assets/surrealql-Cjom0U5J.js +1 -0
- package/dist/view/assets/svelte-Cy7k_4gC.js +1 -0
- package/dist/view/assets/swift-C2oV4EkX.js +1 -0
- package/dist/view/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/view/assets/system-verilog-0hqHdDBg.js +1 -0
- package/dist/view/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/view/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/view/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/view/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/view/assets/templ-DhtptRzy.js +1 -0
- package/dist/view/assets/terraform-BETggiCN.js +1 -0
- package/dist/view/assets/tex-D96PA37w.js +1 -0
- package/dist/view/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/view/assets/toml-vGWfd6FD.js +1 -0
- package/dist/view/assets/ts-tags-D351s5mN.js +1 -0
- package/dist/view/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/view/assets/tsx-COt5Ahok.js +1 -0
- package/dist/view/assets/turtle-BsS91CYL.js +1 -0
- package/dist/view/assets/twig-27uCiNez.js +1 -0
- package/dist/view/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/view/assets/typespec-CAFt9gP4.js +1 -0
- package/dist/view/assets/typst-BUadGCkm.js +1 -0
- package/dist/view/assets/v-BGw2Nkan.js +1 -0
- package/dist/view/assets/vala-CsfeWuGM.js +1 -0
- package/dist/view/assets/vb-Cu-pLBUe.js +1 -0
- package/dist/view/assets/verilog-nZwndyjY.js +1 -0
- package/dist/view/assets/vesper-DRje8inN.js +1 -0
- package/dist/view/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/view/assets/viml-CJc9bBzg.js +1 -0
- package/dist/view/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/view/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/view/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/view/assets/vue-BqiEGhQt.js +1 -0
- package/dist/view/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/view/assets/vue-vine-BoDAl6tE.js +1 -0
- package/dist/view/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/view/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/view/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/view/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/view/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/view/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/view/assets/wit-5i3qLPDT.js +1 -0
- package/dist/view/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/view/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/view/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/view/assets/yaml-Buea-lGh.js +1 -0
- package/dist/view/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/view/assets/zig-VOosw3JB.js +1 -0
- package/dist/view/index.html +11 -3
- package/docs/README.md +21 -15
- package/docs/README.zh.md +15 -9
- package/docs/en/authoring.md +85 -80
- package/docs/en/cli-reference.md +110 -72
- package/docs/en/deployment.md +183 -0
- package/docs/en/getting-started.md +112 -0
- package/docs/en/material-types.md +241 -0
- package/docs/en/settings.md +133 -0
- package/docs/en/shortcuts.md +89 -0
- package/docs/en/typing-rules.md +208 -0
- package/docs/zh/authoring.md +74 -69
- package/docs/zh/cli-reference.md +92 -58
- package/docs/zh/deployment.md +179 -0
- package/docs/zh/getting-started.md +104 -0
- package/docs/zh/material-types.md +769 -0
- package/docs/zh/settings.md +120 -0
- package/docs/zh/shortcuts.md +83 -0
- package/docs/zh/typing-rules.md +191 -0
- package/package.json +13 -11
- package/templates/en/QTYPE.md +155 -495
- package/templates/en/README.md +11 -11
- package/templates/en/TODO.md +1 -1
- package/templates/en/material/article.md +20 -0
- package/templates/en/material/assets/article/sunrise.svg +12 -0
- package/templates/en/material/assets/code/tree.svg +19 -0
- package/templates/en/material/assets/qa/login-flow.svg +17 -0
- package/templates/en/material/assets/sentence/pangram.svg +6 -0
- package/templates/en/material/assets/snippet/moon.svg +8 -0
- package/templates/en/material/assets/word/apple.svg +5 -0
- package/templates/en/material/code.md +55 -0
- package/templates/en/material/qa.md +33 -0
- package/templates/en/material/sentence.md +18 -0
- package/templates/en/material/snippet.md +17 -0
- package/templates/en/material/word.md +20 -0
- package/templates/zh/QTYPE.md +191 -473
- package/templates/zh/README.md +6 -6
- package/templates/zh/index.md +1 -1
- package/templates/zh/material/article.md +18 -0
- package/templates/zh/material/assets/article/sunrise.svg +12 -0
- package/templates/zh/material/assets/code/tree.svg +19 -0
- package/templates/zh/material/assets/qa/login-flow.svg +17 -0
- package/templates/zh/material/assets/sentence/pangram.svg +6 -0
- package/templates/zh/material/assets/snippet/moon.svg +8 -0
- package/templates/zh/material/assets/word/apple.svg +5 -0
- package/templates/zh/material/code.md +55 -0
- package/templates/zh/material/qa.md +33 -0
- package/templates/zh/material/sentence.md +19 -0
- package/templates/zh/material/snippet.md +20 -0
- package/templates/zh/material/word.md +20 -0
- package/dist/cli/commands/check/action.js +0 -56
- package/dist/cli/commands/check/i18n.js +0 -26
- package/dist/cli/commands/plugin/i18n.js +0 -28
- package/dist/cli/commands/plugin/index.js +0 -41
- package/dist/core/typing/engine.js +0 -112
- package/dist/view/assets/index-DBABH3dG.js +0 -111
- package/dist/view/assets/index-RTLZ-zOC.css +0 -1
- package/templates/en/material/example.md +0 -47
- package/templates/zh/material/example.md +0 -47
|
@@ -62,11 +62,30 @@ export async function repoNewAction(opts) {
|
|
|
62
62
|
fs.writeFileSync(path.join(repoDir, fileName), readTemplate(templatesDir, fileName, vars));
|
|
63
63
|
console.log(` + ${fileName}`);
|
|
64
64
|
}
|
|
65
|
-
// 4. Seed material
|
|
66
|
-
// but stays trivially prunable if the user wipes the seed.
|
|
65
|
+
// 4. Seed one starter per material type + .gitkeep so the directory
|
|
66
|
+
// exists but stays trivially prunable if the user wipes the seed.
|
|
67
|
+
// Each starter is a valid `qtype check`-clean deck the user can
|
|
68
|
+
// copy, rename, and edit. Their image assets ship alongside so the
|
|
69
|
+
// demos render out of the box.
|
|
67
70
|
fs.mkdirSync(path.join(repoDir, 'material'), { recursive: true });
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
const starters = [
|
|
72
|
+
['word', 'apple.svg'],
|
|
73
|
+
['sentence', 'pangram.svg'],
|
|
74
|
+
['article', 'sunrise.svg'],
|
|
75
|
+
['snippet', 'moon.svg'],
|
|
76
|
+
['qa', 'login-flow.svg'],
|
|
77
|
+
['code', 'tree.svg'],
|
|
78
|
+
];
|
|
79
|
+
for (const [type, asset] of starters) {
|
|
80
|
+
const mdRel = path.join('material', `${type}.md`);
|
|
81
|
+
fs.writeFileSync(path.join(repoDir, mdRel), readTemplate(templatesDir, mdRel, vars));
|
|
82
|
+
console.log(` + ${mdRel}`);
|
|
83
|
+
const assetRel = path.join('material', 'assets', type, asset);
|
|
84
|
+
const assetDir = path.dirname(path.join(repoDir, assetRel));
|
|
85
|
+
fs.mkdirSync(assetDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(repoDir, assetRel), readTemplate(templatesDir, assetRel, vars));
|
|
87
|
+
console.log(` + ${assetRel}`);
|
|
88
|
+
}
|
|
70
89
|
fs.writeFileSync(path.join(repoDir, 'material', '.gitkeep'), '');
|
|
71
90
|
console.log(` + material/.gitkeep`);
|
|
72
91
|
// 5. Write .qtype/config.json with the repo language.
|
|
@@ -16,7 +16,7 @@ export const messages = {
|
|
|
16
16
|
'repo.new.msg.done': '仓库已建好:{path}',
|
|
17
17
|
'repo.new.msg.nextSteps': '下一步:',
|
|
18
18
|
'repo.new.msg.next.cd': 'cd {name}',
|
|
19
|
-
'repo.new.msg.next.edit': '
|
|
19
|
+
'repo.new.msg.next.edit': 'ls material/ # 6 种类型的入门模板,改哪个都行',
|
|
20
20
|
'repo.new.msg.next.commit': 'git add -A && git commit # 首次提交',
|
|
21
21
|
'repo.new.msg.next.gitHint': 'git init # 如需版本控制,也可以 qtype repo new --git',
|
|
22
22
|
'repo.new.tpl.defaultDescription': '资料库',
|
|
@@ -38,7 +38,7 @@ export const messages = {
|
|
|
38
38
|
'repo.new.msg.done': 'Repo created: {path}',
|
|
39
39
|
'repo.new.msg.nextSteps': 'Next steps:',
|
|
40
40
|
'repo.new.msg.next.cd': 'cd {name}',
|
|
41
|
-
'repo.new.msg.next.edit': '
|
|
41
|
+
'repo.new.msg.next.edit': 'ls material/ # 6 starter templates, edit any of them',
|
|
42
42
|
'repo.new.msg.next.commit': 'git add -A && git commit # initial commit',
|
|
43
43
|
'repo.new.msg.next.gitHint': 'git init # or re-run with qtype repo new --git',
|
|
44
44
|
'repo.new.tpl.defaultDescription': 'material library',
|
|
@@ -16,12 +16,32 @@ function buildSyncPlan() {
|
|
|
16
16
|
{ kind: 'file', target: 'TODO.md', source: 'TODO.md', managed: false },
|
|
17
17
|
{ kind: 'file', target: 'index.md', source: 'index.md', managed: false },
|
|
18
18
|
{ kind: 'file', target: 'log.md', source: 'log.md', managed: false },
|
|
19
|
-
|
|
19
|
+
// Starter cards — one per material type. Users copy the one that
|
|
20
|
+
// matches what they're writing, rename it, edit content, and are
|
|
21
|
+
// off to the races. Every file is a valid `qtype check`-clean deck
|
|
22
|
+
// out of the box (the smoke test asserts this).
|
|
23
|
+
...['word', 'sentence', 'article', 'snippet', 'qa', 'code'].map((type) => ({
|
|
20
24
|
kind: 'file',
|
|
21
|
-
target:
|
|
22
|
-
source: path.join('material',
|
|
25
|
+
target: `material/${type}.md`,
|
|
26
|
+
source: path.join('material', `${type}.md`),
|
|
23
27
|
managed: false,
|
|
24
|
-
},
|
|
28
|
+
})),
|
|
29
|
+
// Starter card image assets — SVGs referenced by the starters.
|
|
30
|
+
// `currentColor`-stroked so they inherit the site's text color and
|
|
31
|
+
// adapt to dark/light theme without extra work.
|
|
32
|
+
...[
|
|
33
|
+
['word', 'apple.svg'],
|
|
34
|
+
['sentence', 'pangram.svg'],
|
|
35
|
+
['article', 'sunrise.svg'],
|
|
36
|
+
['snippet', 'moon.svg'],
|
|
37
|
+
['qa', 'login-flow.svg'],
|
|
38
|
+
['code', 'tree.svg'],
|
|
39
|
+
].map(([type, name]) => ({
|
|
40
|
+
kind: 'file',
|
|
41
|
+
target: `material/assets/${type}/${name}`,
|
|
42
|
+
source: path.join('material', 'assets', type, name),
|
|
43
|
+
managed: false,
|
|
44
|
+
})),
|
|
25
45
|
// ── Skeleton (create-only, never touched) ───────
|
|
26
46
|
{ kind: 'dir', target: '.qtype', managed: false },
|
|
27
47
|
{ kind: 'dir', target: 'material', managed: false },
|
|
@@ -9,7 +9,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
* ├── en/
|
|
10
10
|
* │ ├── QTYPE.md README.md TODO.md index.md log.md
|
|
11
11
|
* │ └── material/
|
|
12
|
-
* │
|
|
12
|
+
* │ ├── word.md article.md qa.md
|
|
13
|
+
* │ └── sentence.md snippet.md code.md
|
|
13
14
|
* └── zh/
|
|
14
15
|
* └── (same files)
|
|
15
16
|
*
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for walking a qtype repo's material/ directory.
|
|
3
|
+
*
|
|
4
|
+
* Every CLI command that reads material files (publish build, content
|
|
5
|
+
* lint / validate / search, check) goes through here so:
|
|
6
|
+
* - The "recursively descend into subfolders" behavior is a single
|
|
7
|
+
* implementation (used to be duplicated with different depths).
|
|
8
|
+
* - Only `.md` files are yielded (any accidental `.DS_Store`, images,
|
|
9
|
+
* etc. are skipped).
|
|
10
|
+
* - Paths are always returned with forward slashes so downstream
|
|
11
|
+
* JSON manifests and URL building agree even on Windows.
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
/** Directory name under a qtype repo root that holds the deck files. */
|
|
16
|
+
export const MATERIAL_DIR = 'material';
|
|
17
|
+
/**
|
|
18
|
+
* Yield every `.md` file under `<repoRoot>/material/`, recursively.
|
|
19
|
+
* Returns an empty array when the directory doesn't exist — callers
|
|
20
|
+
* that treat a missing dir as fatal should check that separately.
|
|
21
|
+
*/
|
|
22
|
+
export function walkMaterial(repoRoot) {
|
|
23
|
+
const root = path.join(repoRoot, MATERIAL_DIR);
|
|
24
|
+
if (!fs.existsSync(root))
|
|
25
|
+
return [];
|
|
26
|
+
const out = [];
|
|
27
|
+
const walk = (subdir) => {
|
|
28
|
+
const dir = path.join(root, subdir);
|
|
29
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
30
|
+
const rel = subdir ? `${subdir}/${entry.name}` : entry.name;
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
walk(rel);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!entry.name.endsWith('.md'))
|
|
36
|
+
continue;
|
|
37
|
+
out.push({ rel, abs: path.join(root, rel) });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
walk('');
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read a material file. Small wrapper for consistency across commands
|
|
45
|
+
* that all want utf-8 text.
|
|
46
|
+
*/
|
|
47
|
+
export function readMaterial(file) {
|
|
48
|
+
return fs.readFileSync(file.abs, 'utf8');
|
|
49
|
+
}
|
package/dist/core/encrypt.js
CHANGED
|
@@ -24,6 +24,23 @@
|
|
|
24
24
|
* path stays clean (plaintext → argon2id → raw bytes → AES key).
|
|
25
25
|
*/
|
|
26
26
|
import { argon2id } from '@noble/hashes/argon2.js';
|
|
27
|
+
// argon2id parameters — tuned 2026-08 against @noble/hashes on a mid-range
|
|
28
|
+
// laptop. Target: ≥500ms per derivation so the offline brute-force cost
|
|
29
|
+
// stays material without making the interactive login feel sluggish.
|
|
30
|
+
//
|
|
31
|
+
// Benchmark (M-series, `argon2id(pw, 16-byte salt, { dkLen: 32 })`, median
|
|
32
|
+
// of 3 warm runs):
|
|
33
|
+
// 32 MiB / t=2 → ~223ms ✗ below target
|
|
34
|
+
// 32 MiB / t=3 → ~327ms ✗ below target
|
|
35
|
+
// 64 MiB / t=2 → ~445ms ✗ just under target
|
|
36
|
+
// 64 MiB / t=3 → ~669ms ✓ current — comfortable margin above 500ms
|
|
37
|
+
// 96 MiB / t=3 → ~949ms ✗ ~1s login lag on modest hardware
|
|
38
|
+
// 128 MiB / t=3 → ~1272ms ✗ same
|
|
39
|
+
//
|
|
40
|
+
// Threat model recap: static-site content decrypts client-side, so an
|
|
41
|
+
// attacker with the bundle can brute force offline unbounded. Higher
|
|
42
|
+
// costs only linearly slow that attack. Sub-second is the plateau
|
|
43
|
+
// beyond which UX pain outweighs security gain for this class of app.
|
|
27
44
|
export const ARGON2_MEMORY = 64 * 1024;
|
|
28
45
|
export const ARGON2_TIME = 3;
|
|
29
46
|
export const ARGON2_PARALLEL = 1;
|
|
@@ -78,7 +95,13 @@ export async function deriveAesKey(password, saltB64, params = DEFAULT_ARGON2_PA
|
|
|
78
95
|
p: params.p,
|
|
79
96
|
dkLen: AES_KEY_LEN,
|
|
80
97
|
});
|
|
81
|
-
|
|
98
|
+
// `extractable: true` so the view layer can `exportKey('raw', …)` and
|
|
99
|
+
// stash the bytes in sessionStorage — that's what lets a page reload
|
|
100
|
+
// skip the argon2 re-derivation. On a static-site threat model this
|
|
101
|
+
// grants nothing new: whoever controls the tab already sees every
|
|
102
|
+
// AES.decrypt() plaintext, so making the CryptoKey itself extractable
|
|
103
|
+
// adds no attacker capability.
|
|
104
|
+
return globalThis.crypto.subtle.importKey('raw', toBufferSource(raw), { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
|
|
82
105
|
}
|
|
83
106
|
// Bridge helper: WebCrypto's DOM BufferSource type demands
|
|
84
107
|
// Uint8Array<ArrayBuffer>, while noble/hashes returns Uint8Array<ArrayBufferLike>
|
package/dist/core/index.js
CHANGED
|
@@ -1,46 +1,43 @@
|
|
|
1
1
|
const FM_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Material-level frontmatter (top of the file, before the first `#`).
|
|
4
|
+
*
|
|
5
|
+
* ---
|
|
6
|
+
* title: <标题>
|
|
7
|
+
* type: word | sentence | article | qa | code
|
|
8
|
+
* tags: [<t1>, <t2>]
|
|
9
|
+
* created: YYYY-MM-DD
|
|
10
|
+
* updated: YYYY-MM-DD
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* Card-level frontmatter no longer exists — everything a card carries
|
|
14
|
+
* is expressed by its markdown structure (see parseMaterial for the
|
|
15
|
+
* per-type rules).
|
|
16
|
+
*/
|
|
17
|
+
export function parseMaterialFrontmatter(source) {
|
|
3
18
|
const meta = { tags: [] };
|
|
4
|
-
const match =
|
|
19
|
+
const match = source.match(FM_RE);
|
|
5
20
|
if (!match)
|
|
6
|
-
return { meta, rest:
|
|
21
|
+
return { meta, rest: source };
|
|
7
22
|
const body = match[1];
|
|
8
|
-
const
|
|
23
|
+
const fields = {};
|
|
9
24
|
for (const line of body.split('\n')) {
|
|
10
25
|
const kv = line.match(/^(\w+)\s*:\s*(.+)$/);
|
|
11
26
|
if (!kv)
|
|
12
27
|
continue;
|
|
13
|
-
|
|
14
|
-
const value = rawValue.trim();
|
|
15
|
-
if (key === 'tags') {
|
|
16
|
-
meta.tags = parseTagList(value);
|
|
17
|
-
}
|
|
18
|
-
else if (key === 'difficulty') {
|
|
19
|
-
const n = Number(value);
|
|
20
|
-
if (!Number.isNaN(n))
|
|
21
|
-
meta.difficulty = n;
|
|
22
|
-
}
|
|
23
|
-
else if (key === 'hint') {
|
|
24
|
-
const v = value;
|
|
25
|
-
if (v === 'none' || v === 'ghost')
|
|
26
|
-
meta.hint = v;
|
|
27
|
-
}
|
|
28
|
-
else if (key === 'type') {
|
|
29
|
-
if (value === 'word' || value === 'sentence' || value === 'normal') {
|
|
30
|
-
meta.type = value;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else if (key === 'phonetic') {
|
|
34
|
-
meta.phonetic = value;
|
|
35
|
-
}
|
|
36
|
-
else if (key === 'gloss') {
|
|
37
|
-
meta.gloss = value;
|
|
38
|
-
}
|
|
39
|
-
else if (key === 'translation') {
|
|
40
|
-
meta.translation = value;
|
|
41
|
-
}
|
|
28
|
+
fields[kv[1]] = kv[2].trim();
|
|
42
29
|
}
|
|
43
|
-
|
|
30
|
+
if (fields.title)
|
|
31
|
+
meta.title = fields.title;
|
|
32
|
+
if (fields.type)
|
|
33
|
+
meta.type = fields.type;
|
|
34
|
+
if (fields.tags)
|
|
35
|
+
meta.tags = parseTagList(fields.tags);
|
|
36
|
+
if (fields.created)
|
|
37
|
+
meta.created = fields.created;
|
|
38
|
+
if (fields.updated)
|
|
39
|
+
meta.updated = fields.updated;
|
|
40
|
+
return { meta, rest: source.slice(match[0].length) };
|
|
44
41
|
}
|
|
45
42
|
function parseTagList(value) {
|
|
46
43
|
const trimmed = value.trim();
|
|
@@ -56,3 +53,11 @@ function parseTagList(value) {
|
|
|
56
53
|
.map((s) => s.trim())
|
|
57
54
|
.filter(Boolean);
|
|
58
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Kept for backwards compatibility of the barrel export. Card-level
|
|
58
|
+
* frontmatter is no longer used; this always returns empty meta and
|
|
59
|
+
* the original block unchanged.
|
|
60
|
+
*/
|
|
61
|
+
export function parseFrontmatter(block) {
|
|
62
|
+
return { meta: { tags: [] }, rest: block };
|
|
63
|
+
}
|
|
@@ -1,42 +1,192 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseMaterialFrontmatter } from './frontmatter.js';
|
|
2
2
|
import { textToCharPlans } from '../keystroke/index.js';
|
|
3
3
|
import { validateTypingText } from '../keystroke/textNormalize.js';
|
|
4
|
-
|
|
4
|
+
import { containsCJK, romanize } from '../romanization/index.js';
|
|
5
5
|
export function parseMaterial(source, filePath = 'inline') {
|
|
6
6
|
const normalized = source.replace(/\r\n/g, '\n').trim() + '\n';
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.filter(Boolean);
|
|
7
|
+
const { meta: materialMeta, rest } = parseMaterialFrontmatter(normalized);
|
|
8
|
+
const materialType = materialMeta.type ?? 'article';
|
|
9
|
+
const rawCards = splitByH1(rest);
|
|
11
10
|
const cards = [];
|
|
12
|
-
for (let i = 0; i <
|
|
13
|
-
const card = parseCard(
|
|
11
|
+
for (let i = 0; i < rawCards.length; i++) {
|
|
12
|
+
const card = parseCard(rawCards[i], materialType, filePath, i);
|
|
14
13
|
if (card)
|
|
15
|
-
cards.push(card);
|
|
14
|
+
cards.push(attachRomanization(card));
|
|
16
15
|
}
|
|
17
|
-
return { filePath, cards };
|
|
16
|
+
return { filePath, meta: materialMeta, cards };
|
|
18
17
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Split the material body (after frontmatter) into per-card chunks,
|
|
20
|
+
* one per H1. Each chunk starts with its `# heading` line and includes
|
|
21
|
+
* everything up to the next H1 (or end-of-file).
|
|
22
|
+
*/
|
|
23
|
+
function splitByH1(body) {
|
|
24
|
+
const lines = body.split('\n');
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let current = null;
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
if (/^#\s+/.test(line)) {
|
|
29
|
+
if (current)
|
|
30
|
+
chunks.push(current.join('\n').trim());
|
|
31
|
+
current = [line];
|
|
32
|
+
}
|
|
33
|
+
else if (current) {
|
|
34
|
+
current.push(line);
|
|
35
|
+
}
|
|
36
|
+
// Lines before the first H1 are ignored (material-level prose).
|
|
37
|
+
}
|
|
38
|
+
if (current)
|
|
39
|
+
chunks.push(current.join('\n').trim());
|
|
40
|
+
return chunks.filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* If the card's `answer` contains any Han character, attach a
|
|
44
|
+
* romanization (pinyin) so the typing engine has a keyboard-typable
|
|
45
|
+
* target. Non-CJK cards get nothing (fields stay undefined).
|
|
46
|
+
*/
|
|
47
|
+
function attachRomanization(card) {
|
|
48
|
+
if (!containsCJK(card.answer))
|
|
49
|
+
return card;
|
|
50
|
+
const rom = romanize(card.answer);
|
|
51
|
+
if (!rom.text)
|
|
52
|
+
return card;
|
|
53
|
+
return {
|
|
54
|
+
...card,
|
|
55
|
+
romanization: rom.text,
|
|
56
|
+
glyphMap: rom.glyphMap,
|
|
57
|
+
romanizationAccepted: rom.accepted,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function parseCard(chunk, materialType, filePath, index) {
|
|
61
|
+
const lines = chunk.split('\n');
|
|
62
|
+
const h1Line = lines[0];
|
|
63
|
+
const titleMatch = h1Line.match(/^#\s+(.*)$/);
|
|
64
|
+
if (!titleMatch)
|
|
65
|
+
return null;
|
|
66
|
+
const title = titleMatch[1].trim();
|
|
67
|
+
if (!title)
|
|
24
68
|
return null;
|
|
25
|
-
const
|
|
69
|
+
const body = lines.slice(1).join('\n').trim();
|
|
70
|
+
switch (materialType) {
|
|
71
|
+
case 'word':
|
|
72
|
+
case 'sentence':
|
|
73
|
+
return buildAnnotatedCard(title, body, filePath, index);
|
|
74
|
+
case 'qa':
|
|
75
|
+
case 'code':
|
|
76
|
+
return buildQACard(title, body, filePath, index);
|
|
77
|
+
case 'article':
|
|
78
|
+
case 'snippet':
|
|
79
|
+
default:
|
|
80
|
+
return buildArticleCard(title, body, filePath, index);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* word / sentence: H1 is the typing target. H2/H3/H4 are optional
|
|
85
|
+
* single-line annotation slots.
|
|
86
|
+
*/
|
|
87
|
+
function buildAnnotatedCard(title, body, filePath, index) {
|
|
88
|
+
const validated = validateTypingText(title);
|
|
89
|
+
const answer = validated.text;
|
|
90
|
+
if (!answer)
|
|
91
|
+
return null;
|
|
92
|
+
const h2 = extractHeadingContent(body, 2);
|
|
93
|
+
const h3 = extractHeadingContent(body, 3);
|
|
94
|
+
const h4 = extractHeadingContent(body, 4);
|
|
95
|
+
const charPlans = textToCharPlans(answer);
|
|
96
|
+
return {
|
|
97
|
+
id: `${filePath}#${index}`,
|
|
98
|
+
title: answer,
|
|
99
|
+
answer,
|
|
100
|
+
phonetic: h2 || undefined,
|
|
101
|
+
gloss: h3 || undefined,
|
|
102
|
+
image: h4 || undefined,
|
|
103
|
+
charPlans,
|
|
104
|
+
unknownChars: validated.unknowns,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* article: H1 is the section heading; the entire body under H1 is
|
|
109
|
+
* the typing target. Images and other non-text markdown are stripped
|
|
110
|
+
* from the typing text but preserved in `question` so the renderer
|
|
111
|
+
* can show them alongside the arena.
|
|
112
|
+
*/
|
|
113
|
+
function buildArticleCard(title, body, filePath, index) {
|
|
114
|
+
const rawTarget = stripImages(body).trim();
|
|
115
|
+
if (!rawTarget)
|
|
116
|
+
return null;
|
|
117
|
+
const validated = validateTypingText(rawTarget);
|
|
118
|
+
const answer = validated.text;
|
|
119
|
+
if (!answer)
|
|
120
|
+
return null;
|
|
121
|
+
const charPlans = textToCharPlans(answer);
|
|
122
|
+
return {
|
|
123
|
+
id: `${filePath}#${index}`,
|
|
124
|
+
title,
|
|
125
|
+
answer,
|
|
126
|
+
// `question` carries the raw body so images render in the card
|
|
127
|
+
// shell alongside the arena. The arena itself gets `answer`, which
|
|
128
|
+
// is the images-stripped normalized text.
|
|
129
|
+
question: body,
|
|
130
|
+
charPlans,
|
|
131
|
+
unknownChars: validated.unknowns,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* qa / code: H1 is the card heading; `## Q` marks the prompt; `## A`
|
|
136
|
+
* marks the answer (typing target). Both sub-sections are captured as
|
|
137
|
+
* raw markdown; the answer is stripped of image markdown for typing.
|
|
138
|
+
*/
|
|
139
|
+
function buildQACard(title, body, filePath, index) {
|
|
140
|
+
const qaMatch = body.match(/##\s*Q\b\s*\n([\s\S]*?)(?=\n##\s*A\b|$)/);
|
|
141
|
+
const aMatch = body.match(/##\s*A\b\s*\n([\s\S]*)$/);
|
|
142
|
+
if (!aMatch)
|
|
143
|
+
return null;
|
|
144
|
+
const question = qaMatch ? qaMatch[1].trim() : '';
|
|
26
145
|
const rawAnswer = aMatch[1].trim();
|
|
27
|
-
|
|
146
|
+
const targetText = extractTypingText(rawAnswer);
|
|
147
|
+
if (!targetText)
|
|
28
148
|
return null;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const charPlans = textToCharPlans(
|
|
149
|
+
const validated = validateTypingText(targetText);
|
|
150
|
+
const answer = validated.text;
|
|
151
|
+
if (!answer)
|
|
152
|
+
return null;
|
|
153
|
+
const charPlans = textToCharPlans(answer);
|
|
34
154
|
return {
|
|
35
155
|
id: `${filePath}#${index}`,
|
|
156
|
+
title,
|
|
157
|
+
answer,
|
|
36
158
|
question,
|
|
37
|
-
answer: validated.text,
|
|
38
|
-
meta,
|
|
39
159
|
charPlans,
|
|
40
160
|
unknownChars: validated.unknowns,
|
|
41
161
|
};
|
|
42
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the inline text of the first H2/H3/H4 in a block, `null` if
|
|
165
|
+
* absent. Only same-line content is returned — H4 for word cards is
|
|
166
|
+
* expected to be a single `` on the heading line.
|
|
167
|
+
*/
|
|
168
|
+
function extractHeadingContent(body, level) {
|
|
169
|
+
const hashes = '#'.repeat(level);
|
|
170
|
+
const re = new RegExp(`^${hashes}\\s+(.*)$`, 'm');
|
|
171
|
+
const m = body.match(re);
|
|
172
|
+
return m ? m[1].trim() : null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Strip markdown images so the typing target contains only characters
|
|
176
|
+
* the user can actually type. Fenced code fences themselves are
|
|
177
|
+
* removed (backticks aren't typed), but their contents are kept.
|
|
178
|
+
*/
|
|
179
|
+
function extractTypingText(md) {
|
|
180
|
+
return stripFencedCodeMarkers(stripImages(md)).trim();
|
|
181
|
+
}
|
|
182
|
+
function stripImages(md) {
|
|
183
|
+
return md.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Remove the ``` fences around fenced code blocks so the code body
|
|
187
|
+
* itself becomes the typing target. The optional language tag on the
|
|
188
|
+
* opening fence is also dropped.
|
|
189
|
+
*/
|
|
190
|
+
function stripFencedCodeMarkers(md) {
|
|
191
|
+
return md.replace(/```[^\n]*\n?/g, '').replace(/```\s*$/g, '');
|
|
192
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* romanization — turn CJK text into a keyboard-typable roman-alphabet
|
|
3
|
+
* form so users can practice against the actual key sequence (pinyin),
|
|
4
|
+
* not the composed glyph they can only produce via an IME.
|
|
5
|
+
*
|
|
6
|
+
* Currently supports Chinese via `pinyin-pro`. The field name and API
|
|
7
|
+
* are neutral so we can plug in romaji / hangul romanization later
|
|
8
|
+
* behind the same abstraction.
|
|
9
|
+
*
|
|
10
|
+
* Output:
|
|
11
|
+
* text — the roman string (typing target)
|
|
12
|
+
* glyphMap[i] — index into the original text that romanChar `i`
|
|
13
|
+
* belongs to. Non-CJK chars are 1:1 self-mapped;
|
|
14
|
+
* each CJK char's pinyin syllable + a trailing space
|
|
15
|
+
* all map back to that single CJK char.
|
|
16
|
+
*
|
|
17
|
+
* Punctuation:
|
|
18
|
+
* Full-width CJK punctuation (`,` `。` `;` etc.) is folded to its
|
|
19
|
+
* ASCII equivalent (`,` `.` `;`) so the user can actually type it
|
|
20
|
+
* without switching to a CJK IME.
|
|
21
|
+
*/
|
|
22
|
+
import { pinyin } from 'pinyin-pro';
|
|
23
|
+
/** True if the string contains any CJK Han character. */
|
|
24
|
+
const HAN_RE = /\p{Script=Han}/u;
|
|
25
|
+
export function containsCJK(s) {
|
|
26
|
+
return HAN_RE.test(s);
|
|
27
|
+
}
|
|
28
|
+
/** Full-width CJK punctuation → ASCII fold table. */
|
|
29
|
+
const PUNCT_FOLD = {
|
|
30
|
+
',': ',',
|
|
31
|
+
'。': '.',
|
|
32
|
+
';': ';',
|
|
33
|
+
':': ':',
|
|
34
|
+
'?': '?',
|
|
35
|
+
'!': '!',
|
|
36
|
+
'、': ',',
|
|
37
|
+
'(': '(',
|
|
38
|
+
')': ')',
|
|
39
|
+
'「': '"',
|
|
40
|
+
'」': '"',
|
|
41
|
+
'『': '"',
|
|
42
|
+
'』': '"',
|
|
43
|
+
'“': '"',
|
|
44
|
+
'”': '"',
|
|
45
|
+
'‘': "'",
|
|
46
|
+
'’': "'",
|
|
47
|
+
'《': '<',
|
|
48
|
+
'》': '>',
|
|
49
|
+
'·': '.',
|
|
50
|
+
'—': '-',
|
|
51
|
+
'…': '.',
|
|
52
|
+
'~': '~',
|
|
53
|
+
};
|
|
54
|
+
function foldPunct(ch) {
|
|
55
|
+
return PUNCT_FOLD[ch] ?? ch;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the romanization + glyph map for a piece of mixed CJK/ASCII
|
|
59
|
+
* text. Multi-reading Hanzi get their alternate pinyins folded into
|
|
60
|
+
* `accepted[i]` so any valid reading types-through cleanly.
|
|
61
|
+
*/
|
|
62
|
+
export function romanize(text) {
|
|
63
|
+
const chars = Array.from(text);
|
|
64
|
+
const out = [];
|
|
65
|
+
const map = [];
|
|
66
|
+
const accepted = [];
|
|
67
|
+
for (let i = 0; i < chars.length; i++) {
|
|
68
|
+
const ch = chars[i];
|
|
69
|
+
if (HAN_RE.test(ch)) {
|
|
70
|
+
// Default reading — drives the visible text and the syllable
|
|
71
|
+
// length. Multi-reading Hanzi additionally contribute their
|
|
72
|
+
// letters into `accepted` at aligned positions.
|
|
73
|
+
const defaultSyl = pinyin(ch, {
|
|
74
|
+
toneType: 'none',
|
|
75
|
+
type: 'string',
|
|
76
|
+
v: true,
|
|
77
|
+
});
|
|
78
|
+
let readings;
|
|
79
|
+
if (!defaultSyl || defaultSyl === ch) {
|
|
80
|
+
// pinyin-pro has no entry for this glyph. Emit a single '?'
|
|
81
|
+
// placeholder so downstream indexing stays aligned; consumers
|
|
82
|
+
// can accept any letter here (rare chars are effectively
|
|
83
|
+
// free-pass).
|
|
84
|
+
readings = ['?'];
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const multi = pinyin(ch, {
|
|
88
|
+
toneType: 'none',
|
|
89
|
+
type: 'string',
|
|
90
|
+
v: true,
|
|
91
|
+
multiple: true,
|
|
92
|
+
});
|
|
93
|
+
// Some pinyin-pro versions return the multi-reading string
|
|
94
|
+
// as space-separated syllables. Split and dedupe. Always keep
|
|
95
|
+
// defaultSyl first so `text` uses it.
|
|
96
|
+
const parts = typeof multi === 'string'
|
|
97
|
+
? multi.split(' ').map((s) => s.trim()).filter(Boolean)
|
|
98
|
+
: [defaultSyl];
|
|
99
|
+
readings = Array.from(new Set([defaultSyl, ...parts]));
|
|
100
|
+
}
|
|
101
|
+
const primary = readings[0];
|
|
102
|
+
for (let k = 0; k < primary.length; k++) {
|
|
103
|
+
const c = primary[k];
|
|
104
|
+
out.push(c);
|
|
105
|
+
map.push(i);
|
|
106
|
+
// Union of the k-th letter of every reading whose length > k.
|
|
107
|
+
const set = new Set();
|
|
108
|
+
if (primary === '?') {
|
|
109
|
+
// Rare-char slot: accept ANY letter (represented by empty
|
|
110
|
+
// set treated as wildcard by consumers).
|
|
111
|
+
set.add('*');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
for (const r of readings) {
|
|
115
|
+
if (k < r.length)
|
|
116
|
+
set.add(r[k]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
accepted.push(set);
|
|
120
|
+
}
|
|
121
|
+
// Separator space between syllables.
|
|
122
|
+
out.push(' ');
|
|
123
|
+
map.push(i);
|
|
124
|
+
accepted.push(new Set([' ']));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const folded = foldPunct(ch);
|
|
128
|
+
out.push(folded);
|
|
129
|
+
map.push(i);
|
|
130
|
+
accepted.push(new Set([folded]));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Trim trailing separator.
|
|
134
|
+
while (out.length > 0 &&
|
|
135
|
+
out[out.length - 1] === ' ' &&
|
|
136
|
+
map[map.length - 1] === chars.length - 1) {
|
|
137
|
+
out.pop();
|
|
138
|
+
map.pop();
|
|
139
|
+
accepted.pop();
|
|
140
|
+
}
|
|
141
|
+
return { text: out.join(''), glyphMap: map, accepted };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if `ch` is acceptable at position `i` in a romanization.
|
|
145
|
+
* Handles the wildcard '*' used for rare-char placeholders.
|
|
146
|
+
*/
|
|
147
|
+
export function isAccepted(accepted, i, ch) {
|
|
148
|
+
const set = accepted[i];
|
|
149
|
+
if (!set)
|
|
150
|
+
return false;
|
|
151
|
+
return set.has('*') || set.has(ch);
|
|
152
|
+
}
|