From 3fe4275b2482d8b1e59a8780fd4a5508f2f8c0a8 Mon Sep 17 00:00:00 2001 From: David McArthur Date: Mon, 14 Oct 2024 10:11:18 +0100 Subject: [PATCH 1/4] new document design --- .github/workflows/publish.yml | 16 +- index.html | 27 +- package-lock.json | 271 +- package.json | 6 +- packages/export/package.json | 8 +- packages/export/src/create-test-bundle.ts | 50 + packages/export/src/index.ts | 9 +- packages/export/test.html | 2850 +++++++++++++++++ packages/processor/package.json | 6 + .../processor/src/__test__/headings.test.ts | 200 +- .../processor/src/__test__/references.test.ts | 6 +- .../processor/src/__test__/sections.test.ts | 6 +- .../processor/src/__test__/sidenotes.test.ts | 4 +- packages/processor/src/__test__/title.test.ts | 2 +- packages/processor/src/index.ts | 11 +- .../processor/src/latex-to-markdown/index.ts | 4 +- .../markdown-to-mdx/hast-transforms/index.ts | 70 +- .../hast-transforms/link-headings.ts | 25 + .../hast-transforms/wrapper.ts | 12 +- .../processor/src/markdown-to-mdx/index.ts | 47 +- .../mdast-transforms/heading-attributes.ts | 40 - .../{heading-increments.ts => headings.ts} | 73 +- .../markdown-to-mdx/mdast-transforms/index.ts | 42 +- .../mdast-transforms/sectionize.ts | 64 + .../markdown-to-mdx/mdx-handlers/index.tsx | 16 +- .../mdx-handlers/mathjax/index.tsx | 76 + .../mdx-handlers/mathjax/litedom.ts | 84 + .../toc-highlight/intersection-observer.tsx | 54 + .../mdx-handlers/toc-highlight/section.tsx | 21 + .../toc-highlight/toc-highlight-provider.tsx | 56 + .../toc-highlight/toc-list-item.tsx | 16 + .../processor/src/markdown-to-mdx/sidebar.ts | 90 + .../utils/__test__/heading-counter.test.ts | 75 +- .../utils/__test__/theorem-counter.test.ts | 36 + .../markdown-to-mdx/utils/heading-counter.ts | 14 +- .../src/test-utils/create-e2e-test-bundle.ts | 1 + packages/runtime/index.html | 2 +- packages/runtime/package.json | 8 +- packages/runtime/src/article.tsx | 27 +- packages/runtime/src/components/hamburger.tsx | 8 +- packages/runtime/src/constants/readability.ts | 10 +- packages/runtime/src/providers/index.tsx | 7 +- .../src/providers/view-options-provider.tsx | 27 +- packages/runtime/src/runtime.tsx | 41 +- packages/runtime/src/sidebar.tsx | 90 - packages/runtime/src/sidebar/index.tsx | 95 + packages/runtime/src/sidebar/range-input.tsx | 79 + packages/runtime/src/sidebar/theme.tsx | 76 + packages/runtime/src/sidebar/view-options.tsx | 76 + packages/runtime/src/styles/article.scss | 20 +- packages/runtime/src/styles/index.scss | 59 +- .../runtime/src/styles/nouveaux/index.scss | 214 ++ packages/runtime/src/styles/odl/nav.scss | 45 +- .../runtime/src/styles/odl/sidenotes.scss | 2 +- packages/runtime/src/styles/range-input.scss | 171 + packages/runtime/src/styles/scrollbars.scss | 52 +- packages/runtime/src/styles/sidebar.scss | 156 +- packages/runtime/src/styles/sidenote.scss | 2 +- packages/runtime/src/styles/structure.scss | 82 +- packages/runtime/src/styles/variables.scss | 2 +- packages/runtime/src/view-options/index.tsx | 27 - .../runtime/src/view-options/readability.tsx | 130 - packages/runtime/src/view-options/themes.tsx | 69 - packages/runtime/src/vite-env.d.ts | 4 + packages/runtime/vite.config.ts | 31 + packages/use-local-storage/src/index.ts | 32 +- public/tauri.svg | 6 + public/vite.svg | 1 + sidenote | 129 + src-tauri/capabilities/default.json | 3 + src-tauri/resources/cm-serif-bold-italic.woff | Bin 37472 -> 0 bytes src-tauri/resources/cm-serif-bold.woff | Bin 30276 -> 0 bytes src-tauri/resources/cm-serif-italic.woff | Bin 37332 -> 0 bytes src-tauri/resources/cm-serif.woff | Bin 32648 -> 0 bytes src-tauri/resources/index.css | 1 - src-tauri/resources/runtime.js | 497 --- src-tauri/resources/runtime.js.map | 1 - src-tauri/tauri.conf.json | 5 +- src/app.tsx | 6 +- src/cli.ts | 12 +- src/header/index.tsx | 4 +- src/main.tsx | 25 +- src/styles.scss | 18 +- src/styles.tsx | 1 - tsconfig.json | 2 +- vite.config.ts | 4 + 86 files changed, 5326 insertions(+), 1421 deletions(-) create mode 100644 packages/export/src/create-test-bundle.ts create mode 100644 packages/export/test.html create mode 100644 packages/processor/src/markdown-to-mdx/hast-transforms/link-headings.ts delete mode 100644 packages/processor/src/markdown-to-mdx/mdast-transforms/heading-attributes.ts rename packages/processor/src/markdown-to-mdx/mdast-transforms/{heading-increments.ts => headings.ts} (76%) create mode 100644 packages/processor/src/markdown-to-mdx/mdast-transforms/sectionize.ts create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/index.tsx create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/litedom.ts create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/intersection-observer.tsx create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/section.tsx create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-highlight-provider.tsx create mode 100644 packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-list-item.tsx create mode 100644 packages/processor/src/markdown-to-mdx/sidebar.ts create mode 100644 packages/processor/src/markdown-to-mdx/utils/__test__/theorem-counter.test.ts delete mode 100644 packages/runtime/src/sidebar.tsx create mode 100644 packages/runtime/src/sidebar/index.tsx create mode 100644 packages/runtime/src/sidebar/range-input.tsx create mode 100644 packages/runtime/src/sidebar/theme.tsx create mode 100644 packages/runtime/src/sidebar/view-options.tsx create mode 100644 packages/runtime/src/styles/nouveaux/index.scss create mode 100644 packages/runtime/src/styles/range-input.scss delete mode 100644 packages/runtime/src/view-options/index.tsx delete mode 100644 packages/runtime/src/view-options/readability.tsx delete mode 100644 packages/runtime/src/view-options/themes.tsx create mode 100644 public/tauri.svg create mode 100644 public/vite.svg create mode 100644 sidenote delete mode 100644 src-tauri/resources/cm-serif-bold-italic.woff delete mode 100644 src-tauri/resources/cm-serif-bold.woff delete mode 100644 src-tauri/resources/cm-serif-italic.woff delete mode 100644 src-tauri/resources/cm-serif.woff delete mode 100644 src-tauri/resources/index.css delete mode 100644 src-tauri/resources/runtime.js delete mode 100644 src-tauri/resources/runtime.js.map diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4ffd808..73edc42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -78,6 +78,17 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + + - name: install dependencies (windows only) + if: matrix.platform == 'windows-latest' + shell: bash + env: + WINDOWS_SIGN_COMMAND: trusted-signing-cli -e https://weu.codesigning.azure.net/ -a ${{ secrets.AZURE_CODE_SIGNING_NAME }} -c ${{ secrets.AZURE_CERT_PROFILE_NAME }} %1 + run: | + cd "$GITHUB_WORKSPACE" + cat './src-tauri/tauri.conf.json' | jq '.bundle .windows += {"signCommand": env.WINDOWS_SIGN_COMMAND}' > './src-tauri/temp.json' && mv './src-tauri/temp.json' './src-tauri/tauri.conf.json' + cargo install trusted-signing-cli + - name: install frontend dependencies run: npm ci - uses: tauri-apps/tauri-action@v0 @@ -91,6 +102,9 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + AZURE_TENANT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_SECRET }} with: releaseId: ${{ needs.create-release.outputs.release-id }} args: ${{ matrix.args }} @@ -110,7 +124,7 @@ jobs: RELEASE_ID: ${{ needs.create-release.outputs.release-id }} publish-docs: - needs: version-check + needs: [version-check, post-release] if: needs.version-check.outputs.has-version-change == 'true' runs-on: ubuntu-20.04 permissions: diff --git a/index.html b/index.html index e0cb85c..d054217 100644 --- a/index.html +++ b/index.html @@ -1,18 +1,15 @@ - - - -ISOS - - - - -
- - + + + + ISOS + + + +
+ + diff --git a/package-lock.json b/package-lock.json index f9c77aa..fe2a50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,11 @@ "@tauri-apps/plugin-shell": ">=2.0.0-rc.0", "@tauri-apps/plugin-window-state": "^2.0.0-rc", "classnames": "^2.5.1", + "mathjax-fira-font": "^4.0.0-beta.7", + "mathjax-full": "^4.0.0-beta.7", "preact": "^10.23.2", "rehype-document": "^7.0.3", "rehype-format": "^5.0.0", - "rehype-mathjax": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", @@ -6219,6 +6220,15 @@ "vite": ">=3.2.7" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -6482,18 +6492,6 @@ "dev": true, "license": "MIT" }, - "node_modules/better-react-mathjax": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz", - "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==", - "license": "MIT", - "dependencies": { - "mathjax-full": "^3.2.2" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -8621,6 +8619,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-body-ok-link": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.0.tgz", @@ -9930,18 +9941,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mathjax-fira-font": { + "version": "4.0.0-beta.7", + "resolved": "https://registry.npmjs.org/mathjax-fira-font/-/mathjax-fira-font-4.0.0-beta.7.tgz", + "integrity": "sha512-Jg8RYMPx0BMriIoDNmuNsN+lNa1F9udGm+9hl7IkziPsCpgKIRH/mWS1OGZqIwlbUs32KQQvI4cV00Gia47eIg==", + "license": "Apache-2.0" + }, "node_modules/mathjax-full": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", - "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "version": "4.0.0-beta.7", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-4.0.0-beta.7.tgz", + "integrity": "sha512-zJUaqak6XPWyY9IW/eurxDXNGrhdYBllciUxJMrsw3TqchH3EHHVCEhKYGDcEmMZTtYjaz3rOa5RLvkBhCF86A==", "license": "Apache-2.0", "dependencies": { - "esm": "^3.2.25", - "mhchemparser": "^4.1.0", - "mj-context-menu": "^0.6.1", - "speech-rule-engine": "^4.0.6" + "@xmldom/xmldom": "^0.8.10", + "mathjax-modern-font": "^4.0.0-beta.5", + "mhchemparser": "^4.2.1", + "mj-context-menu": "^0.9.1", + "speech-rule-engine": "^4.1.0-beta.11", + "wicked-good-xpath": "^1.3.0" } }, + "node_modules/mathjax-modern-font": { + "version": "4.0.0-beta.7", + "resolved": "https://registry.npmjs.org/mathjax-modern-font/-/mathjax-modern-font-4.0.0-beta.7.tgz", + "integrity": "sha512-mGrlxuFPRoHpGYyRaXJpfyM9k77XCpH11lIVLO7+VNTFJXIeqnMXv+AGFiYL/GftQpdhPlCg4zqd/JXlse9S0A==", + "license": "Apache-2.0" + }, "node_modules/mdast-util-directive": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", @@ -10981,9 +11006,9 @@ } }, "node_modules/mj-context-menu": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.9.1.tgz", + "integrity": "sha512-ECPcVXZFRfeYOxb1MWGzctAtnQcZ6nRucE3orfkKX7t/KE2mlXO2K/bq4BcCGOuhdz3Wg2BZDy2S8ECK73/iIw==", "license": "Apache-2.0" }, "node_modules/mrmime": { @@ -11595,9 +11620,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, "funding": [ { @@ -11633,6 +11658,15 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/preact-intersection-observer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/preact-intersection-observer/-/preact-intersection-observer-2.3.6.tgz", + "integrity": "sha512-f+1SRbyz3+nrhvzumLxrRKUV8LktAqGd5U3PolcJHPHcqztlHlCHgtbNRurGTX0DH5zlYoe1hrT2zk3Tv6eLbg==", + "license": "MIT", + "peerDependencies": { + "preact": "10.x" + } + }, "node_modules/preact-render-to-string": { "version": "6.5.10", "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.10.tgz", @@ -11891,6 +11925,24 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-document": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/rehype-document/-/rehype-document-7.0.3.tgz", @@ -11977,6 +12029,47 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-mathjax/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/rehype-mathjax/node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "license": "Apache-2.0", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, + "node_modules/rehype-mathjax/node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", + "license": "Apache-2.0" + }, + "node_modules/rehype-mathjax/node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "license": "Apache-2.0", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, "node_modules/rehype-minify-whitespace": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.0.tgz", @@ -12352,6 +12445,78 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-sectionize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-sectionize/-/remark-sectionize-2.0.0.tgz", + "integrity": "sha512-B+sCNNQroXybxX5Gwu9xbkjFIgK6vHMwbgPM/CEzQTP2ODxUiBsQRBjoSC6XR+yPOkgHvXV83HWCNA8IZuvJKg==", + "license": "MIT", + "dependencies": { + "unist-util-find-after": "^4.0.1", + "unist-util-visit": "^4.1.2" + } + }, + "node_modules/remark-sectionize/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/remark-sectionize/node_modules/unist-util-find-after": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz", + "integrity": "sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-sectionize/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-sectionize/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-sectionize/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-stringify": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", @@ -12678,26 +12843,35 @@ } }, "node_modules/speech-rule-engine": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", - "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "version": "4.1.0-beta.11", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.0-beta.11.tgz", + "integrity": "sha512-mCBdj/2jDSGqbsmsYDgFlyCiQ2oeEAue86G9RvPTBVjVzOE8kBi8n/7btZa+F1q2E2cmEgjpnc0EJ95Z7q5oXw==", "license": "Apache-2.0", "dependencies": { - "commander": "9.2.0", - "wicked-good-xpath": "1.3.0", - "xmldom-sre": "0.1.31" + "@xmldom/xmldom": "0.9.0-beta.8", + "commander": "12.0.0", + "wicked-good-xpath": "1.3.0" }, "bin": { "sre": "bin/sre" } }, + "node_modules/speech-rule-engine/node_modules/@xmldom/xmldom": { + "version": "0.9.0-beta.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.0-beta.8.tgz", + "integrity": "sha512-Q5bFbYxRJKTYP7S1a0HIlumTmJRHHMGrNvBp8F1mUEyyGTeCs0g8+FKAaA6tU+YFsZgHKA0eRKzZhYdhpgAHAw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/speech-rule-engine/node_modules/commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=18" } }, "node_modules/sprintf-js": { @@ -13646,14 +13820,14 @@ } }, "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", + "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -14271,7 +14445,10 @@ }, "packages/export": { "name": "@isos/export", - "version": "0.0.1" + "version": "0.0.1", + "devDependencies": { + "vite-node": "^2.0.5" + } }, "packages/fs": { "name": "@isos/fs", @@ -14306,9 +14483,14 @@ "@unified-latex/unified-latex-util-parse": "^1.7.1", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", + "hast-util-from-dom": "^5.0.0", + "mathjax-full": "^4.0.0-beta.6", "mdast-util-toc": "^7.1.0", "mime": "^4.0.3", "pathe": "^1.1.2", + "preact-intersection-observer": "^2.3.6", + "rehype-autolink-headings": "^7.1.0", + "rehype-mathjax": "^6.0.0", "rehype-parse": "^9.0.0", "rehype-remark": "^10.0.0", "remark-directive": "^3.0.0", @@ -14316,6 +14498,7 @@ "remark-heading-id": "^1.0.1", "remark-mdx-math-enhanced": "^0.0.1-beta.3", "remark-parse": "^11.0.0", + "remark-sectionize": "^2.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "vfile-matter": "^5.0.0", @@ -14331,12 +14514,8 @@ "dependencies": { "@mdx-js/mdx": "^3.0.1", "@mdx-js/preact": "^3.0.1", - "better-react-mathjax": "^2.0.3", "classnames": "^2.5.1", - "mdast-util-toc": "^7.1.0", - "preact": "^10.20.2", - "rehype-mathjax": "^6.0.0", - "remark-math": "^6.0.0" + "preact": "^10.20.2" }, "devDependencies": { "@babel/core": "^7.24.4", diff --git a/package.json b/package.json index a78cdeb..2e03df8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "local-build": "./scripts/local-build/local-build.sh", "test": "vitest", "e2e": "playwright test", - "prettier": "prettier" + "prettier": "prettier", + "test-bundle": "npm -w @isos/runtime run build && npm -w @isos/export run bundle" }, "dependencies": { "@isos/processor": "0.0.1", @@ -26,10 +27,11 @@ "@tauri-apps/plugin-shell": ">=2.0.0-rc.0", "@tauri-apps/plugin-window-state": "^2.0.0-rc", "classnames": "^2.5.1", + "mathjax-fira-font": "^4.0.0-beta.7", + "mathjax-full": "^4.0.0-beta.7", "preact": "^10.23.2", "rehype-document": "^7.0.3", "rehype-format": "^5.0.0", - "rehype-mathjax": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", diff --git a/packages/export/package.json b/packages/export/package.json index c7ed2bf..56699c9 100644 --- a/packages/export/package.json +++ b/packages/export/package.json @@ -8,5 +8,11 @@ ], "exports": [ "./src/index.ts" - ] + ], + "scripts": { + "bundle": "NODE_ENV=test vite-node src/create-test-bundle.ts" + }, + "devDependencies": { + "vite-node": "^2.0.5" + } } diff --git a/packages/export/src/create-test-bundle.ts b/packages/export/src/create-test-bundle.ts new file mode 100644 index 0000000..52e298b --- /dev/null +++ b/packages/export/src/create-test-bundle.ts @@ -0,0 +1,50 @@ +import { createRuntimeHtml } from '.'; +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +import { createContext, inputToMarkdown } from '@isos/processor'; + +// const fonts = [ +// 'modern', // good +// 'asana', +// 'bonum', +// 'dejavu', +// 'pagella', // good +// 'schola', // good +// 'termes', // good +// 'stix2', // good +// 'fira', +// 'euler', +// 'tex', +// ]; + +const ctx = await createContext( + '/Users/staff/Work/latex-experiments/test1/MCA_lecturenotes.tex', +); + +const markdown = await inputToMarkdown(ctx); + +const frontmatter = { + docTitle: 'Test', +}; + +const bundle = { + css: await readFile( + fileURLToPath( + new URL('../../runtime/dist/assets/index.css', import.meta.url), + ), + 'utf-8', + ), + js: await readFile( + fileURLToPath( + new URL('../../runtime/dist/assets/runtime.js', import.meta.url), + ), + 'utf-8', + ), + font: 'termes', +}; + +const filePath = fileURLToPath(new URL('../test.html', import.meta.url)); +const runtime = await createRuntimeHtml(markdown, frontmatter, bundle); + +await writeFile(filePath, runtime); diff --git a/packages/export/src/index.ts b/packages/export/src/index.ts index 3f3e912..b4a8203 100644 --- a/packages/export/src/index.ts +++ b/packages/export/src/index.ts @@ -6,12 +6,13 @@ type FrontMatter = { type RuntimeBundle = { css: string; js: string; + font: string; }; export async function createRuntimeHtml( markdown: string, frontmatter: FrontMatter, - bundle: RuntimeBundle + bundle: RuntimeBundle, ) { return ` @@ -21,6 +22,12 @@ export async function createRuntimeHtml( ${frontmatter.docTitle} + +
+ + + + \ No newline at end of file diff --git a/packages/processor/package.json b/packages/processor/package.json index 413beea..e6d0e84 100644 --- a/packages/processor/package.json +++ b/packages/processor/package.json @@ -14,9 +14,14 @@ "@unified-latex/unified-latex-util-parse": "^1.7.1", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", + "hast-util-from-dom": "^5.0.0", + "mathjax-full": "^4.0.0-beta.6", "mdast-util-toc": "^7.1.0", "mime": "^4.0.3", "pathe": "^1.1.2", + "preact-intersection-observer": "^2.3.6", + "rehype-autolink-headings": "^7.1.0", + "rehype-mathjax": "^6.0.0", "rehype-parse": "^9.0.0", "rehype-remark": "^10.0.0", "remark-directive": "^3.0.0", @@ -24,6 +29,7 @@ "remark-heading-id": "^1.0.1", "remark-mdx-math-enhanced": "^0.0.1-beta.3", "remark-parse": "^11.0.0", + "remark-sectionize": "^2.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "vfile-matter": "^5.0.0", diff --git a/packages/processor/src/__test__/headings.test.ts b/packages/processor/src/__test__/headings.test.ts index d6fac55..f4107bd 100644 --- a/packages/processor/src/__test__/headings.test.ts +++ b/packages/processor/src/__test__/headings.test.ts @@ -75,29 +75,63 @@ test('headings with counters and attributes', async () => { const expected = unindentStringAndTrim(`

Alfa

-

1. Bravo hi

-

1.1. Charlie

-

1.1.1. Delta hi

-
Echo
-
Foxtrot
-
Golf
-
Hotel
-

India

-

2. Juliett

-

Kilo

-

3. Lima

-

3.1. Mike

-

3.1.1. November

-

3.2. Oscar

-

3.2.1. Papa

-

Quebec hi

-

3.2.2. Romeo

+
+

1. Bravo hi

+
+

1.1. Charlie

+
+

1.1.1. Delta hi

+
+
Echo
+
+
Foxtrot
+
+
+
+
Golf
+
+
Hotel
+
+
+
+
+
+
+

India

+
+
+

2. Juliett

+
+

Kilo

+
+
+
+

3. Lima

+
+

3.1. Mike

+
+

3.1.1. November

+
+
+
+

3.2. Oscar

+
+

3.2.1. Papa

+
+
+

Quebec hi

+
+
+

3.2.2. Romeo

+
+
+
`); expect(html).toBe(expected); }); -test('headings on environments', async () => { +test.only('headings on environments', async () => { const latex = ` \\documentclass{amsproc} @@ -331,67 +365,75 @@ test('headings on environments', async () => { // console.log(html); const expectedHtml = unindentStringAndTrim(` -

PART I. DIFFERENTIATION

-

1. Limits

-
-

Lemma 1.1.

-
-
-

Example 1.2. Test

-
-
-

Definition 1.3. Test

-
-
-

Proposition 1.4. Test

-
-

2. Introduction to differentiation

-
-

Definition 2.1. Test

-
-
-

Lemma 2.2. Test

-
-
-

Example 2.3. Test

-
-
-

Solution. Test

-
-
-

Example 2.4. Test

-
-
-

Solution. Test

-
-
-

Example 2.5. Test

-
-
-

Solution. Test

-
-
-

Definition 2.6. Test

-
-
-

Definition 2.7. Test

-
-
-

Example 2.8. Test

-
-
-

Definition 2.9. Test

-
-

3. Power series

-
-

Theorem 3.1. Test

-
-
-

Theorem 3.2. Test

-
-
-

Example 3.3. Test

-
+
+

PART I. DIFFERENTIATION

+
+
+

1. Limits

+
+

Lemma 1.1.

+
+
+

Example 1.2. Test

+
+
+

Definition 1.3. Test

+
+
+

Proposition 1.4. Test

+
+
+
+

2. Introduction to differentiation

+
+

Definition 2.1. Test

+
+
+

Lemma 2.2. Test

+
+
+

Example 2.3. Test

+
+
+

Solution. Test

+
+
+

Example 2.4. Test

+
+
+

Solution. Test

+
+
+

Example 2.5. Test

+
+
+

Solution. Test

+
+
+

Definition 2.6. Test

+
+
+

Definition 2.7. Test

+
+
+

Example 2.8. Test

+
+
+

Definition 2.9. Test

+
+
+
+

3. Power series

+
+

Theorem 3.1. Test

+
+
+

Theorem 3.2. Test

+
+
+

Example 3.3. Test

+
+
`); expect(html).toBe(expectedHtml); diff --git a/packages/processor/src/__test__/references.test.ts b/packages/processor/src/__test__/references.test.ts index e44d17b..6b9e40e 100644 --- a/packages/processor/src/__test__/references.test.ts +++ b/packages/processor/src/__test__/references.test.ts @@ -54,8 +54,10 @@ test('heading references', async () => { const html = await testProcessor.md(markdown); const expectedHtml = unindentStringAndTrim(` -

1. Hello

-

Definition 1 is automatically satisfied.

+
+

1. Hello

+

Definition 1 is automatically satisfied.

+
`); expect(html).toBe(expectedHtml); diff --git a/packages/processor/src/__test__/sections.test.ts b/packages/processor/src/__test__/sections.test.ts index 6c50f57..a4f0fe4 100644 --- a/packages/processor/src/__test__/sections.test.ts +++ b/packages/processor/src/__test__/sections.test.ts @@ -3,7 +3,7 @@ import { expect, test } from 'vitest'; import { unindentStringAndTrim } from '../test-utils/unindent-string'; import { testProcessor } from '../test-utils/unit-test-processor'; -test('fancy section heading', async () => { +test.skip('fancy section heading', async () => { const markdown = await testProcessor.latex(` \\fancysection{Chapter overview} `); @@ -21,7 +21,9 @@ test('fancy section heading', async () => { // console.log(html); const expectedHtml = unindentStringAndTrim(` -

Chapter overview

+
+

Chapter overview

+
`); expect(html).toBe(expectedHtml); diff --git a/packages/processor/src/__test__/sidenotes.test.ts b/packages/processor/src/__test__/sidenotes.test.ts index eeff331..6a29934 100644 --- a/packages/processor/src/__test__/sidenotes.test.ts +++ b/packages/processor/src/__test__/sidenotes.test.ts @@ -61,7 +61,9 @@ test.only('sidenote parsing bug 2', async () => { const expectedHtml = unindentStringAndTrim(`

a

-

Title(sidenote: 1 Maths)

+
+

Title(sidenote: 1 Maths)

+
`); expect(html).toBe(expectedHtml); diff --git a/packages/processor/src/__test__/title.test.ts b/packages/processor/src/__test__/title.test.ts index b8212bf..529b1a0 100644 --- a/packages/processor/src/__test__/title.test.ts +++ b/packages/processor/src/__test__/title.test.ts @@ -3,7 +3,7 @@ import { expect, test } from 'vitest'; import { unindentStringAndTrim } from '../test-utils/unindent-string'; import { testProcessor } from '../test-utils/unit-test-processor'; -test('maths 2 fancytitle', async () => { +test.skip('maths 2 fancytitle', async () => { const markdown = await testProcessor.latex(` \\documentclass{UoG-lecture} diff --git a/packages/processor/src/index.ts b/packages/processor/src/index.ts index 14b17e2..bb0f050 100644 --- a/packages/processor/src/index.ts +++ b/packages/processor/src/index.ts @@ -7,6 +7,15 @@ export { export { inputToMarkdown } from './latex-to-markdown'; -export { markdownToJs, runOptions } from './markdown-to-mdx'; +export { + markdownToJs, + runOptions, + sidebarRunOptions, +} from './markdown-to-mdx'; export { createContext } from './latex-to-markdown/context'; + +export { + TocHighlightProvider, + TocHighlightContext, +} from './markdown-to-mdx'; diff --git a/packages/processor/src/latex-to-markdown/index.ts b/packages/processor/src/latex-to-markdown/index.ts index a3d7032..99d96d1 100644 --- a/packages/processor/src/latex-to-markdown/index.ts +++ b/packages/processor/src/latex-to-markdown/index.ts @@ -18,14 +18,14 @@ import { FileType } from './utils/parse-file-path'; export async function inputToMarkdown( ctx: Context, - options: Partial = {} + options: Partial = {}, ) { const mdast = await getMdast(ctx); const processor = createRemarkProcessor( createMdastTransforms(ctx, { ...defaultOptions, ...options, - }) + }), ); const precompiled = await processor.run(mdast); const markdown = processor.stringify(precompiled as MDastRoot).trim(); diff --git a/packages/processor/src/markdown-to-mdx/hast-transforms/index.ts b/packages/processor/src/markdown-to-mdx/hast-transforms/index.ts index 8c6f15d..bba3519 100644 --- a/packages/processor/src/markdown-to-mdx/hast-transforms/index.ts +++ b/packages/processor/src/markdown-to-mdx/hast-transforms/index.ts @@ -1,8 +1,8 @@ import { Options } from '../options'; +import { ProcessorOptions } from '@mdx-js/mdx'; import { ElementContent } from 'hast'; import { PhrasingContent, Root } from 'mdast'; // import { createSvg } from '../utils/icons'; -// import autolinkHeadings from 'rehype-autolink-headings'; import mathjaxBrowser from 'rehype-mathjax/browser'; // import mathjaxChtml from 'rehype-mathjax/chtml'; import mathjaxSvg from 'rehype-mathjax/svg'; @@ -14,6 +14,12 @@ import { PluggableList, unified } from 'unified'; import { Context } from '../context'; import { createWrapper } from './wrapper'; +export const processorOptions: ProcessorOptions = { + outputFormat: 'function-body', + elementAttributeNameCase: 'html', + providerImportSource: '@mdx-js/preact', +}; + const mathjaxOptions = { // chtml: { // // minScale: 0.8, @@ -32,43 +38,9 @@ const mathjaxOptions = { }, }; -function createRehypeFragmentPlugins( - _ctx: Context, - options: Partial = {} -) { - const plugins: PluggableList = []; - - if (options.mathsAsTex) { - plugins.push([mathjaxBrowser, mathjaxOptions]); - } else { - plugins.push([mathjaxSvg, mathjaxOptions]); - } - - return plugins; -} - export function createRehypePlugins(ctx: Context, options: Options) { const plugins = createRehypeFragmentPlugins(ctx, options); - // plugins.push( - // () => (tree) => { - // console.dir(tree, { depth: null }); - // }, - - // rehypeRaw, - // rehypeSlug, - // [ - // autolinkHeadings, - // // { - // // content: createSvg('link-icon') as any, - // // properties: { className: 'link' }, - // // }, - // ], - // () => (tree) => { - // console.log(JSON.stringify(tree, null, 2)); - // }, - // ); - if (!options.noWrapper) { plugins.push([createWrapper, ctx]); } @@ -78,10 +50,10 @@ export function createRehypePlugins(ctx: Context, options: Options) { export async function toHast( children: PhrasingContent[], ctx: Context, - options?: Partial + options?: Partial, ) { const processor = unified().use( - createRehypeFragmentPlugins(ctx, options) + createRehypeFragmentPlugins(ctx, options), ); const root: Root = { @@ -91,3 +63,27 @@ export async function toHast( const hast = (await processor.run(root)) as Root; return hast.children as ElementContent[]; } + +function createRehypeFragmentPlugins( + _ctx: Context, + options: Partial = {}, +) { + const plugins: PluggableList = [ + // TODO: + // [ + // autolinkHeadings, + // { + // content: createSvg('link-icon') as any, + // properties: { className: 'link' }, + // }, + // ], + ]; + + if (options.mathsAsTex) { + plugins.push([mathjaxBrowser, mathjaxOptions]); + } else { + plugins.push([mathjaxSvg, mathjaxOptions]); + } + + return plugins; +} diff --git a/packages/processor/src/markdown-to-mdx/hast-transforms/link-headings.ts b/packages/processor/src/markdown-to-mdx/hast-transforms/link-headings.ts new file mode 100644 index 0000000..a23794e --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/hast-transforms/link-headings.ts @@ -0,0 +1,25 @@ +import { Root } from 'hast'; +import { visit } from 'unist-util-visit'; + +const titles = ['h2', 'h3']; + +// + +export function linkHeadings() { + return (tree: Root) => { + visit(tree, 'element', (node) => { + if (titles.includes(node.tagName)) { + node.children.unshift({ + type: 'element', + tagName: 'a', + properties: { + className: 'link', + href: '', + }, + children: [], + }); + console.log(node); + } + }); + }; +} diff --git a/packages/processor/src/markdown-to-mdx/hast-transforms/wrapper.ts b/packages/processor/src/markdown-to-mdx/hast-transforms/wrapper.ts index 2fd974d..0d02702 100644 --- a/packages/processor/src/markdown-to-mdx/hast-transforms/wrapper.ts +++ b/packages/processor/src/markdown-to-mdx/hast-transforms/wrapper.ts @@ -1,22 +1,20 @@ -import { ElementContent, Root } from 'hast'; +import { ElementContent, Properties, Root } from 'hast'; import { Context } from '../context'; export function createWrapper(ctx: Context) { return (tree: Root) => { - const className = ['wrapper']; + const properties: Properties = {}; if (ctx.hasSidenotes) { - className.push('has-sidenotes'); + properties.className = 'has-sidenotes'; } tree.children = [ { type: 'element', - tagName: 'div', - properties: { - className, - }, + tagName: 'article', + properties, children: tree.children as ElementContent[], }, ]; diff --git a/packages/processor/src/markdown-to-mdx/index.ts b/packages/processor/src/markdown-to-mdx/index.ts index f44f1ea..b24e720 100644 --- a/packages/processor/src/markdown-to-mdx/index.ts +++ b/packages/processor/src/markdown-to-mdx/index.ts @@ -1,18 +1,19 @@ -import { createRehypePlugins } from './hast-transforms'; +import { createRehypePlugins, processorOptions } from './hast-transforms'; import { markdownToMdast } from './mdast-transforms'; import { useMDXComponents } from './mdx-handlers'; import { Options, defaultOptions } from './options'; -import { - ProcessorOptions, - RunOptions, - createProcessor, -} from '@mdx-js/mdx'; -import { Nodes, Root } from 'mdast'; -import { toc } from 'mdast-util-toc'; +import { RunOptions, createProcessor } from '@mdx-js/mdx'; import { Fragment, jsx, jsxDEV, jsxs } from 'preact/jsx-runtime'; import { createContext } from './context'; +export { + TocHighlightProvider, + TocHighlightContext, +} from './mdx-handlers/toc-highlight/toc-highlight-provider'; + +export { sidebarRunOptions } from './sidebar'; + export const runOptions: RunOptions = { Fragment, useMDXComponents, @@ -25,22 +26,16 @@ export const runOptions: RunOptions = { jsxDEV, }; -const processorOptions: ProcessorOptions = { - outputFormat: 'function-body', - elementAttributeNameCase: 'html', - providerImportSource: '@mdx-js/preact', -}; - export async function markdownToJs( markdown: string, - _options: Partial = {} + _options: Partial = {}, ) { const ctx = createContext(); const options = { ...defaultOptions, ..._options, }; - const mdast = await markdownToMdast(markdown, ctx); + const { mdast, tableOfContents } = await markdownToMdast(markdown, ctx); // console.dir(mdast, { depth: null }); const processor = createProcessor({ @@ -50,24 +45,10 @@ export async function markdownToJs( // @ts-expect-error: mdast is not of type Program const estree = await processor.run(mdast); + const article = processor.stringify(estree); return { - article: processor.stringify(estree), - tableOfContents: await createTableOfContents(mdast as Root), + article, + tableOfContents, }; } - -async function createTableOfContents(mdast: Root) { - const tocMdast = toc(mdast as Nodes, { maxDepth: 3, minDepth: 2 }).map; - - if (tocMdast === undefined) { - return ''; - } - - const processor = createProcessor(processorOptions); - - // @ts-expect-error: mdast is not of type Program - const estree = await processor.run(tocMdast); - - return processor.stringify(estree); -} diff --git a/packages/processor/src/markdown-to-mdx/mdast-transforms/heading-attributes.ts b/packages/processor/src/markdown-to-mdx/mdast-transforms/heading-attributes.ts deleted file mode 100644 index a2ee64f..0000000 --- a/packages/processor/src/markdown-to-mdx/mdast-transforms/heading-attributes.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - hasAttributes, - parseAttributes, -} from '../../shared-utils/parse-heading-attributes'; -import { Heading, Text } from 'mdast'; -import { Node } from 'unist'; -import { visit } from 'unist-util-visit'; - -export function headingAttributes() { - return (tree: Node) => { - visit(tree, 'heading', (node: Heading) => { - transformHeadings(node); - }); - }; -} - -function transformHeadings(node: Heading) { - const lastTextIdx = node.children.findLastIndex( - (o) => o.type === 'text' - ); - const lastTextChild = node.children[lastTextIdx] as Text; - const lastChildValue = lastTextChild?.value || ''; - - if (hasAttributes(lastChildValue)) { - const { text, attributes } = parseAttributes(lastChildValue); - - lastTextChild.value = text; - - const classes = attributes.classes.filter((s) => s !== 'starred'); - if (classes.length) { - node.data = { - ...(node.data || {}), - hProperties: { - ...(node.data?.hProperties || {}), - className: classes.join(' '), - }, - }; - } - } -} diff --git a/packages/processor/src/markdown-to-mdx/mdast-transforms/heading-increments.ts b/packages/processor/src/markdown-to-mdx/mdast-transforms/headings.ts similarity index 76% rename from packages/processor/src/markdown-to-mdx/mdast-transforms/heading-increments.ts rename to packages/processor/src/markdown-to-mdx/mdast-transforms/headings.ts index 4ed3387..c5eb5f0 100644 --- a/packages/processor/src/markdown-to-mdx/mdast-transforms/heading-increments.ts +++ b/packages/processor/src/markdown-to-mdx/mdast-transforms/headings.ts @@ -1,5 +1,6 @@ import { Attributes, + hasAttributes, parseAttributes, } from '../../shared-utils/parse-heading-attributes'; import GithubSlugger from 'github-slugger'; @@ -16,19 +17,24 @@ import { createHeadingCounter, getHeadingDepth, } from '../utils/heading-counter'; -// import { slug } from '../utils/slug'; import { TheoremCounter, createTheoremCounter, } from '../utils/theorem-counter'; -export function headingIncrements(ctx: Context) { +export function headings(ctx: Context) { return (tree: Root) => { const slugger = new GithubSlugger(); const headingCounter = createHeadingCounter(); const theoremCounter = createTheoremCounter(); visit(tree, (node) => { + // TODO: add heading link icon here + // + // if (node.depth < 4 && node.depth > 1) { + // console.log(node); + // } + if (node.type === 'heading') { transformHeading(node, slugger, headingCounter, ctx); } @@ -38,7 +44,7 @@ export function headingIncrements(ctx: Context) { slugger, headingCounter, theoremCounter, - ctx + ctx, ); } }); @@ -49,33 +55,39 @@ function transformHeading( heading: Heading, slugger: GithubSlugger, headingCounter: HeadingCounter, - ctx: Context + ctx: Context, ) { - const textChildren = heading.children.filter((o) => o.type === 'text'); - const { text, attributes } = parseAttributes(toString(textChildren)); + const childrenText = heading.children.filter( + (o) => o.type !== 'textDirective', + ); + const { text, attributes } = parseAttributes(toString(childrenText)); + const id = slugger.slug(attributes.id || text); + const className = getClassNames(heading); + + // apply id and classes + const hProperties = heading.data?.hProperties || {}; + hProperties.id = id; + if (className.length > 0) { + hProperties.className = className.join(' '); + } + heading.data = { + ...(heading.data || {}), + hProperties, + }; + // create and cache incrementing label if (headingShouldIncrement(attributes)) { headingCounter.increment(heading.depth); } - - const id = slugger.slug(attributes.id || text); const label = headingCounter.getCounts(heading.depth).join('.'); - if (attributes.id) { ctx.refMap[attributes.id] = { id, label }; } - heading.data = { - ...(heading.data || {}), - hProperties: { - ...(heading.data?.hProperties || {}), - id, - }, - }; - if ( headingShouldIncrement(attributes) && - headingShouldDisplayCount(heading) + headingShouldDisplayCount(heading) && + label !== '' ) { heading.children.unshift( { @@ -97,7 +109,7 @@ function transformHeading( { type: 'text', value: ' ', - } + }, ); } } @@ -107,7 +119,7 @@ function transformEnvironment( slugger: GithubSlugger, headingCounter: HeadingCounter, theoremCounter: TheoremCounter, - ctx: Context + ctx: Context, ) { const name = container.name.trim(); if (!boxoutAllowList.includes(name)) { @@ -119,6 +131,7 @@ function transformEnvironment( let label: string; if (theorem) { const depth = getHeadingDepth(ctx.theorems, name); + // console.log(depth); headingCounter.increment(depth); label = headingCounter.getCounts(depth).join('.'); } else { @@ -127,7 +140,7 @@ function transformEnvironment( } const id = slugger.slug( - container.attributes?.id || getSlug(`${name} ${label}`) + container.attributes?.id || getSlug(`${name} ${label}`), ); container.data = { @@ -183,7 +196,7 @@ function transformEnvironment( }, stop, ], - } + }, ); } else { type.data?.hChildren?.push(stop); @@ -213,6 +226,22 @@ function getSlug(text: string, counts?: number[]) { return text + (countStr.length ? `-${countStr}` : ''); } +function getClassNames(heading: Heading) { + const lastTextIdx = heading.children.findLastIndex( + (o) => o.type === 'text', + ); + const lastTextChild = heading.children[lastTextIdx] as Text; + const lastChildValue = lastTextChild?.value || ''; + + if (!hasAttributes(lastChildValue)) { + return []; + } + + const { text, attributes } = parseAttributes(lastChildValue); + lastTextChild.value = text; + return attributes.classes.filter((s) => s !== 'starred'); +} + function headingShouldIncrement(attributes: Attributes) { return !attributes.classes.includes('starred'); } diff --git a/packages/processor/src/markdown-to-mdx/mdast-transforms/index.ts b/packages/processor/src/markdown-to-mdx/mdast-transforms/index.ts index d91c3b9..8bc77e8 100644 --- a/packages/processor/src/markdown-to-mdx/mdast-transforms/index.ts +++ b/packages/processor/src/markdown-to-mdx/mdast-transforms/index.ts @@ -1,41 +1,53 @@ import { boxouts } from './boxouts'; import { escapeCharsForMdx } from './escape-mdx-chars'; -import { headingAttributes } from './heading-attributes'; -import { headingIncrements } from './heading-increments'; +import { headings } from './headings'; import { references } from './references'; import { sidenotes } from './sidenotes'; import { Root } from 'mdast'; import { createRemarkProcessor } from '../../shared-utils/remark-pipeline'; import { Context } from '../context'; +import { createTableOfContents } from '../sidebar'; import { center } from './center'; import { extractFrontmatter } from './extract-frontmatter'; -import { fancyTitle } from './fancy-title'; +// import { fancyTitle } from './fancy-title'; +import { sectionize } from './sectionize'; import { underline } from './underline'; export async function markdownToMdast( markdown: string, - ctx: Context + ctx: Context, // options: Options ) { - const processor = createRemarkProcessor([ + const stage1 = createRemarkProcessor([ [extractFrontmatter, ctx], - fancyTitle, - [headingIncrements, ctx], - headingAttributes, + [headings, ctx], + // fancyTitle, [references, ctx], [boxouts, ctx], - center, [sidenotes, ctx], + center, underline, - - // should be last - escapeCharsForMdx, - // () => (tree) => { // console.dir(tree, { depth: null }); // }, ]); - const mdast = processor.parse(markdown); - return processor.run(mdast as Root); + + const mdast = stage1.parse(markdown); + const transformed = await stage1.run(mdast as Root); + + // table of contents can't be generated after remark-sectionize + const tableOfContents = await createTableOfContents(transformed as Root); + + const stage2 = createRemarkProcessor([ + sectionize, + // should be last + escapeCharsForMdx, + ]); + const withSectionized = await stage2.run(transformed as Root); + + return { + mdast: withSectionized, + tableOfContents, + }; } diff --git a/packages/processor/src/markdown-to-mdx/mdast-transforms/sectionize.ts b/packages/processor/src/markdown-to-mdx/mdast-transforms/sectionize.ts new file mode 100644 index 0000000..c857376 --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdast-transforms/sectionize.ts @@ -0,0 +1,64 @@ +import { BlockContent, Heading, Root, RootContent } from 'mdast'; +import { findAfter } from 'unist-util-find-after'; +import { visit } from 'unist-util-visit'; + +const MIN_HEADING_DEPTH = 1; +const MAX_HEADING_DEPTH = 6; + +export function sectionize() { + return (tree: Root) => { + for ( + let depth = MAX_HEADING_DEPTH; + depth > MIN_HEADING_DEPTH; + depth-- + ) { + visit(tree, 'heading', (node, index, parent) => { + // console.dir(node, { depth: null }); + if ( + node.depth !== depth || + index === undefined || + parent === undefined + ) { + return; + } + + const end = findAfter(parent, node, (next) => { + if (next.type === 'heading') { + const heading = next as Heading; + return heading.depth <= node.depth; + } + }); + const endIndex = parent.children.indexOf(end as BlockContent); + const children = parent.children.slice( + index, + endIndex > 0 ? endIndex : undefined, + ); + + // steal id from title and give it to section + const hProps = node.data?.hProperties || {}; + // console.log(hProps); + + const { id, ...hProperties } = hProps; + node.data = { + ...(node.data || {}), + hProperties, + }; + + const section = { + type: 'section', + data: { + hName: 'section', + hProperties: { id }, + }, + children, + }; + + parent.children.splice( + index, + section.children.length, + section as RootContent, + ); + }); + } + }; +} diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/index.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/index.tsx index 09a1e07..ae9649c 100644 --- a/packages/processor/src/markdown-to-mdx/mdx-handlers/index.tsx +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/index.tsx @@ -1,7 +1,8 @@ -// import { MathJax } from 'better-react-mathjax'; import { MDXComponents } from 'mdx/types'; +// import { MathJax } from './mathjax'; import { Task } from './task/Task'; +import { Section } from './toc-highlight/section'; export function useMDXComponents(): MDXComponents { // const ctx = {} @@ -9,12 +10,19 @@ export function useMDXComponents(): MDXComponents { div(props) { if (props.class?.includes('task')) { return ; + } else { + return
; } - return
; }, + + // TODO: remove sectionize and use the titles + section: Section, // code(props) { - // if (props.class?.includes('math-inline')) { - // return ; + // if ( + // props.class?.includes('math-inline') || + // props.class?.includes('math-display') + // ) { + // return ; // } // return ; // }, diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/index.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/index.tsx new file mode 100644 index 0000000..ecadcc1 --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/index.tsx @@ -0,0 +1,76 @@ +import type { LiteDocument } from 'mathjax-full/js/adaptors/lite/Document.js'; +import type { LiteNode } from 'mathjax-full/js/adaptors/lite/Element.js'; +import type { LiteText } from 'mathjax-full/js/adaptors/lite/Text.js'; +import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js'; +// import { STATE } from 'mathjax-full/js/core/MathItem.js'; +// import { MmlNode } from 'mathjax-full/js/core/MmlTree/MmlNode.js'; +// import { SerializedMmlVisitor } from 'mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js'; +import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument.js'; +import { TeX } from 'mathjax-full/js/input/tex.js'; +import { SVG } from 'mathjax-full/js/output/svg.js'; + +import { render } from './litedom'; + +export type Document = HTMLDocument; + +let cache: Document | null = null; + +function getConverter(): Document { + if (cache !== null) { + return cache; + } + + const document = new HTMLDocument('', liteAdaptor(), { + mathjax: { + asyncLoad: async (name: string) => import(`${name}.js`), + }, + InputJax: new TeX({ + packages: ['base'], + // tags: 'ams', + // Allow single $ delimiters + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + displayMath: [ + ['$$', '$$'], + [`\\[`, `\\]`], + ], + }), + OutputJax: new SVG({ + // font: 'mathjax-fira', + }), + }); + // const visitor = new SerializedMmlVisitor(); + // const toMathML = (node: MmlNode) => visitor.visitTree(node); + + // const converter = { + // convert(expr: string) { + // const node = document.convert(expr); + // return node; + // }, + // }; + + cache = document; + return document; +} + +export interface MathjaxProps { + expr: string; + document?: Document; +} + +type Props = { + expr: string; +}; + +export function MathJax({ expr }: Props) { + // console.log('hey!'); + const converter = getConverter(); + // console.log(converter); + const node = converter.convert(expr); + // console.log(node); + // return null; + const children = render(node.children); + return <>{children}; +} diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/litedom.ts b/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/litedom.ts new file mode 100644 index 0000000..d13dc5f --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/mathjax/litedom.ts @@ -0,0 +1,84 @@ +import type { + LiteElement, + LiteNode, +} from 'mathjax-full/js/adaptors/lite/Element.js'; +import type { LiteText } from 'mathjax-full/js/adaptors/lite/Text.js'; +import { createElement } from 'preact'; + +export function render(nodes: LiteNode[]): React.ReactNode[] { + return nodes.map((node, idx) => { + if (isLiteText(node)) { + return node.value; + } else { + return createElement( + node.kind, + props(node, idx), + ...render(node.children), + ); + } + }); +} + +function isLiteText(node: LiteNode): node is LiteText { + return node.kind === '#text'; +} + +interface Style { + [name: string]: string; +} + +// Convert cebab-case into camelCase. `camelCase` function from 'camel-case' npm package is slower because it does not +// know the input is in cebab-case. This function is about 1.5x faster than it. +const DELIMITER = /[:-]/; +function camelize(name: string): string { + if (!DELIMITER.test(name)) { + return name; + } + const parts = name.split(DELIMITER); + for (let i = 1; i < parts.length; i++) { + const part = parts[i]; + if (part.length > 0) { + parts[i] = part[0].toUpperCase() + part.slice(1); + } + } + return parts.join(''); +} + +// This function will be no longer necessary when this PR is merged. +// https://github.com/mathjax/MathJax-src/pull/877 +function parseStyle(text: string): Style { + const style: Style = {}; + for (const s of text.split(';')) { + const [name, val] = s.split(':'); + style[camelize(name)] = val; + } + return style; +} + +interface Props { + key: string | number; + style?: Style; + [key: string]: unknown; +} + +function props(elem: LiteElement, key: number | string): Props { + const props: Props = { key }; + let style = ''; + for (const name of Object.keys(elem.attributes)) { + const value = elem.attributes[name]; + if (name.startsWith('data-') || name.startsWith('aria-')) { + props[name] = value; + } else if (name === 'style') { + style = value; + } else { + props[camelize(name)] = value; + } + } + if (elem.styles !== null) { + style += elem.styles.cssText; + } + if (style.length > 0) { + props.style = parseStyle(style); + } + return props; +} diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/intersection-observer.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/intersection-observer.tsx new file mode 100644 index 0000000..576619c --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/intersection-observer.tsx @@ -0,0 +1,54 @@ +import { Ref } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; + +export type Entry = IntersectionObserverEntry | undefined; +export type InView = boolean; +export type TriggerOnce = boolean; + +export interface ObserverOptions { + rootMargin?: IntersectionObserverInit['rootMargin']; + threshold?: IntersectionObserverInit['threshold']; + defaultInView?: InView; + triggerOnce?: TriggerOnce; +} + +export const useObserver = ( + options?: ObserverOptions, +): [ref: Ref, inView: InView, entry: Entry] => { + const [inView, setInView] = useState( + options?.defaultInView || false, + ); + const observer = useRef(); + const entry = useRef(); + const ref = useRef(); + + if (typeof window !== 'undefined') { + if (!observer.current) { + observer.current = new IntersectionObserver( + (entries) => { + entry.current = entries[0]; + setInView(entries[0].isIntersecting); + }, + { + ...options, + root: ref.current, + }, + ); + } + + useEffect(() => { + if (!entry.current) { + observer.current?.observe(ref.current!); + } else if (options?.triggerOnce && ref.current) { + observer.current?.unobserve(ref.current); + } + }, [ref, inView, options]); + } + + return [ + // @ts-expect-error ref !== mutableRef + ref, + inView, + entry.current, + ]; +}; diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/section.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/section.tsx new file mode 100644 index 0000000..b6c901b --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/section.tsx @@ -0,0 +1,21 @@ +import { useContext, useEffect } from 'preact/hooks'; +import { JSX } from 'preact/jsx-runtime'; + +import { useObserver } from './intersection-observer'; +import { TocHighlightContext } from './toc-highlight-provider'; + +export function Section(props: JSX.HTMLAttributes) { + const [ref, inView, entry] = useObserver({ + rootMargin: '-50% 0 -50% 0', + threshold: 0, + }); + const { setActiveSection } = useContext(TocHighlightContext); + + useEffect(() => { + const id = String(props.id); + const height = entry?.boundingClientRect.height || 0; + setActiveSection(inView, { id, height }); + }, [inView]); + + return
; +} diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-highlight-provider.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-highlight-provider.tsx new file mode 100644 index 0000000..97e1f4a --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-highlight-provider.tsx @@ -0,0 +1,56 @@ +import { ComponentChildren, createContext } from 'preact'; +import { useMemo, useState } from 'preact/hooks'; + +type Section = { + id: string; + height: number; +}; + +const initialSection: Section = { id: '', height: Infinity }; + +type TocHighlight = { + activeSectionId: string; + setActiveSection: (inView: boolean, section: Section) => unknown; +}; + +export const TocHighlightContext = createContext({ + activeSectionId: initialSection.id, + setActiveSection: () => {}, +}); + +export function TocHighlightProvider({ + children, +}: { + children: ComponentChildren; +}) { + const [activeSections, setActiveSection] = useState([]); + + const context = useMemo((): TocHighlight => { + return { + activeSectionId: getShortestSection(activeSections).id, + setActiveSection(inView: boolean, section: Section) { + setActiveSection((activeSections) => { + if (inView && !activeSections.find((o) => o.id === section.id)) { + return [...activeSections, section]; + } + if (!inView && activeSections.find((o) => o.id === section.id)) { + return activeSections.filter((o) => o.id !== section.id); + } + return activeSections; + }); + }, + }; + }, [activeSections]); + + return ( + + {children} + + ); +} + +function getShortestSection(activeSections: Section[]) { + return activeSections.reduce((acc: Section, section: Section) => { + return section.height <= acc.height ? section : acc; + }, initialSection); +} diff --git a/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-list-item.tsx b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-list-item.tsx new file mode 100644 index 0000000..d0a6219 --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/mdx-handlers/toc-highlight/toc-list-item.tsx @@ -0,0 +1,16 @@ +import classNames from 'classnames'; +import { VNode } from 'preact'; +import { useContext } from 'preact/hooks'; +import { JSX } from 'preact/jsx-runtime'; + +import { TocHighlightContext } from './toc-highlight-provider'; + +export function TocListItem(props: JSX.HTMLAttributes) { + const { activeSectionId } = useContext(TocHighlightContext); + const child = props?.children as VNode; + const href = child?.props?.href; + const className = classNames(props.class, { + active: href === `#${activeSectionId}`, + }); + return
  • ; +} diff --git a/packages/processor/src/markdown-to-mdx/sidebar.ts b/packages/processor/src/markdown-to-mdx/sidebar.ts new file mode 100644 index 0000000..ce6fc5c --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/sidebar.ts @@ -0,0 +1,90 @@ +import { processorOptions } from './hast-transforms'; +import { createProcessor } from '@mdx-js/mdx'; +import { RunOptions } from '@mdx-js/mdx'; +import { ListItem, Nodes, Paragraph, Root } from 'mdast'; +import { toc } from 'mdast-util-toc'; +import { Fragment, jsx, jsxDEV, jsxs } from 'preact/jsx-runtime'; + +import { TocListItem } from './mdx-handlers/toc-highlight/toc-list-item'; + +export const sidebarRunOptions: RunOptions = { + Fragment, + useMDXComponents() { + return { + li: TocListItem, + }; + }, + + // @ts-expect-error: jsx is incompatible for unknown reasons + jsx, + // @ts-expect-error: jsxs is incompatible for unknown reasons + jsxs, + // @ts-expect-error: jsxDEV is incompatible for unknown reasons + jsxDEV, +}; + +export async function createTableOfContents(mdast: Root) { + const { map: tocMdast } = toc(mdast as Nodes, { + maxDepth: 3, + minDepth: 2, + tight: true, + // parents: ['tree', 'section'], + }); + + // if (tocMdast === undefined) { + // return []; + // } + // return inlineList(tocMdast.children || []); + + if (tocMdast === undefined) { + return ''; + } + + const list = inlineList(tocMdast.children || []); + // console.log(list); + + const processor = createProcessor(processorOptions); + + const estree = await processor.run({ + // @ts-expect-error: mdast is not of type Program + type: 'list', + ordered: true, + children: list, + }); + + return processor.stringify(estree); +} + +function inlineList(list: ListItem[]) { + return list.reduce((acc: ListItem[], h2) => { + const h2p = h2.children[0] as Paragraph; + const h2Item: ListItem = { + type: 'listItem', + children: [h2p], + data: { + hProperties: { + className: 'depth-2', + }, + }, + }; + acc.push(h2Item); + + const h3s = h2.children[1]; + if (h3s?.type === 'list') { + const h3Items = h3s.children.map( + (li): ListItem => ({ + type: 'listItem', + children: li.children, + data: { + hProperties: { + className: 'depth-3', + }, + }, + }), + ); + acc.push(...h3Items); + } + + return acc; + }, []); +} diff --git a/packages/processor/src/markdown-to-mdx/utils/__test__/heading-counter.test.ts b/packages/processor/src/markdown-to-mdx/utils/__test__/heading-counter.test.ts index 7ecb4b1..c619e3a 100644 --- a/packages/processor/src/markdown-to-mdx/utils/__test__/heading-counter.test.ts +++ b/packages/processor/src/markdown-to-mdx/utils/__test__/heading-counter.test.ts @@ -1,47 +1,34 @@ -import { test } from 'vitest'; +import { expect, test } from 'vitest'; -// import { createHeadingCounter } from '../heading-counter'; +import { createHeadingCounter } from '../heading-counter'; -test.skip('heading counter', async () => { - // const counter = createHeadingCounter(); - // const counts = [ - // [...counter.increment(1)], - // [...counter.increment(2)], - // [...counter.increment(3)], - // [...counter.increment(4)], - // [...counter.increment(5)], - // [...counter.increment(6)], - // [...counter.increment(6)], - // [...counter.increment(5)], - // [...counter.increment(6)], - // [...counter.increment(2)], - // [...counter.increment(3)], - // [...counter.increment(2)], - // [...counter.increment(3)], - // [...counter.increment(4)], - // [...counter.increment(3)], - // [...counter.increment(4)], - // [...counter.increment(4)], - // ]; - // expect(counts).toEqual([ - // [1, 0, 0, 0, 0, 0], - // [1, 1, 0, 0, 0, 0], - // [1, 1, 1, 0, 0, 0], - // [1, 1, 1, 1, 0, 0], - // [1, 1, 1, 1, 1, 0], - // [1, 1, 1, 1, 1, 1], - // [1, 1, 1, 1, 1, 2], - // [1, 1, 1, 1, 2, 0], - // [1, 1, 1, 1, 2, 1], - // [1, 2, 0, 0, 0, 0], - // [1, 2, 1, 0, 0, 0], - // [1, 3, 0, 0, 0, 0], - // [1, 3, 1, 0, 0, 0], - // [1, 3, 1, 1, 0, 0], - // [1, 3, 2, 0, 0, 0], - // [1, 3, 2, 1, 0, 0], - // [1, 3, 2, 2, 0, 0], - // ]); - // const str = counter.format(4); - // expect(str).toBe('3.2.2.'); +test('heading counter', async () => { + const counter = createHeadingCounter(); + + const depths = [1, 2, 3, 4, 5, 6, 6, 5, 6, 2, 3, 2, 3, 4, 3, 4, 4]; + + const counts = depths.map((depth) => { + counter.increment(depth); + return counter.getCounts(depth); + }); + + expect(counts).toEqual([ + [], + [1], + [1, 1], + [1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 2], + [1, 1, 1, 2], + [1, 1, 1, 2, 1], + [2], + [2, 1], + [3], + [3, 1], + [3, 1, 1], + [3, 2], + [3, 2, 1], + [3, 2, 2], + ]); }); diff --git a/packages/processor/src/markdown-to-mdx/utils/__test__/theorem-counter.test.ts b/packages/processor/src/markdown-to-mdx/utils/__test__/theorem-counter.test.ts new file mode 100644 index 0000000..aad102e --- /dev/null +++ b/packages/processor/src/markdown-to-mdx/utils/__test__/theorem-counter.test.ts @@ -0,0 +1,36 @@ +import { expect, test } from 'vitest'; + +import { createTheoremCounter } from '../theorem-counter'; + +test('theorem counter', async () => { + const counter = createTheoremCounter(); + + const depths = [ + 'alpha', + 'bravo', + 'charlie', + 'delta', + 'echo', + 'foxtrot', + 'foxtrot', + 'echo', + 'foxtrot', + 'bravo', + 'charlie', + 'bravo', + 'charlie', + 'delta', + 'charlie', + 'delta', + 'delta', + ]; + + const counts = depths.map((depth) => { + counter.increment(depth); + return counter.get(depth); + }); + + expect(counts).toEqual([ + 1, 1, 1, 1, 1, 1, 2, 2, 3, 2, 2, 3, 3, 2, 4, 3, 4, + ]); +}); diff --git a/packages/processor/src/markdown-to-mdx/utils/heading-counter.ts b/packages/processor/src/markdown-to-mdx/utils/heading-counter.ts index d0bfd35..e6bfd8f 100644 --- a/packages/processor/src/markdown-to-mdx/utils/heading-counter.ts +++ b/packages/processor/src/markdown-to-mdx/utils/heading-counter.ts @@ -15,8 +15,11 @@ export function createHeadingCounter(): HeadingCounter { return count[depth - 1]; }, getCounts(depth: number) { - // TODO: `2` relates to headingDepth - (subsection - 1) - return count.slice(2, depth); + const counts = count.slice(1, depth); + + // remove zero counts at the left side of the count + const idx = counts.findIndex((n) => n !== 0); + return idx > 0 ? counts.slice(idx) : counts; }, increment(depth: number) { ++count[depth - 1]; @@ -48,7 +51,7 @@ const headingDepths: Record = { subparagraph: 6, }; -// TODO: return TheoremCounter instead of erroring +// TODO: return TheoremCounter instead of error? export function getHeadingDepth(theorems: Theorems, name: string): number { // console.log(theorems, name); const environment = theorems[name]; @@ -65,11 +68,14 @@ export function getHeadingDepth(theorems: Theorems, name: string): number { return findParentHeading(environment.referenceCounter); } throw new Error( - `thereom parent \`numberWithin\` not found for: ${name}` + `thereom parent \`numberWithin\` not found for: ${name}`, ); } const parentHeading = findParentHeading(name); + + // console.log(parentHeading, theorems); + const parentDepth = headingDepths[parentHeading]; if (parentDepth === undefined) { throw new Error(`thereom \`numberWithin\` not found: ${name}`); diff --git a/packages/processor/src/test-utils/create-e2e-test-bundle.ts b/packages/processor/src/test-utils/create-e2e-test-bundle.ts index b8248d7..929e64f 100644 --- a/packages/processor/src/test-utils/create-e2e-test-bundle.ts +++ b/packages/processor/src/test-utils/create-e2e-test-bundle.ts @@ -14,6 +14,7 @@ export async function createE2eTestBundle(markdown: string) { const bundle = { css: await readFile(path.resolve(`${resources}/runtime.css`), 'utf-8'), js: await readFile(path.resolve(`${resources}/runtime.js`), 'utf-8'), + font: 'termes', }; return createRuntimeHtml(md, frontmatter, bundle); } diff --git a/packages/runtime/index.html b/packages/runtime/index.html index 551f8c1..2af3261 100644 --- a/packages/runtime/index.html +++ b/packages/runtime/index.html @@ -3,11 +3,11 @@ + ISOS -