@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
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { t } from '../../i18n/index.js';
|
|
2
|
-
import { messages } from '
|
|
3
|
-
import {
|
|
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('
|
|
8
|
-
describe: () => t('
|
|
6
|
+
messages: lintMessages,
|
|
7
|
+
summary: () => t('content.lint.summary'),
|
|
8
|
+
describe: () => t('content.lint.description'),
|
|
9
9
|
options: [
|
|
10
|
-
{ flags: '--repo <path>', describe: () => t('
|
|
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('
|
|
14
|
-
{ command: 'qtype check --
|
|
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: ['
|
|
17
|
+
seeAlso: ['content lint', 'content validate', 'publish build'],
|
|
17
18
|
action: async (opts) => {
|
|
18
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
52
|
+
code: 'material.unknownChars',
|
|
48
53
|
path: rel,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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, {
|
|
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
|
|
26
|
+
const materials = walkMaterial(root);
|
|
29
27
|
// ── Filename uniqueness (case-insensitive) ──────────────
|
|
30
|
-
|
|
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
|
|
33
|
-
|
|
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 !==
|
|
36
|
+
if (prior && prior !== file.rel) {
|
|
39
37
|
errors.push({
|
|
40
38
|
code: 'duplicate.file',
|
|
41
|
-
path:
|
|
42
|
-
message: t('content.validate.err.duplicateFile', { file:
|
|
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,
|
|
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
|
|
55
|
-
const rel =
|
|
56
|
-
const src =
|
|
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
|
|
76
|
-
|
|
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
|
-
//
|
|
97
|
-
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
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,
|
|
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(
|
|
91
|
+
fs.writeFileSync(outPath, JSON.stringify(blob));
|
|
74
92
|
}
|
|
75
93
|
else {
|
|
76
|
-
fs.writeFileSync(
|
|
94
|
+
fs.writeFileSync(outPath, m.body);
|
|
77
95
|
}
|
|
78
|
-
entries.push({
|
|
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 `` 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, '&')
|
|
226
|
+
.replace(/</g, '<')
|
|
227
|
+
.replace(/>/g, '>')
|
|
228
|
+
.replace(/"/g, '"');
|
|
229
|
+
const withTitle = html.replace(/<title>[^<]*<\/title>/, `<title>${escaped}</title>`);
|
|
230
|
+
// Add or replace data-qtype-repo-name on the root <html> tag.
|
|
231
|
+
// Two branches so a re-run cleanly overwrites an existing attribute.
|
|
232
|
+
const rewritten = /<html\b[^>]*\bdata-qtype-repo-name=/.test(withTitle)
|
|
233
|
+
? withTitle.replace(/(<html\b[^>]*\bdata-qtype-repo-name=)"[^"]*"/, `$1"${escaped}"`)
|
|
234
|
+
: withTitle.replace(/<html\b/, `<html data-qtype-repo-name="${escaped}"`);
|
|
235
|
+
if (rewritten !== html)
|
|
236
|
+
fs.writeFileSync(indexPath, rewritten);
|
|
237
|
+
}
|
|
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)) {
|