├── .editorconfig ├── .envrc ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── release.yml │ ├── test.yml │ └── typedoc.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierignore ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel-jest.config.js ├── build.js ├── dependencies ├── README.md ├── electron-webview │ └── preload.js ├── font-awesome │ ├── css │ │ └── all.min.css │ └── webfonts │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-v4compatibility.ttf │ │ └── fa-v4compatibility.woff2 ├── katex │ ├── fonts │ │ ├── KaTeX_AMS-Regular.ttf │ │ ├── KaTeX_AMS-Regular.woff │ │ ├── KaTeX_AMS-Regular.woff2 │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ ├── KaTeX_Fraktur-Bold.woff │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ ├── KaTeX_Fraktur-Regular.woff │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ ├── KaTeX_Main-Bold.ttf │ │ ├── KaTeX_Main-Bold.woff │ │ ├── KaTeX_Main-Bold.woff2 │ │ ├── KaTeX_Main-BoldItalic.ttf │ │ ├── KaTeX_Main-BoldItalic.woff │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ ├── KaTeX_Main-Italic.ttf │ │ ├── KaTeX_Main-Italic.woff │ │ ├── KaTeX_Main-Italic.woff2 │ │ ├── KaTeX_Main-Regular.ttf │ │ ├── KaTeX_Main-Regular.woff │ │ ├── KaTeX_Main-Regular.woff2 │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ ├── KaTeX_Math-BoldItalic.woff │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ ├── KaTeX_Math-Italic.ttf │ │ ├── KaTeX_Math-Italic.woff │ │ ├── KaTeX_Math-Italic.woff2 │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ ├── KaTeX_SansSerif-Bold.woff │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ ├── KaTeX_SansSerif-Italic.woff │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ ├── KaTeX_SansSerif-Regular.woff │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ ├── KaTeX_Script-Regular.ttf │ │ ├── KaTeX_Script-Regular.woff │ │ ├── KaTeX_Script-Regular.woff2 │ │ ├── KaTeX_Size1-Regular.ttf │ │ ├── KaTeX_Size1-Regular.woff │ │ ├── KaTeX_Size1-Regular.woff2 │ │ ├── KaTeX_Size2-Regular.ttf │ │ ├── KaTeX_Size2-Regular.woff │ │ ├── KaTeX_Size2-Regular.woff2 │ │ ├── KaTeX_Size3-Regular.ttf │ │ ├── KaTeX_Size3-Regular.woff │ │ ├── KaTeX_Size3-Regular.woff2 │ │ ├── KaTeX_Size4-Regular.ttf │ │ ├── KaTeX_Size4-Regular.woff │ │ ├── KaTeX_Size4-Regular.woff2 │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ ├── KaTeX_Typewriter-Regular.woff │ │ └── KaTeX_Typewriter-Regular.woff2 │ └── katex.min.css ├── mermaid │ └── mermaid.min.js ├── remarkable │ └── remarkable.js ├── reveal │ ├── css │ │ ├── print │ │ │ ├── paper.css │ │ │ └── pdf.css │ │ ├── reset.css │ │ ├── reveal.css │ │ └── theme │ │ │ ├── README.md │ │ │ ├── beige.css │ │ │ ├── black.css │ │ │ ├── blood.css │ │ │ ├── league.css │ │ │ ├── moon.css │ │ │ ├── night.css │ │ │ ├── none.css │ │ │ ├── serif.css │ │ │ ├── simple.css │ │ │ ├── sky.css │ │ │ ├── solarized.css │ │ │ └── white.css │ ├── js │ │ └── reveal.js │ ├── lib │ │ ├── css │ │ │ └── zenburn.css │ │ ├── font │ │ │ ├── league-gothic │ │ │ │ ├── LICENSE │ │ │ │ ├── league-gothic.css │ │ │ │ ├── league-gothic.eot │ │ │ │ ├── league-gothic.ttf │ │ │ │ └── league-gothic.woff │ │ │ └── source-sans-pro │ │ │ │ ├── LICENSE │ │ │ │ ├── source-sans-pro-italic.eot │ │ │ │ ├── source-sans-pro-italic.ttf │ │ │ │ ├── source-sans-pro-italic.woff │ │ │ │ ├── source-sans-pro-regular.eot │ │ │ │ ├── source-sans-pro-regular.ttf │ │ │ │ ├── source-sans-pro-regular.woff │ │ │ │ ├── source-sans-pro-semibold.eot │ │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ │ ├── source-sans-pro-semibold.woff │ │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ │ └── source-sans-pro.css │ │ └── js │ │ │ ├── classList.js │ │ │ ├── head.min.js │ │ │ └── html5shiv.js │ └── plugin │ │ ├── multiplex │ │ ├── client.js │ │ ├── index.js │ │ ├── master.js │ │ └── package.json │ │ ├── notes-server │ │ ├── client.js │ │ ├── index.js │ │ └── notes.html │ │ ├── notes │ │ ├── notes.html │ │ ├── notes.js │ │ ├── plugin.js │ │ └── speaker-view.html │ │ ├── print-pdf │ │ └── print-pdf.js │ │ ├── search │ │ ├── plugin.js │ │ └── search.js │ │ ├── zoom-js │ │ └── zoom.js │ │ └── zoom │ │ ├── plugin.js │ │ └── zoom.js ├── vega-embed │ └── vega-embed.min.js ├── vega-lite │ └── vega-lite.min.js ├── vega │ └── vega.min.js └── wavedrom │ ├── skins │ ├── default.js │ └── narrow.js │ └── wavedrom.min.js ├── flake.lock ├── flake.nix ├── gulpfile.js ├── jest.config.js ├── package.json ├── postcss.config.js ├── prettier.config.js ├── shell.nix ├── src ├── code-chunk │ ├── code-chunk-data.ts │ └── code-chunk.ts ├── converters │ ├── ebook-convert.ts │ ├── markdown-convert.ts │ ├── pandoc-convert.ts │ ├── prince-convert.ts │ └── process-graphs.ts ├── custom-markdown-it-features │ ├── admonition.ts │ ├── code-fences.ts │ ├── critic-markup.ts │ ├── curly-bracket-attributes.ts │ ├── emoji.ts │ ├── fontawesome.ts │ ├── html5-embed.ts │ ├── math.ts │ ├── sourcemap.ts │ ├── widget.ts │ └── wikilink.ts ├── index.ts ├── lib │ ├── block-attributes │ │ ├── index.ts │ │ ├── normalizeBlockAttributes.ts │ │ ├── parseBlockAttributes.ts │ │ ├── stringifyBlockAttributes.ts │ │ └── types.ts │ ├── block-info │ │ ├── index.ts │ │ ├── normalize-block-info.ts │ │ ├── parse-block-info.ts │ │ └── types.ts │ └── compute-checksum.ts ├── markdown-engine │ ├── custom-subjects.ts │ ├── extension-helper.ts │ ├── heading-id-generator.ts │ ├── index.ts │ ├── toc.ts │ └── transformer.ts ├── notebook │ ├── config-helper.ts │ ├── graph-view.ts │ ├── index.ts │ ├── markdown.ts │ ├── note.ts │ ├── reference.ts │ ├── search.ts │ ├── slash.ts │ └── types.ts ├── prism │ ├── README.md │ ├── iele.ts │ ├── k.ts │ └── prism.js ├── render-enhancers │ ├── code-block-styling.ts │ ├── embedded-local-images.ts │ ├── embedded-svgs.ts │ ├── extended-table-syntax.ts │ ├── fenced-code-chunks.ts │ ├── fenced-diagrams.ts │ ├── fenced-math.ts │ └── resolved-image-paths.ts ├── renderers │ ├── bitfield.ts │ ├── parse-math.ts │ ├── puml-server.ts │ ├── puml.ts │ ├── vega-lite.ts │ ├── vega.ts │ └── viz.ts ├── tools │ ├── image-uploader.ts │ ├── latex.ts │ ├── magick.ts │ ├── mermaid.ts │ ├── pdf.ts │ ├── sharp.ts │ └── wavedrom.ts ├── utility.ts └── webview │ ├── .eslintrc.js │ ├── backlinks.tsx │ ├── components │ ├── Backlinks.tsx │ ├── ContextMenu.tsx │ ├── FloatingActions.tsx │ ├── Footer.tsx │ ├── ImageHelper.tsx │ ├── LoadingIcon.tsx │ ├── MarkdownEditor.tsx │ ├── Preview.tsx │ ├── RefreshingIcon.tsx │ ├── SidebarToc.tsx │ └── Topbar.tsx │ ├── containers │ └── preview.ts │ ├── index.css │ ├── lib │ ├── types.ts │ └── utility.ts │ └── preview.tsx ├── styles ├── markdown-it-admonition.css ├── octocat-spinner-128.gif ├── preview.less ├── preview_theme │ ├── atom-dark.less │ ├── atom-light.less │ ├── atom-material.less │ ├── github-dark.less │ ├── github-light.less │ ├── github.less │ ├── gothic.less │ ├── medium.less │ ├── monokai.less │ ├── newsprint.less │ ├── night.less │ ├── none.less │ ├── one-dark.less │ ├── one-light.less │ ├── solarized-dark.less │ ├── solarized-light.less │ └── vue.less ├── prism_theme │ ├── atom-dark.less │ ├── atom-light.less │ ├── atom-material.less │ ├── coy.css │ ├── darcula.css │ ├── dark.css │ ├── default.css │ ├── funky.css │ ├── github-dark.less │ ├── github.css │ ├── hopscotch.css │ ├── monokai.less │ ├── okaidia.css │ ├── one-dark.less │ ├── one-light.less │ ├── pen-paper-coffee.less │ ├── pojoaque.css │ ├── solarized-dark.less │ ├── solarized-light.less │ ├── twilight.css │ ├── vs.css │ ├── vue.less │ └── xonokai.css ├── style-template.less └── twemoji.css ├── tailwind.config.js ├── test ├── header-id-generator.test.ts ├── integration │ └── fixtures │ │ ├── basics.md │ │ ├── code-chunks.md │ │ ├── data │ │ └── sp500.csv │ │ ├── diagrams.md │ │ ├── file-imports.md │ │ ├── interactive-diagrams.md │ │ └── math.md ├── lib │ ├── attributes.test.ts │ └── block-info.test.ts └── markdown │ ├── test-files │ ├── test1.expect.md │ ├── test1.md │ ├── test2.expect.md │ ├── test2.md │ ├── test3.expect.md │ ├── test3.md │ ├── test4.expect.md │ ├── test4.md │ ├── test5.expect.md │ ├── test5.md │ ├── test6.expect.md │ ├── test6.md │ ├── test7.expect.md │ ├── test7.md │ ├── test8.expect.md │ └── test8.md │ └── transformer.test.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 2 8 | indent_style = space 9 | 10 | [*.json] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build.js 2 | gulpfile.js 3 | tailwind.config.js 4 | .eslintrc.js 5 | jest.config.js 6 | prettier.config.js 7 | test.js 8 | node_modules/** 9 | out/** 10 | styles/** 11 | dependencies/** 12 | src/prism/prism.js 13 | postcss.config.js 14 | babel-jest.config.js 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | root: true, 7 | }; 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | dependencies/**/* linguist-vendored 2 | dependencies/README.md linguist-vendored=false 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: shd101wyy 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Environment** 14 | - OS: Windows 11 15 | - Crossnote: 0.8.0 16 | - Node.js: 16.13.0 17 | - Browser: Chrome 95.0.4638.69 18 | 19 | **To Reproduce** 20 | Steps to reproduce the behavior: 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Screenshots** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request] " 5 | labels: feature request 6 | assignees: shd101wyy 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish a package to the npm registry 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - develop 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: 'Install nodejs 20' 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: '20' 17 | registry-url: 'https://registry.npmjs.org' 18 | - name: Install dependencies 19 | run: | 20 | corepack enable 21 | yarn install 22 | - name: Build 23 | run: yarn run build 24 | - name: Get current package version 25 | id: package_version 26 | uses: martinbeentjes/npm-get-version-action@v1.1.0 27 | - name: Get Changelog Entry 28 | id: changelog_reader 29 | uses: mindsers/changelog-reader-action@v2.0.0 30 | with: 31 | validation_level: warn 32 | version: ${{ steps.package_version.outputs.current-version }} 33 | path: 'CHANGELOG.md' 34 | - name: Publish package 35 | if: github.ref == 'refs/heads/master' 36 | run: npm publish 37 | env: 38 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | - name: Create a Release 40 | if: github.ref == 'refs/heads/master' 41 | id: create_release 42 | uses: actions/create-release@v1 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | with: 46 | tag_name : ${{ steps.package_version.outputs.current-version}} 47 | release_name: ${{ steps.package_version.outputs.current-version}} 48 | body: ${{ steps.changelog_reader.outputs.changes }} 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | pull_request: 7 | branches: 8 | - develop 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | jobs: 13 | test: 14 | name: "Test" 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Check out code' 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | - name: 'Install nodejs 20' 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: '20' 25 | - name: 'Build and test' 26 | run: | 27 | corepack enable 28 | yarn install 29 | yarn check:all 30 | yarn test 31 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy typedoc to GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | jobs: 10 | build-and-deploy: 11 | name: "Deploy typedoc to GitHub Pages" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 'Check out code' 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | - name: 'Install nodejs 20' 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: '20' 22 | - name: 'Build and test' 23 | run: | 24 | corepack enable 25 | yarn install 26 | yarn typedoc 27 | - name: Deploy 🚀 28 | uses: JamesIves/github-pages-deploy-action@v4 29 | with: 30 | branch: gh-pages # The branch the action should deploy to. 31 | folder: docs # The folder the action should deploy. 32 | clean: true 33 | single-commit: true 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | coverage 4 | node_modules 5 | out 6 | test.js 7 | test.mjs 8 | .direnv 9 | tsconfig.tsbuildinfo 10 | yarn-error.log 11 | .mume 12 | mume 13 | .crossnote 14 | crossnote 15 | styles/**/*.css 16 | docs 17 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | ## extensions 2 | *.* 3 | !*.css 4 | !*.js 5 | !*.json 6 | !*.less 7 | !*.md 8 | !*.ts 9 | !*.tsx 10 | !*.yaml 11 | !*.yml 12 | 13 | ## same as in .gitignore 14 | .DS_Store 15 | coverage 16 | node_modules 17 | out 18 | 19 | ## special cases 20 | dependencies/** 21 | !dependencies/README.md 22 | styles/*.css 23 | 24 | test.js 25 | test.mjs 26 | test/markdown/test-files/** 27 | 28 | src/prism/prism.js 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.insertFinalNewline": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build:watch", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "problemMatcher": ["$tsc-watch"], 12 | "isBackground": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | University of Illinois/NCSA 2 | Open Source License 3 | 4 | ``` 5 | Copyright (c) 2017 ~ 2023 Yiyi Wang 6 | All rights reserved. 7 | 8 | Developed by: Yiyi Wang 9 | https://github.com/shd101wyy/crossnote 10 | ``` 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. 15 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. 16 | Neither the names of Yiyi Wang, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /babel-jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@babel/core').TransformOptions} */ 2 | module.exports = { 3 | presets: [ 4 | '@babel/preset-env', 5 | '@babel/preset-react', 6 | '@babel/preset-typescript', 7 | ], 8 | plugins: ['@babel/transform-runtime', 'babel-plugin-transform-import-meta'], 9 | }; 10 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const { context, build } = require('esbuild'); 2 | const { dependencies, devDependencies } = require('./package.json'); 3 | const { tailwindPlugin } = require('esbuild-plugin-tailwindcss'); 4 | 5 | /** 6 | * @type {import('esbuild').BuildOptions} 7 | */ 8 | const sharedConfig = { 9 | entryPoints: ['./src/index.ts'], 10 | bundle: true, 11 | minify: true, 12 | // sourcemap: true, 13 | external: [ 14 | 'fs', 15 | 'path', 16 | 'child_process', 17 | 'os', 18 | 'vm', 19 | 'stream', 20 | 'node:fs/promises', 21 | 'url', 22 | // === from package.json 23 | ...Object.keys(dependencies), 24 | ...Object.keys(devDependencies), 25 | ], 26 | }; 27 | 28 | /** 29 | * @type {import('esbuild').BuildOptions} 30 | */ 31 | const cjsConfig = { 32 | ...sharedConfig, 33 | platform: 'node', // For CJS 34 | outfile: './out/cjs/index.cjs', 35 | target: 'node16', 36 | }; 37 | 38 | /** 39 | * @type {import('esbuild').BuildOptions} 40 | */ 41 | const esmConfig = { 42 | ...sharedConfig, 43 | // TODO: Support browser 44 | platform: 'neutral', // For ESM 45 | outfile: './out/esm/index.mjs', 46 | }; 47 | 48 | /** 49 | * @type {import('esbuild').BuildOptions} 50 | */ 51 | const webviewConfig = { 52 | entryPoints: ['./src/webview/preview.tsx', './src/webview/backlinks.tsx'], 53 | bundle: true, 54 | minify: true, 55 | platform: 'browser', 56 | // outfile: './out/webview/index.js', 57 | outdir: './out/webview', 58 | loader: { 59 | '.png': 'dataurl', 60 | '.woff': 'dataurl', 61 | '.woff2': 'dataurl', 62 | '.eot': 'dataurl', 63 | '.ttf': 'dataurl', 64 | '.svg': 'dataurl', 65 | }, 66 | plugins: [tailwindPlugin({})], 67 | }; 68 | 69 | async function main() { 70 | try { 71 | if (process.argv.includes('--watch')) { 72 | // CommonJS 73 | const cjsContext = await context({ 74 | ...cjsConfig, 75 | sourcemap: true, 76 | }); 77 | 78 | // ESM 79 | const esmContext = await context({ 80 | ...esmConfig, 81 | sourcemap: true, 82 | }); 83 | 84 | // Webview 85 | const webviewContext = await context({ 86 | ...webviewConfig, 87 | sourcemap: true, 88 | }); 89 | 90 | await Promise.all([ 91 | cjsContext.watch(), 92 | esmContext.watch(), 93 | webviewContext.watch(), 94 | ]); 95 | } else { 96 | // CommonJS 97 | await build(cjsConfig); 98 | 99 | // ESM 100 | await build(esmConfig); 101 | 102 | // Webview 103 | await build(webviewConfig); 104 | } 105 | } catch (error) { 106 | console.error(error); 107 | } 108 | } 109 | 110 | main(); 111 | -------------------------------------------------------------------------------- /dependencies/README.md: -------------------------------------------------------------------------------- 1 | I managed some of the libraries by myself instead of through npm to reduce the overall file size. 2 | 3 | **Versions** 4 | 5 | ```json 6 | { 7 | "font-awesome": "6.4.2", // Download from here: https://fontawesome.com/download 8 | // Fontawesome cheatsheet is available here: https://kapeli.com/cheat_sheets/Font_Awesome.docset/Contents/Resources/Documents/index 9 | "katex": "v0.16.11", // Only keep the css and fonts files. 10 | "mermaid": "11.5.0", // https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js 11 | "reveal": "4.6.0", 12 | 13 | // NOTE: Don't forget to update `dependentLibraryMaterials` in `markdown-engine/index.ts` 14 | "vega-embed": "6.23.0", // https://cdn.jsdelivr.net/npm/vega-embed@6.23.0/build/vega-embed.min.js 15 | // HACK: Needs to replace `structuredClone` to `globalThis.structuredClone` in `vega-lite.min.js` 16 | // HACK: Needs to replace `require("vega")` to `require("../vega/vega.min.js")` in `vega-lite.min.js` 17 | "vega-lite": "5.16.1", // https://cdn.jsdelivr.net/npm/vega-lite@5.16.1/build/vega-lite.min.js 18 | "vega": "5.25.0", // https://cdn.jsdelivr.net/npm/vega@5.25.0/build/vega.min.js 19 | 20 | "wavedrom": "3.3.0" // - https://cdn.jsdelivr.net/npm/wavedrom@3.3.0/wavedrom.min.js 21 | } 22 | ``` 23 | 24 | _Attention_: Need to remove `font: inherit;` from `reveal.css`. Otherwise, `KaTeX` and `MathJax` will have trouble rendering. Also don't forget to add the empty file `none.css`. 25 | 26 | _Attention_: Don't forget to modify the `dependentLibraryMaterials` variable in `markdown-engine.ts` 27 | 28 | _Attention_: NOTE: We have to disable the `_self = window` line in `prism.js` to make it work with VSCode web extension. 29 | -------------------------------------------------------------------------------- /dependencies/electron-webview/preload.js: -------------------------------------------------------------------------------- 1 | // referred and modified from 2 | // https://github.com/KidkArolis/electronic-post-message/blob/master/epm.js 3 | 4 | const {ipcRenderer} = require('electron') 5 | 6 | function dispatch (msgStr, source) { 7 | var message = new MessageEvent('message', { 8 | view: window.parent, 9 | bubbles: false, 10 | cancelable: false, 11 | data: msgStr, 12 | source: source 13 | }) 14 | window.dispatchEvent(message) 15 | } 16 | 17 | // Please notice that host shoud have channelName: `_postMessage` 18 | const channelName = '_postMessage' 19 | 20 | // inside of webview 21 | window.parent.postMessage = function (msgStr) { 22 | ipcRenderer.sendToHost(channelName, { 23 | data: msgStr 24 | }) 25 | } 26 | 27 | ipcRenderer.on(channelName, function (event, msgStr) { 28 | dispatch(msgStr, window.parent) 29 | }) -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /dependencies/font-awesome/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/font-awesome/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-BoldItalic.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-BoldItalic.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-BoldItalic.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /dependencies/katex/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/katex/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /dependencies/reveal/css/print/pdf.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This stylesheet is used to print reveal.js 3 | * presentations to PDF. 4 | * 5 | * https://github.com/hakimel/reveal.js#pdf-export 6 | */ 7 | 8 | * { 9 | -webkit-print-color-adjust: exact; 10 | } 11 | 12 | body { 13 | margin: 0 auto !important; 14 | border: 0; 15 | padding: 0; 16 | float: none !important; 17 | overflow: visible; 18 | } 19 | 20 | html { 21 | width: 100%; 22 | height: 100%; 23 | overflow: visible; 24 | } 25 | 26 | /* Remove any elements not needed in print. */ 27 | .nestedarrow, 28 | .reveal .controls, 29 | .reveal .progress, 30 | .reveal .playback, 31 | .reveal.overview, 32 | .fork-reveal, 33 | .share-reveal, 34 | .state-background { 35 | display: none !important; 36 | } 37 | 38 | h1, h2, h3, h4, h5, h6 { 39 | text-shadow: 0 0 0 #000 !important; 40 | } 41 | 42 | .reveal pre code { 43 | overflow: hidden !important; 44 | font-family: Courier, 'Courier New', monospace !important; 45 | } 46 | 47 | ul, ol, div, p { 48 | visibility: visible; 49 | position: static; 50 | width: auto; 51 | height: auto; 52 | display: block; 53 | overflow: visible; 54 | margin: auto; 55 | } 56 | .reveal { 57 | width: auto !important; 58 | height: auto !important; 59 | overflow: hidden !important; 60 | } 61 | .reveal .slides { 62 | position: static; 63 | width: 100% !important; 64 | height: auto !important; 65 | zoom: 1 !important; 66 | 67 | left: auto; 68 | top: auto; 69 | margin: 0 !important; 70 | padding: 0 !important; 71 | 72 | overflow: visible; 73 | display: block; 74 | 75 | perspective: none; 76 | perspective-origin: 50% 50%; 77 | } 78 | 79 | .reveal .slides .pdf-page { 80 | position: relative; 81 | overflow: hidden; 82 | z-index: 1; 83 | 84 | page-break-after: always; 85 | } 86 | 87 | .reveal .slides section { 88 | visibility: visible !important; 89 | display: block !important; 90 | position: absolute !important; 91 | 92 | margin: 0 !important; 93 | padding: 0 !important; 94 | box-sizing: border-box !important; 95 | min-height: 1px; 96 | 97 | opacity: 1 !important; 98 | 99 | transform-style: flat !important; 100 | transform: none !important; 101 | } 102 | 103 | .reveal section.stack { 104 | position: relative !important; 105 | margin: 0 !important; 106 | padding: 0 !important; 107 | page-break-after: avoid !important; 108 | height: auto !important; 109 | min-height: auto !important; 110 | } 111 | 112 | .reveal img { 113 | box-shadow: none; 114 | } 115 | 116 | .reveal .roll { 117 | overflow: visible; 118 | line-height: 1em; 119 | } 120 | 121 | /* Slide backgrounds are placed inside of their slide when exporting to PDF */ 122 | .reveal .slide-background { 123 | display: block !important; 124 | position: absolute; 125 | top: 0; 126 | left: 0; 127 | width: 100%; 128 | height: 100%; 129 | z-index: auto !important; 130 | } 131 | 132 | /* Display slide speaker notes when 'showNotes' is enabled */ 133 | .reveal.show-notes { 134 | max-width: none; 135 | max-height: none; 136 | } 137 | .reveal .speaker-notes-pdf { 138 | display: block; 139 | width: 100%; 140 | height: auto; 141 | max-height: none; 142 | top: auto; 143 | right: auto; 144 | bottom: auto; 145 | left: auto; 146 | z-index: 100; 147 | } 148 | 149 | /* Layout option which makes notes appear on a separate page */ 150 | .reveal .speaker-notes-pdf[data-layout="separate-page"] { 151 | position: relative; 152 | color: inherit; 153 | background-color: transparent; 154 | padding: 20px; 155 | page-break-after: always; 156 | border: 0; 157 | } 158 | 159 | /* Display slide numbers when 'slideNumber' is enabled */ 160 | .reveal .slide-number-pdf { 161 | display: block; 162 | position: absolute; 163 | font-size: 14px; 164 | } 165 | -------------------------------------------------------------------------------- /dependencies/reveal/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /dependencies/reveal/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment including the Grunt dependencies installed before proceeding: https://github.com/hakimel/reveal.js#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/Gruntfile.js)) when you run `npm run build -- css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /dependencies/reveal/css/theme/none.css: -------------------------------------------------------------------------------- 1 | /* yep, this file does nothing */ 2 | -------------------------------------------------------------------------------- /dependencies/reveal/lib/css/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('league-gothic.eot'); 4 | src: url('league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('league-gothic.woff') format('woff'), 6 | url('league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License 2 | 3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | —————————————————————————————- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | —————————————————————————————- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s). 21 | 22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/dependencies/reveal/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /dependencies/reveal/lib/font/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('source-sans-pro-regular.eot'); 4 | src: url('source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('source-sans-pro-regular.woff') format('woff'), 6 | url('source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('source-sans-pro-italic.eot'); 14 | src: url('source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('source-sans-pro-italic.woff') format('woff'), 16 | url('source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('source-sans-pro-semibold.eot'); 24 | src: url('source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('source-sans-pro-semibold.woff') format('woff'), 26 | url('source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('source-sans-pro-semibolditalic.eot'); 34 | src: url('source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } -------------------------------------------------------------------------------- /dependencies/reveal/lib/js/classList.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 2 | if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;pbody{font-family: sans-serif;}

reveal.js multiplex server.

Generate token'); 38 | res.end(); 39 | }); 40 | stream.on('readable', function() { 41 | stream.pipe(res); 42 | }); 43 | }); 44 | 45 | app.get("/token", function(req,res) { 46 | var ts = new Date().getTime(); 47 | var rand = Math.floor(Math.random()*9999999); 48 | var secret = ts.toString() + rand.toString(); 49 | res.send({secret: secret, socketId: createHash(secret)}); 50 | }); 51 | 52 | var createHash = function(secret) { 53 | var cipher = crypto.createCipher('blowfish', secret); 54 | return(cipher.final('hex')); 55 | }; 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset ); -------------------------------------------------------------------------------- /dependencies/reveal/plugin/multiplex/master.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Don't emit events from inside of notes windows 4 | if ( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var multiplex = Reveal.getConfig().multiplex; 7 | 8 | var socket = io.connect( multiplex.url ); 9 | 10 | function post() { 11 | 12 | var messageData = { 13 | state: Reveal.getState(), 14 | secret: multiplex.secret, 15 | socketId: multiplex.id 16 | }; 17 | 18 | socket.emit( 'multiplex-statechanged', messageData ); 19 | 20 | }; 21 | 22 | // post once the page is loaded, so the client follows also on "open URL". 23 | window.addEventListener( 'load', post ); 24 | 25 | // Monitor events that trigger a change in state 26 | Reveal.addEventListener( 'slidechanged', post ); 27 | Reveal.addEventListener( 'fragmentshown', post ); 28 | Reveal.addEventListener( 'fragmenthidden', post ); 29 | Reveal.addEventListener( 'overviewhidden', post ); 30 | Reveal.addEventListener( 'overviewshown', post ); 31 | Reveal.addEventListener( 'paused', post ); 32 | Reveal.addEventListener( 'resumed', post ); 33 | 34 | }()); 35 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/multiplex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal-js-multiplex", 3 | "version": "1.0.0", 4 | "description": "reveal.js multiplex server", 5 | "homepage": "http://revealjs.com", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "engines": { 10 | "node": "~4.1.1" 11 | }, 12 | "dependencies": { 13 | "express": "~4.13.3", 14 | "grunt-cli": "~0.1.13", 15 | "mustache": "~2.2.1", 16 | "socket.io": "~1.3.7" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/notes-server/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // don't emit events from inside the previews themselves 4 | if( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var socket = io.connect( window.location.origin ), 7 | socketId = Math.random().toString().slice( 2 ); 8 | 9 | console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId ); 10 | 11 | window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId ); 12 | 13 | /** 14 | * Posts the current slide data to the notes window 15 | */ 16 | function post() { 17 | 18 | var slideElement = Reveal.getCurrentSlide(), 19 | notesElement = slideElement.querySelector( 'aside.notes' ); 20 | 21 | var messageData = { 22 | notes: '', 23 | markdown: false, 24 | socketId: socketId, 25 | state: Reveal.getState() 26 | }; 27 | 28 | // Look for notes defined in a slide attribute 29 | if( slideElement.hasAttribute( 'data-notes' ) ) { 30 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 31 | } 32 | 33 | // Look for notes defined in an aside element 34 | if( notesElement ) { 35 | messageData.notes = notesElement.innerHTML; 36 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 37 | } 38 | 39 | socket.emit( 'statechanged', messageData ); 40 | 41 | } 42 | 43 | // When a new notes window connects, post our current state 44 | socket.on( 'new-subscriber', function( data ) { 45 | post(); 46 | } ); 47 | 48 | // When the state changes from inside of the speaker view 49 | socket.on( 'statechanged-speaker', function( data ) { 50 | Reveal.setState( data.state ); 51 | } ); 52 | 53 | // Monitor events that trigger a change in state 54 | Reveal.addEventListener( 'slidechanged', post ); 55 | Reveal.addEventListener( 'fragmentshown', post ); 56 | Reveal.addEventListener( 'fragmenthidden', post ); 57 | Reveal.addEventListener( 'overviewhidden', post ); 58 | Reveal.addEventListener( 'overviewshown', post ); 59 | Reveal.addEventListener( 'paused', post ); 60 | Reveal.addEventListener( 'resumed', post ); 61 | 62 | // Post the initial state 63 | post(); 64 | 65 | }()); 66 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/notes-server/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var Mustache = require('mustache'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port : 1947, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | 20 | socket.on( 'new-subscriber', function( data ) { 21 | socket.broadcast.emit( 'new-subscriber', data ); 22 | }); 23 | 24 | socket.on( 'statechanged', function( data ) { 25 | delete data.state.overview; 26 | socket.broadcast.emit( 'statechanged', data ); 27 | }); 28 | 29 | socket.on( 'statechanged-speaker', function( data ) { 30 | delete data.state.overview; 31 | socket.broadcast.emit( 'statechanged-speaker', data ); 32 | }); 33 | 34 | }); 35 | 36 | [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) { 37 | app.use( '/' + dir, staticDir( opts.baseDir + dir ) ); 38 | }); 39 | 40 | app.get('/', function( req, res ) { 41 | 42 | res.writeHead( 200, { 'Content-Type': 'text/html' } ); 43 | fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res ); 44 | 45 | }); 46 | 47 | app.get( '/notes/:socketId', function( req, res ) { 48 | 49 | fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) { 50 | res.send( Mustache.to_html( data.toString(), { 51 | socketId : req.params.socketId 52 | })); 53 | }); 54 | 55 | }); 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' ); 65 | 66 | console.log( brown + 'reveal.js - Speaker Notes' + reset ); 67 | console.log( '1. Open the slides at ' + green + slidesLocation + reset ); 68 | console.log( '2. Click on the link in your JS console to go to the notes page' ); 69 | console.log( '3. Advance through your slides and your notes will advance automatically' ); 70 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/print-pdf/print-pdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * phantomjs script for printing presentations to PDF. 3 | * 4 | * Example: 5 | * phantomjs print-pdf.js "http://revealjs.com?print-pdf" reveal-demo.pdf 6 | * 7 | * @author Manuel Bieh (https://github.com/manuelbieh) 8 | * @author Hakim El Hattab (https://github.com/hakimel) 9 | * @author Manuel Riezebosch (https://github.com/riezebosch) 10 | */ 11 | 12 | // html2pdf.js 13 | var system = require( 'system' ); 14 | 15 | var probePage = new WebPage(); 16 | var printPage = new WebPage(); 17 | 18 | var inputFile = system.args[1] || 'index.html?print-pdf'; 19 | var outputFile = system.args[2] || 'slides.pdf'; 20 | 21 | if( outputFile.match( /\.pdf$/gi ) === null ) { 22 | outputFile += '.pdf'; 23 | } 24 | 25 | console.log( 'Export PDF: Reading reveal.js config [1/4]' ); 26 | 27 | probePage.open( inputFile, function( status ) { 28 | 29 | console.log( 'Export PDF: Preparing print layout [2/4]' ); 30 | 31 | var config = probePage.evaluate( function() { 32 | return Reveal.getConfig(); 33 | } ); 34 | 35 | if( config ) { 36 | 37 | printPage.paperSize = { 38 | width: Math.floor( config.width * ( 1 + config.margin ) ), 39 | height: Math.floor( config.height * ( 1 + config.margin ) ), 40 | border: 0 41 | }; 42 | 43 | printPage.open( inputFile, function( status ) { 44 | console.log( 'Export PDF: Preparing pdf [3/4]') 45 | printPage.evaluate( function() { 46 | Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom ); 47 | } ); 48 | } ); 49 | 50 | printPage.onCallback = function( data ) { 51 | // For some reason we need to "jump the queue" for syntax highlighting to work. 52 | // See: http://stackoverflow.com/a/3580132/129269 53 | setTimeout( function() { 54 | console.log( 'Export PDF: Writing file [4/4]' ); 55 | printPage.render( outputFile ); 56 | console.log( 'Export PDF: Finished successfully!' ); 57 | phantom.exit(); 58 | }, 0 ); 59 | }; 60 | } 61 | else { 62 | 63 | console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' ); 64 | phantom.exit( 1 ); 65 | 66 | } 67 | } ); 68 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/search/search.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealSearch=t()}(this,(function(){"use strict"; 2 | /*! 3 | * Handles finding a text string anywhere in the slides and showing the next occurrence to the user 4 | * by navigatating to that slide and highlighting it. 5 | * 6 | * @author Jon Snyder , February 2013 7 | */return()=>{let e,t,n,l,o,i,r;function s(){t=document.createElement("div"),t.classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='\n\t\t',n=t.querySelector(".searchinput"),n.style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){if(13===t.keyCode)t.preventDefault(),function(){if(i){var t=n.value;""===t?(r&&r.remove(),l=null):(r=new c("slidecontent"),l=r.apply(t),o=0)}l&&(l.length&&l.length<=o&&(o=0),l.length>o&&(e.slide(l[o].h,l[o].v),o++))}(),i=!1;else i=!0}),!1),d()}function a(){t||s(),t.style.display="inline",n.focus(),n.select()}function d(){t||s(),t.style.display="none",r&&r.remove()}function c(t,n){var l=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),r=["#ff6","#a0ffff","#9f9","#f99","#f6f"],s=[],a=0,d="",c=[];this.setRegex=function(e){e=e.replace(/^[^\w]+|[^\w]+$/g,"").replace(/[^\w'-]+/g,"|"),d=new RegExp("("+e+")","i")},this.getRegex=function(){return d.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&d&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n{e=n,e.registerKeyboardShortcut("CTRL + Shift + F","Search"),document.addEventListener("keydown",(function(e){"F"==e.key&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),t||s(),"inline"!==t.style.display?a():d())}),!1)},open:a}}})); 8 | -------------------------------------------------------------------------------- /dependencies/reveal/plugin/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=t()}(this,(function(){"use strict"; 2 | /*! 3 | * reveal.js Zoom plugin 4 | */const e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))},destroy:()=>{t.reset()}};var t=function(){var e=1,o=0,n=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,o){var n=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,l)if(1===o)document.body.style.transform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,s(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}(); 5 | /*! 6 | * zoom.js 0.3 (modified for use with reveal.js) 7 | * http://lab.hakim.se/zoom-js 8 | * MIT licensed 9 | * 10 | * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se 11 | */return()=>e})); 12 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1741862977, 24 | "narHash": "sha256-prZ0M8vE/ghRGGZcflvxCu40ObKaB+ikn74/xQoNrGQ=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "cdd2ef009676ac92b715ff26630164bb88fec4e0", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-24.11", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A very basic flake"; 3 | 4 | inputs = { 5 | nixpkgs = { 6 | url = "github:NixOS/nixpkgs/nixos-24.11"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | flake-utils = { url = "github:numtide/flake-utils"; }; 10 | }; 11 | 12 | outputs = { self, nixpkgs, flake-utils }: 13 | flake-utils.lib.eachDefaultSystem (system: 14 | let pkgs = import nixpkgs { inherit system; }; 15 | in { devShell = import ./shell.nix { inherit pkgs; }; }); 16 | } 17 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const less = require('gulp-less'); 3 | const cleanCss = require('gulp-clean-css'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | gulp.task('clean-out', function (cb) { 8 | // Delete ./out folder 9 | if (fs.existsSync('./out')) { 10 | fs.rmSync('./out', { recursive: true }); 11 | } 12 | cb(); 13 | }); 14 | 15 | gulp.task('compile-less', function (cb) { 16 | // 1. Compile all *.less files in ./styles 17 | gulp 18 | .src('./styles/**/*.less') 19 | .pipe( 20 | less({ 21 | paths: [ 22 | path.join(__dirname, 'styles'), 23 | path.join(__dirname, 'styles/preview_theme'), 24 | path.join(__dirname, 'styles/prism_theme'), 25 | ], 26 | }), 27 | ) 28 | .pipe(cleanCss()) 29 | .pipe(gulp.dest('./out/styles')); 30 | 31 | // 2. Copy all files except *.less in ./styles to ./out/styles 32 | gulp 33 | .src(['./styles/**/*', '!./styles/**/*.less']) 34 | .pipe(cleanCss()) 35 | .pipe(gulp.dest('./out/styles')); 36 | 37 | cb(); 38 | }); 39 | 40 | // Whenever there is a change in ./styles, run 'compile-less' task 41 | gulp.task('watch-less', function (cb) { 42 | gulp.watch('./styles/**/*', gulp.series('compile-less')); 43 | 44 | cb(); 45 | }); 46 | 47 | gulp.task('copy-files', function (cb) { 48 | // Copy ./dependencies to ./out 49 | gulp.src('./dependencies/**/*').pipe(gulp.dest('./out/dependencies')); 50 | 51 | cb(); 52 | }); 53 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | collectCoverage: true, 4 | collectCoverageFrom: ['src/**/*.{ts,tsx,js}'], 5 | moduleFileExtensions: ['ts', 'tsx', 'js'], 6 | transform: { 7 | '\\.m?[tj]sx?$': ['babel-jest', { configFile: './babel-jest.config.js' }], 8 | }, 9 | transformIgnorePatterns: [ 10 | // '/node_modules/(?!\@sindresorhus/slugify)', 11 | // '/node_modules/(?!escape-string-regexp)', 12 | ], 13 | roots: ['test'], 14 | testMatch: ['**/?(*.)(spec|test).(j|t)s?(x)'], 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /**@type {import("prettier").Config} */ 2 | module.exports = { 3 | semi: true, 4 | arrowParens: 'always', 5 | endOfLine: 'lf', 6 | quoteProps: 'consistent', 7 | trailingComma: 'all', 8 | singleQuote: true, 9 | tabWidth: 2, 10 | }; 11 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | with pkgs; 3 | mkShell { 4 | buildInputs = [ nodejs_20 yarn ]; 5 | shellHook = '' 6 | # ... 7 | ''; 8 | } 9 | -------------------------------------------------------------------------------- /src/code-chunk/code-chunk-data.ts: -------------------------------------------------------------------------------- 1 | import { BlockInfo } from '../lib/block-info'; 2 | 3 | export interface CodeChunkData { 4 | /** 5 | * id of the code chunk 6 | */ 7 | id: string; 8 | /** 9 | * code content of the code chunk 10 | */ 11 | code: string; 12 | /** 13 | * code block info (normalized in advance) 14 | */ 15 | normalizedInfo: BlockInfo; 16 | /** 17 | * result after running code chunk 18 | */ 19 | plainResult: string; 20 | 21 | /** 22 | * result after formatting according to options['output'] format 23 | */ 24 | result: string; 25 | /** 26 | * whether is running the code chunk or not 27 | */ 28 | running: boolean; 29 | /** 30 | * previous code chunk 31 | */ 32 | prev: string; 33 | /** 34 | * next code chunk 35 | */ 36 | next: string | null; 37 | } 38 | 39 | export interface CodeChunksData { 40 | [key: string]: CodeChunkData; 41 | } 42 | -------------------------------------------------------------------------------- /src/converters/prince-convert.ts: -------------------------------------------------------------------------------- 1 | import { execFile } from 'child_process'; 2 | 3 | export function princeConvert(src, dest): Promise { 4 | return new Promise((resolve, reject) => { 5 | execFile('prince', [src, '--javascript', '-o', dest], (error: Error) => { 6 | if (error) { 7 | let errorMessage = error.toString(); 8 | if (error.message.indexOf('spawn prince ENOENT') >= 0) { 9 | errorMessage = '"princexml" is required to be installed.'; 10 | } 11 | return reject(errorMessage); 12 | } 13 | return resolve(); 14 | }); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/code-fences.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * modified to support math block 3 | * check https://github.com/jonschlinkert/remarkable/blob/875554aedb84c9dd190de8d0b86c65d2572eadd5/lib/rules.js 4 | */ 5 | 6 | // tslint:disable-next-line no-implicit-dependencies 7 | import { escape } from 'html-escaper'; 8 | import MarkdownIt from 'markdown-it'; 9 | import { normalizeBlockInfo, parseBlockInfo } from '../lib/block-info'; 10 | 11 | export default (md: MarkdownIt) => { 12 | md.renderer.rules.fence = (tokens, idx) => { 13 | const token = tokens[idx]; 14 | 15 | // get code info (same line as opening fence) 16 | const info = token.info.trim(); 17 | const parsedInfo = parseBlockInfo(info); 18 | const normalizedInfo = normalizeBlockInfo(parsedInfo); 19 | 20 | // get code content 21 | const content = escape(token.content); 22 | 23 | // copied from getBreak function. 24 | const finalBreak = 25 | idx < tokens.length && tokens[idx].type === 'list_item_close' ? '\n' : ''; 26 | 27 | // NOTE: The actual tag is added in the code-block-styling.ts. 28 | return `
${content}
${finalBreak}`; 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/critic-markup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * critic markup HTML LaTeX 3 | * {--[text]--} [text] \st{[text]} 4 | * {++[text]++} [text] \underline{[text]} 5 | * {~~[text1]~>[text2]~~} [text1][text2] \st{[text1]}\underline{[text2]} 6 | * {==[text]==} [text] \hl{[text]} 7 | * {>>[text]<<} \marginpar{[text]} 8 | */ 9 | 10 | // tslint:disable-next-line no-implicit-dependencies 11 | import MarkdownIt from 'markdown-it'; 12 | import { Notebook } from '../notebook'; 13 | 14 | export default (md: MarkdownIt, notebook: Notebook) => { 15 | md.inline.ruler.before('strikethrough', 'critic-markup', (state, silent) => { 16 | const config = notebook.config; 17 | if (!config.enableCriticMarkupSyntax) { 18 | return false; 19 | } 20 | 21 | const { src, pos } = state; 22 | if ( 23 | src[pos] === '{' && 24 | ((src[pos + 1] === '-' && src[pos + 2] === '-') || 25 | (src[pos + 1] === '+' && src[pos + 2] === '+') || 26 | (src[pos + 1] === '~' && src[pos + 2] === '~') || 27 | (src[pos + 1] === '=' && src[pos + 2] === '=') || 28 | (src[pos + 1] === '>' && src[pos + 2] === '>')) 29 | ) { 30 | const tag = src.slice(pos + 1, pos + 3); 31 | const closeTag = tag[0] === '>' ? '<<}' : `${tag}}`; 32 | 33 | let i = pos + 3; 34 | let end = -1; 35 | let content: string | null = null; 36 | while (i < src.length) { 37 | if (src.startsWith(closeTag, i)) { 38 | end = i; 39 | break; 40 | } 41 | 42 | i += 1; 43 | } 44 | 45 | if (end >= 0) { 46 | content = src.slice(pos + 3, end); 47 | } else { 48 | return false; 49 | } 50 | 51 | if (content && !silent) { 52 | const token = state.push('critic-markup', '', 0); 53 | token.content = content; 54 | token.tag = tag; 55 | state.pos = end + closeTag.length; 56 | return true; 57 | } else { 58 | return false; 59 | } 60 | } else { 61 | return false; 62 | } 63 | }); 64 | 65 | /** 66 | * CriticMarkup renderer 67 | */ 68 | md.renderer.rules['critic-markup'] = (tokens, idx) => { 69 | const token = tokens[idx]; 70 | const tag = token.tag; 71 | const content = token.content; 72 | if (tag === '--') { 73 | return `${content}`; 74 | } else if (tag === '++') { 75 | return `${content}`; 76 | } else if (tag === '==') { 77 | return `${content}`; 78 | } else if (tag === '>>') { 79 | return `${content}`; 80 | } else { 81 | // {~~[text1]~>[text2]~~} 82 | const arr = content.split('~>'); 83 | if (arr.length === 2) { 84 | return `${arr[0]}${arr[1]}`; 85 | } else { 86 | return `Error: ~> not found.`; 87 | } 88 | } 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/curly-bracket-attributes.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import Token from 'markdown-it/lib/token'; 3 | import { parseBlockAttributes } from '../lib/block-attributes'; 4 | 5 | export default (md: MarkdownIt) => { 6 | // Follow the same attributes syntax as pandoc & rmarkdown: 7 | // Parse the attribute {...} after: 8 | // - headings: # This is heading {...} 9 | // - links and images: [This is link](link){...} 10 | // For fenced code block, use the ./code-fences.ts 11 | md.core.ruler.push('curly_bracket_attributes', (state) => { 12 | const tokens = state.tokens; 13 | for (let i = 0; i < tokens.length; i++) { 14 | if (tokens[i].type === 'heading_open') { 15 | // const headingOpen = tokens[i]; 16 | const headingInline = tokens[i + 1]; 17 | if (headingInline.type === 'inline') { 18 | processInlineToken(headingInline); 19 | if (!headingInline.children || headingInline.children.length === 0) { 20 | continue; 21 | } 22 | const lastChild = 23 | headingInline.children[headingInline.children.length - 1]; 24 | if (lastChild.type === 'text') { 25 | const match = lastChild.content.match(/{([^}]+)}\s*$/); 26 | if (match) { 27 | const attributes = parseBlockAttributes(match[1]); 28 | lastChild.content = lastChild.content.replace(match[0], ''); 29 | for (const key in attributes) { 30 | tokens[i].attrJoin(key, attributes[key]); 31 | } 32 | } 33 | } 34 | } 35 | } else if (tokens[i].type === 'inline') { 36 | processInlineToken(tokens[i]); 37 | } 38 | 39 | // For inline code, use the ./code-fences.ts 40 | } 41 | }); 42 | 43 | function processInlineToken(token: Token) { 44 | const children = token.children; 45 | if (!children || children.length === 0) { 46 | return; 47 | } 48 | for (let j = 0; j < children.length; j++) { 49 | const child = children[j]; 50 | if (child.type === 'image' && children[j + 1]?.type === 'text') { 51 | const match = children[j + 1].content.match(/^{([^}]+)}/); 52 | if (match) { 53 | const attributes = parseBlockAttributes(match[1]); 54 | children[j + 1].content = children[j + 1].content.replace( 55 | match[0], 56 | '', 57 | ); 58 | for (const key in attributes) { 59 | child.attrJoin(key, attributes[key]); 60 | } 61 | } 62 | } else if (child.type === 'link_open') { 63 | // Find the next link_close 64 | let k = j + 1; 65 | while (k < children.length && children[k].type !== 'link_close') { 66 | k++; 67 | } 68 | j = k; 69 | if (k < children.length - 1 && children[k + 1].type === 'text') { 70 | const match = children[k + 1].content.match(/^{([^}]+)}/); 71 | if (match) { 72 | const attributes = parseBlockAttributes(match[1]); 73 | children[k + 1].content = children[k + 1].content.replace( 74 | match[0], 75 | '', 76 | ); 77 | for (const key in attributes) { 78 | child.attrJoin(key, attributes[key]); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/emoji.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-implicit-dependencies 2 | import MarkdownIt from 'markdown-it'; 3 | import MarkdownItEmoji from 'markdown-it-emoji'; 4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 5 | // @ts-expect-error 6 | import fullEmoji from 'markdown-it-emoji/lib/data/full.json' with { type: 'json' }; 7 | import { Notebook } from '../notebook'; 8 | import { fontawesomeObject, isFontawesomebrand } from './fontawesome'; 9 | 10 | export default (md: MarkdownIt, notebook: Notebook) => { 11 | md.use(MarkdownItEmoji, { 12 | defs: { ...fullEmoji, ...fontawesomeObject }, 13 | }); 14 | 15 | md.renderer.rules.emoji = (tokens, idx) => { 16 | const token = tokens[idx]; 17 | if (notebook.config.enableEmojiSyntax) { 18 | const markup = token.markup; 19 | if (markup.match('fa-')) { 20 | // We only support font-awesome 6 for now 21 | // font-awesome 22 | // fa: font-awesome 4 23 | // fas: font-awesome 5 solid 24 | // fab: font-awesome 5 brands 25 | // fa-brands: font-awesome 6 brands 26 | // fa-solid: font-awesome 6 solid 27 | 28 | return ``; 31 | } else { 32 | // emoji 33 | return token.content; 34 | } 35 | } else { 36 | return `:${token.markup}:`; 37 | } 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/html5-embed.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-implicit-dependencies 2 | import MarkdownIt from 'markdown-it'; 3 | import MarkdownItHtml5Embed from 'markdown-it-html5-embed'; 4 | import { Notebook } from '../notebook'; 5 | 6 | const optionsFromConfig = (notebook: Notebook) => ({ 7 | html5embed: { 8 | useImageSyntax: notebook.config.HTML5EmbedUseImageSyntax, 9 | useLinkSyntax: notebook.config.HTML5EmbedUseLinkSyntax, 10 | isAllowedHttp: notebook.config.HTML5EmbedIsAllowedHttp, 11 | attributes: { 12 | audio: notebook.config.HTML5EmbedAudioAttributes, 13 | video: notebook.config.HTML5EmbedVideoAttributes, 14 | }, 15 | }, 16 | }); 17 | 18 | export default (md: MarkdownIt, notebook: Notebook) => { 19 | if (!notebook.config.enableHTML5Embed) { 20 | return; 21 | } 22 | 23 | md.use(MarkdownItHtml5Embed, optionsFromConfig(notebook)); 24 | }; 25 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/math.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-implicit-dependencies 2 | import MarkdownIt from 'markdown-it'; 3 | import { Notebook } from '../notebook'; 4 | import parseMath from '../renderers/parse-math'; 5 | 6 | export default (md: MarkdownIt, notebook: Notebook) => { 7 | md.inline.ruler.before('escape', 'math', (state, silent) => { 8 | if (notebook.config.mathRenderingOption === 'None') { 9 | return false; 10 | } 11 | 12 | let openTag: string | null = null; 13 | let closeTag: string | null = null; 14 | let displayMode = true; 15 | const { 16 | mathBlockDelimiters: blockDelimiters, 17 | mathInlineDelimiters: inlineDelimiters, 18 | } = notebook.config; 19 | 20 | for (const tagPair of blockDelimiters) { 21 | if (state.src.startsWith(tagPair[0], state.pos)) { 22 | [openTag, closeTag] = tagPair; 23 | break; 24 | } 25 | } 26 | 27 | if (!openTag) { 28 | for (const tagPair of inlineDelimiters) { 29 | if (state.src.startsWith(tagPair[0], state.pos)) { 30 | [openTag, closeTag] = tagPair; 31 | displayMode = false; 32 | break; 33 | } 34 | } 35 | } 36 | 37 | if (!openTag || !closeTag) { 38 | return false; // not math 39 | } 40 | 41 | let content: string | null = null; 42 | let end = -1; 43 | 44 | let i = state.pos + openTag.length; 45 | while (i < state.src.length) { 46 | if (closeTag && state.src.startsWith(closeTag, i)) { 47 | end = i; 48 | break; 49 | } else if (state.src[i] === '\\') { 50 | i += 1; 51 | } 52 | i += 1; 53 | } 54 | 55 | if (end >= 0) { 56 | content = state.src.slice(state.pos + openTag.length, end); 57 | } else { 58 | return false; 59 | } 60 | 61 | if (content && !silent) { 62 | const token = state.push('math', '', 0); 63 | token.content = content.trim(); 64 | token.meta = token.meta || {}; 65 | token.meta.openTag = openTag; 66 | token.meta.closeTag = closeTag; 67 | token.meta.displayMode = displayMode; 68 | 69 | state.pos += content.length + openTag.length + closeTag.length; 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | }); 75 | 76 | md.renderer.rules.math = (tokens, idx) => { 77 | const content: string = tokens[idx].content ?? ''; 78 | return parseMath({ 79 | content, 80 | openTag: tokens[idx].meta.openTag, 81 | closeTag: tokens[idx].meta.closeTag, 82 | renderingOption: notebook.config.mathRenderingOption, 83 | displayMode: tokens[idx].meta.displayMode, 84 | katexConfig: notebook.config.katexConfig, 85 | }); 86 | }; 87 | }; 88 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/sourcemap.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import { ExtendedMarkdownItOptions } from '../notebook'; 3 | 4 | /** 5 | * Add sourcemap to the rendered HTML. 6 | */ 7 | export default (md: MarkdownIt) => { 8 | const defaultRenderer = md.renderer.renderToken.bind(md.renderer); 9 | md.renderer.renderToken = function ( 10 | tokens, 11 | idx, 12 | options: ExtendedMarkdownItOptions, 13 | ) { 14 | if (!options.sourceMap) { 15 | return defaultRenderer(tokens, idx, options); 16 | } 17 | 18 | const token = tokens[idx]; 19 | if ( 20 | token.type.endsWith('_open') && 21 | token.map !== null && 22 | token.map[0] !== undefined 23 | ) { 24 | token.attrSet('data-source-line', `${token.map[0] + 1}`); 25 | } 26 | return defaultRenderer(tokens, idx, options); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/widget.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import Renderer from 'markdown-it/lib/renderer'; 3 | import { Notebook } from '../notebook'; 4 | 5 | /** 6 | * 7 | * NOTE: The following syntax is under design. 8 | * 9 | * The crossnote widget has the following syntax by extending the markdown-it image syntax 10 | * 11 | * ![@widget-name](script-path){attribute1=value1 attribute2=value2} 12 | * 13 | */ 14 | export default (md: MarkdownIt, notebook: Notebook) => { 15 | const defaultImageRenderer: Renderer.RenderRule = 16 | md.renderer.rules.image!.bind(md.renderer); 17 | 18 | md.renderer.rules.image = (tokens, idx, options, env, renderer) => { 19 | const token = tokens[idx]; 20 | /* 21 | const attributes = (token.attrs ?? []).reduce((acc, attr) => { 22 | acc[attr[0]] = attr[1]; 23 | return acc; 24 | }, {}); 25 | */ 26 | // const src = token.attrGet('src'); 27 | /* 28 | if (src && alt && alt.startsWith("@")) { 29 | const widgetName = alt.slice(1); 30 | const widget = notebok.widgets.find((widget) => widget.name === widgetName); 31 | if (widget) { 32 | return `
`; 35 | } 36 | } 37 | */ 38 | if (token.children && token.children[0]?.content === '@embedding') { 39 | const error = token.attrGet('error') ?? ''; 40 | if (error) { 41 | return decodeURIComponent(atob(error)); 42 | } else { 43 | const embedding = token.attrGet('embedding') ?? ''; 44 | const decoded = decodeURIComponent(atob(embedding)); 45 | 46 | // NOTE: We disable the source map here. 47 | const newMd = notebook.initMarkdownIt({ 48 | ...md.options, 49 | sourceMap: false, 50 | }); 51 | const rendered = newMd.render(decoded); 52 | return rendered; 53 | } 54 | } 55 | 56 | return defaultImageRenderer(tokens, idx, options, env, renderer); 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/custom-markdown-it-features/wikilink.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * inline [[]] 3 | * [[...]] 4 | */ 5 | 6 | import MarkdownIt from 'markdown-it'; 7 | import { Notebook } from '../notebook'; 8 | 9 | export default (md: MarkdownIt, notebook: Notebook) => { 10 | md.inline.ruler.before('autolink', 'wikilink', (state, silent) => { 11 | if ( 12 | !notebook.config.enableWikiLinkSyntax || 13 | !state.src.startsWith('[[', state.pos) 14 | ) { 15 | return false; 16 | } 17 | 18 | let content: string | null = null; 19 | const tag = ']]'; 20 | let end = -1; 21 | 22 | let i = state.pos + tag.length; 23 | while (i < state.src.length) { 24 | if (state.src[i] === '\\') { 25 | i += 1; 26 | } else if (state.src.startsWith(tag, i)) { 27 | end = i; 28 | break; 29 | } 30 | i += 1; 31 | } 32 | 33 | if (end >= 0) { 34 | // found ]] 35 | content = state.src.slice(state.pos + tag.length, end); 36 | } else { 37 | return false; 38 | } 39 | 40 | if (content && !silent) { 41 | const token = state.push('wikilink', 'a', 0); 42 | token.content = content; 43 | 44 | state.pos += content.length + 2 * tag.length; 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | }); 50 | 51 | md.renderer.rules.wikilink = (tokens, idx) => { 52 | const { content } = tokens[idx]; 53 | if (!content) { 54 | return ''; 55 | } 56 | 57 | const { text, link } = notebook.processWikilink(content); 58 | 59 | return `${text}`; 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import HeadingIdGenerator from './markdown-engine/heading-id-generator'; 2 | import { 3 | addFileProtocol, 4 | getCrossnoteBuildDirectory, 5 | openFile, 6 | setCrossnoteBuildDirectory, 7 | uploadImage, 8 | useExternalAddFileProtocolFunction, 9 | } from './utility'; 10 | 11 | export const utility = { 12 | addFileProtocol, 13 | getCrossnoteBuildDirectory, 14 | openFile, 15 | setCrossnoteBuildDirectory, 16 | useExternalAddFileProtocolFunction, 17 | uploadImage, 18 | }; 19 | export * from './code-chunk/code-chunk-data'; 20 | export * from './markdown-engine'; 21 | export * from './markdown-engine/transformer'; 22 | export * from './notebook'; 23 | export { 24 | loadConfigsInDirectory, 25 | wrapNodeFSAsApi, 26 | } from './notebook/config-helper'; 27 | export { HeadingIdGenerator }; 28 | -------------------------------------------------------------------------------- /src/lib/block-attributes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './normalizeBlockAttributes'; 2 | export * from './parseBlockAttributes'; 3 | export * from './stringifyBlockAttributes'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /src/lib/block-attributes/normalizeBlockAttributes.ts: -------------------------------------------------------------------------------- 1 | import { snakeCase } from 'case-anything'; 2 | import { BlockAttributes } from './types'; 3 | 4 | /** 5 | * Walks through attribute keys and makes them kebabCase if needed 6 | * https://www.npmjs.com/package/case-anything 7 | * @param attributes 8 | */ 9 | export const normalizeBlockAttributes = ( 10 | attributes: BlockAttributes, 11 | ): BlockAttributes => { 12 | if (typeof attributes !== 'object') { 13 | return {}; 14 | } 15 | let changed = false; 16 | const result = { ...attributes }; 17 | 18 | for (const key in attributes) { 19 | if (Object.prototype.hasOwnProperty.call(attributes, key)) { 20 | // NOTE: Don't normalize the key that starts with `data-` or `aria-` 21 | if (key.startsWith('data-') || key.startsWith('aria-')) { 22 | continue; 23 | } 24 | 25 | const normalizedKey = snakeCase(key); 26 | if (normalizedKey !== key) { 27 | result[normalizedKey] = result[key]; 28 | delete result[key]; 29 | changed = true; 30 | } 31 | } 32 | } 33 | 34 | return changed ? result : attributes; 35 | }; 36 | -------------------------------------------------------------------------------- /src/lib/block-attributes/stringifyBlockAttributes.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes } from './types'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | const stringifyArray = (value: any[]) => { 5 | const parts = ['[']; 6 | value.forEach((v, i) => { 7 | if (v instanceof Array) { 8 | parts.push(stringifyArray(v)); 9 | } else { 10 | parts.push(JSON.stringify(v)); 11 | } 12 | if (i + 1 !== value.length) { 13 | parts.push(', '); 14 | } 15 | }); 16 | parts.push(']'); 17 | 18 | return parts.join(''); 19 | }; 20 | 21 | /** 22 | * Convert attributes as JSON object to attributes as string 23 | * @param attributes 24 | */ 25 | export const stringifyBlockAttributes = ( 26 | attributes: BlockAttributes = {}, 27 | addCurlyBrackets = false, 28 | ): string => { 29 | const parts: string[] = []; 30 | for (const key in attributes) { 31 | if (Object.prototype.hasOwnProperty.call(attributes, key)) { 32 | parts.push(' '); 33 | parts.push(`${key}=`); 34 | const value = attributes[key]; 35 | if (value instanceof Array) { 36 | parts.push(stringifyArray(value)); 37 | } else { 38 | parts.push(JSON.stringify(value)); 39 | } 40 | } 41 | } 42 | parts.shift(); 43 | if (addCurlyBrackets) { 44 | parts.unshift('{'); 45 | parts.push('}'); 46 | } 47 | 48 | return parts.join(''); 49 | }; 50 | -------------------------------------------------------------------------------- /src/lib/block-attributes/types.ts: -------------------------------------------------------------------------------- 1 | export interface BlockAttributes { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | [key: string]: any; 4 | id?: string; 5 | class?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/block-info/index.ts: -------------------------------------------------------------------------------- 1 | export * from './normalize-block-info'; 2 | export * from './parse-block-info'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /src/lib/block-info/normalize-block-info.ts: -------------------------------------------------------------------------------- 1 | import { normalizeBlockAttributes } from '../block-attributes/normalizeBlockAttributes'; 2 | 3 | import { BlockInfo } from './types'; 4 | 5 | const normalizeLanguage = (language?: string): string => { 6 | if (typeof language === 'string') { 7 | return language.trim().toLowerCase(); 8 | } 9 | 10 | return ''; 11 | }; 12 | 13 | export const normalizeBlockInfo = (blockInfo: BlockInfo): BlockInfo => { 14 | const normalizedAttributes = normalizeBlockAttributes(blockInfo.attributes); 15 | const normalizedLanguage = normalizeLanguage(blockInfo.language); 16 | if ( 17 | normalizedAttributes !== blockInfo.attributes || 18 | normalizedLanguage !== blockInfo.language 19 | ) { 20 | return { 21 | language: normalizedLanguage, 22 | attributes: normalizedAttributes, 23 | }; 24 | } 25 | 26 | return blockInfo; 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/block-info/parse-block-info.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes, parseBlockAttributes } from '../block-attributes'; 2 | import { BlockInfo } from './types'; 3 | 4 | export const parseBlockInfo = (raw = ''): BlockInfo => { 5 | let language: string | undefined; 6 | let attributesAsString: string; 7 | let attributes: BlockAttributes; 8 | const trimmedParams = raw.trim(); 9 | const match = 10 | trimmedParams.indexOf('{') !== -1 11 | ? trimmedParams.match(/^([^\s{]*)\s*\{(.*?)\}/) 12 | : trimmedParams.match(/^([^\s]+)\s+(.+?)$/); 13 | 14 | if (match) { 15 | if (match[1].length) { 16 | language = match[1]; 17 | } 18 | attributesAsString = match[2]; 19 | } else { 20 | language = trimmedParams; 21 | attributesAsString = ''; 22 | } 23 | 24 | if (attributesAsString) { 25 | try { 26 | attributes = parseBlockAttributes(attributesAsString); 27 | } catch (e) { 28 | attributes = {}; 29 | } 30 | } else { 31 | attributes = {}; 32 | } 33 | 34 | const classNames = attributes.class ? attributes.class.split(/\s+/) : []; 35 | if (!language) { 36 | language = classNames[0] || ''; 37 | } 38 | if (!classNames.includes(language)) { 39 | attributes.class = [language, ...classNames].join(' '); 40 | } 41 | return { language, attributes }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/lib/block-info/types.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes } from '../block-attributes/types'; 2 | 3 | export interface BlockInfo { 4 | attributes: BlockAttributes; 5 | derivedAttributes?: BlockAttributes; 6 | language: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/compute-checksum.ts: -------------------------------------------------------------------------------- 1 | import md5 from 'md5'; 2 | 3 | export type HashFunction = (text: string) => string; 4 | 5 | const computeChecksum: HashFunction = (text) => { 6 | return md5(text).toString(); 7 | }; 8 | 9 | // md5 can be replaced with a quicker and more robust hash in future 10 | export default computeChecksum; 11 | -------------------------------------------------------------------------------- /src/markdown-engine/custom-subjects.ts: -------------------------------------------------------------------------------- 1 | export enum CustomSubjects { 2 | pagebreak, 3 | newpage, 4 | toc, 5 | tocstop, 6 | slide, 7 | '.slide', 8 | '.slide:', 9 | ebook, 10 | 'toc-bracket', 11 | } 12 | -------------------------------------------------------------------------------- /src/markdown-engine/extension-helper.ts: -------------------------------------------------------------------------------- 1 | // modified according to the prism website 2 | // http://prismjs.com/#languages-list 3 | const scopesForLanguageName = { 4 | 'sh': 'bash', 5 | 'bash': 'bash', 6 | 'c': 'c', 7 | 'c++': 'cpp', 8 | 'cpp': 'cpp', 9 | 'coffee': 'coffeescript', 10 | 'coffeescript': 'coffeescript', 11 | 'coffee-script': 'coffeescript', 12 | 'cs': 'csharp', 13 | 'csharp': 'csharp', 14 | 'css': 'css', 15 | 'scss': 'scss', 16 | 'sass': 'sass', 17 | 'elm': 'elm', 18 | 'elmlang': 'elm', 19 | 'elm-lang': 'elm', 20 | 'erlang': 'erlang', 21 | 'go': 'go', 22 | 'html': 'html', 23 | 'java': 'java', 24 | 'js': 'javascript', 25 | 'javascript': 'javascript', 26 | 'json': 'json', 27 | 'less': 'less', 28 | 'objc': 'objectivec', 29 | 'objectivec': 'objectivec', 30 | 'objective-c': 'objectivec', 31 | 'php': 'php', 32 | 'py': 'python', 33 | 'python': 'python', 34 | 'rb': 'ruby', 35 | 'ruby': 'ruby', 36 | 'text': 'text', 37 | 'tex': 'latex', 38 | 'xml': 'xml', 39 | 'yaml': 'yaml', 40 | 'yml': 'yaml', 41 | // extended 42 | 'yaml_table': 'yaml', 43 | 'mermaid': 'mermaid', 44 | 'plantuml': 'plantuml', 45 | 'puml': 'plantuml', 46 | 'wavedrom': 'wavedrom', 47 | 'graphviz': 'dot', 48 | 'viz': 'dot', 49 | 'dot': 'dot', 50 | 'erd': 'erd', 51 | 'node': 'javascript', 52 | 'md': 'gfm', 53 | 'diff': 'diff', 54 | }; 55 | 56 | export function scopeForLanguageName(language) { 57 | language = language.toLowerCase(); 58 | return scopesForLanguageName[language] || language; 59 | } 60 | -------------------------------------------------------------------------------- /src/markdown-engine/heading-id-generator.ts: -------------------------------------------------------------------------------- 1 | import uslug from 'uslug'; 2 | 3 | export default class HeadingIdGenerator { 4 | private table: { [key: string]: number }; 5 | constructor() { 6 | this.table = {}; 7 | } 8 | public generateId(heading: string): string { 9 | const replacement = (match: string, capture: string): string => { 10 | const sanitized = capture 11 | .replace(/[!"#$%&'()*+,./:;<=>?@[\\]^`{|}~]/g, '') 12 | .replace(/^\s/, '') 13 | .replace(/\s$/, '') 14 | .replace(/`/g, '~'); 15 | return ( 16 | (capture.match(/^\s+$/) ? '~' : sanitized) + 17 | (match.endsWith(' ') && !sanitized.endsWith('~') ? '~' : '') 18 | ); 19 | }; 20 | heading = heading 21 | .trim() 22 | .replace(/~|。/g, '') // sanitize 23 | .replace(/``(.+?)``\s?/g, replacement) 24 | .replace(/`(.*?)`\s?/g, replacement) 25 | .replace(/\s__([^_]+?)__\s/g, `-$1-`) 26 | .replace(/\s_([^_]+?)_\s/g, `-$1-`); 27 | let slug = uslug(heading.replace(/\s/g, '~')).replace(/~/g, '-'); 28 | if (this.table[slug] >= 0) { 29 | this.table[slug] = this.table[slug] + 1; 30 | slug += '-' + this.table[slug]; 31 | } else { 32 | this.table[slug] = 0; 33 | } 34 | return slug; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/notebook/graph-view.ts: -------------------------------------------------------------------------------- 1 | import hash from 'object-hash'; 2 | import { basename } from 'path'; 3 | import { Notebook } from '.'; 4 | export interface GraphViewNode { 5 | id: string; 6 | label: string; 7 | } 8 | 9 | export interface GraphViewLink { 10 | source: string; 11 | target: string; 12 | } 13 | 14 | export interface GraphViewData { 15 | hash: string; 16 | nodes: GraphViewNode[]; 17 | links: GraphViewLink[]; 18 | } 19 | 20 | export function constructGraphView(notebook: Notebook): GraphViewData { 21 | const nodes: GraphViewNode[] = []; 22 | const links: GraphViewLink[] = []; 23 | 24 | const addedNodes: { [key: string]: boolean } = {}; 25 | const addNode = (filePath: string) => { 26 | if (filePath in addedNodes) { 27 | return; 28 | } 29 | const note = notebook.notes[filePath]; 30 | if (note) { 31 | nodes.push({ 32 | id: filePath, 33 | label: note.title, 34 | }); 35 | } else { 36 | const lastIndex = filePath.lastIndexOf('.'); 37 | let label = filePath; 38 | if (lastIndex > 0) { 39 | label = filePath.slice(0, lastIndex); 40 | } 41 | nodes.push({ 42 | id: filePath, 43 | label: basename(label), 44 | }); 45 | } 46 | addedNodes[filePath] = true; 47 | }; 48 | 49 | for (const filePath in notebook.referenceMap.map) { 50 | // eslint-disable-next-line no-prototype-builtins 51 | if (notebook.referenceMap.map.hasOwnProperty(filePath)) { 52 | addNode(filePath); 53 | for (const referredByFilePath in notebook.referenceMap.map[filePath]) { 54 | if ( 55 | // eslint-disable-next-line no-prototype-builtins 56 | notebook.referenceMap.map[filePath].hasOwnProperty(referredByFilePath) 57 | ) { 58 | addNode(referredByFilePath); 59 | links.push({ 60 | source: referredByFilePath, 61 | target: filePath, 62 | }); 63 | } 64 | } 65 | } 66 | } 67 | 68 | return { 69 | hash: hash({ nodes, links }), 70 | nodes, 71 | links, 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/notebook/markdown.ts: -------------------------------------------------------------------------------- 1 | // const loadLanguages = require("prismjs/components/"); 2 | // loadLanguages(["python"]); 3 | 4 | import { JsonObject } from 'type-fest'; 5 | import * as YAML from 'yaml'; 6 | 7 | export const TagStopRegExp = /[@#,.!$%^&*()[\]-_+=~`<>?\\,。]/g; 8 | export function getTags(markdown: string): string[] { 9 | const tags = new Set( 10 | markdown.match( 11 | /(#([^#]+?)#[\s@#,.!$%^&*()[\]-_+=~`<>?\\,。])|(#[^\s@#,.!$%^&*()[\]-_+=~`<>?\\,。]+)/g, 12 | ) || [], 13 | ); 14 | return Array.from(tags).map( 15 | (tag) => tag.replace(TagStopRegExp, '').trim(), // Don't remove \s here 16 | ); 17 | } 18 | 19 | export function sanitizeTag(tagName: string): string { 20 | const tag = tagName.trim() || ''; 21 | return tag 22 | .replace(/\s+/g, ' ') 23 | .replace(TagStopRegExp, '') 24 | .split('/') 25 | .map((t) => t.trim()) 26 | .filter((x) => x.length > 0) 27 | .join('/'); 28 | } 29 | 30 | export function sanitizeNoteTitle(noteTitle: string): string { 31 | return sanitizeTag(noteTitle); 32 | } 33 | 34 | export interface MatterOutput { 35 | data: JsonObject; 36 | content: string; 37 | } 38 | 39 | export function matter(markdown: string): MatterOutput { 40 | let endFrontMatterOffset = 0; 41 | let frontMatter = {}; 42 | if ( 43 | markdown.startsWith('---') && 44 | /* tslint:disable-next-line:no-conditional-assignment */ 45 | (endFrontMatterOffset = markdown.indexOf('\n---')) > 0 46 | ) { 47 | const frontMatterString = markdown.slice(3, endFrontMatterOffset); 48 | try { 49 | frontMatter = YAML.parse(frontMatterString); 50 | } catch { 51 | frontMatter = {}; 52 | } 53 | markdown = markdown 54 | .slice(endFrontMatterOffset + 4) 55 | .replace(/^[ \t]*\n/, ''); 56 | } 57 | return { 58 | data: frontMatter, 59 | content: markdown, 60 | }; 61 | } 62 | 63 | export function matterStringify(markdown: string, frontMatter: JsonObject) { 64 | frontMatter = frontMatter || {}; 65 | const yamlStr = YAML.stringify(frontMatter).trim(); 66 | if (yamlStr === '{}' || !yamlStr) { 67 | return markdown; 68 | } else { 69 | return `--- 70 | ${yamlStr} 71 | --- 72 | ${markdown}`; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/notebook/note.ts: -------------------------------------------------------------------------------- 1 | import { URI } from 'vscode-uri'; 2 | 3 | export type FilePath = string; 4 | 5 | export interface NoteConfigEncryption { 6 | title: string; 7 | // method: string;? // Default AES256 8 | } 9 | 10 | export interface NoteConfig { 11 | createdAt: Date; 12 | modifiedAt: Date; 13 | pinned?: boolean; 14 | favorited?: boolean; 15 | icon?: string; 16 | aliases?: string[]; 17 | } 18 | 19 | export type Mentions = Set; 20 | 21 | export interface Note { 22 | /** 23 | * Absolute path to the notebook. 24 | */ 25 | notebookPath: URI; 26 | /** 27 | * Relative path to the note file from the notebook. 28 | */ 29 | filePath: FilePath; 30 | /** 31 | * Note title. 32 | */ 33 | title: string; 34 | /** 35 | * Note content. 36 | */ 37 | markdown: string; 38 | /** 39 | * Note config. 40 | */ 41 | config: NoteConfig; 42 | /** 43 | * @param key: mentioned note file path 44 | */ 45 | mentions: Mentions; 46 | } 47 | 48 | export interface Notes { 49 | [key: string]: Note; 50 | } 51 | 52 | export function getNoteIcon(note: Note) { 53 | if (note.config.icon) { 54 | return note.config.icon; 55 | } else { 56 | return ':memo:'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/notebook/reference.ts: -------------------------------------------------------------------------------- 1 | import Token from 'markdown-it/lib/token'; 2 | 3 | export interface Reference { 4 | /** 5 | * The id of the referenced element 6 | */ 7 | elementId: string; 8 | parentToken: Token | null; 9 | token: Token; 10 | text: string; 11 | link: string; 12 | } 13 | 14 | export class ReferenceMap { 15 | public map: { [key: string]: { [key: string]: Reference[] } }; 16 | constructor() { 17 | this.map = {}; 18 | } 19 | 20 | public addReference( 21 | noteFilePath: string, 22 | referredByNoteFilePath: string, 23 | reference?: Reference, 24 | ) { 25 | if (noteFilePath === referredByNoteFilePath && !reference) { 26 | if (!(noteFilePath in this.map)) { 27 | this.map[noteFilePath] = { 28 | [referredByNoteFilePath]: [], 29 | }; 30 | } 31 | return; 32 | } 33 | if (!reference) { 34 | return; 35 | } 36 | if (noteFilePath in this.map) { 37 | const mentionedBys = this.map[noteFilePath]; 38 | if (referredByNoteFilePath in mentionedBys) { 39 | mentionedBys[referredByNoteFilePath].push(reference); 40 | } else { 41 | mentionedBys[referredByNoteFilePath] = [reference]; 42 | } 43 | } else { 44 | this.map[noteFilePath] = { 45 | [referredByNoteFilePath]: [reference], 46 | }; 47 | } 48 | } 49 | 50 | public deleteReferences( 51 | noteFilePath: string, 52 | referredByNoteFilePath: string, 53 | ) { 54 | if (noteFilePath === referredByNoteFilePath) { 55 | delete this.map[noteFilePath]; 56 | return; 57 | } 58 | if (noteFilePath in this.map) { 59 | if (referredByNoteFilePath in this.map[noteFilePath]) { 60 | delete this.map[noteFilePath][referredByNoteFilePath]; 61 | if (Object.keys(this.map[noteFilePath]).length === 0) { 62 | delete this.map[noteFilePath]; 63 | } 64 | } 65 | } 66 | } 67 | 68 | public hasRelation(filePath1: string, filePath2: string) { 69 | return ( 70 | (filePath1 in this.map && filePath2 in this.map[filePath1]) || 71 | (filePath2 in this.map && filePath1 in this.map[filePath2]) || 72 | filePath1 === filePath2 73 | ); 74 | } 75 | 76 | public getReferences( 77 | noteFilePath: string, 78 | referredByNoteFilePath: string, 79 | ): Reference[] { 80 | if (noteFilePath in this.map) { 81 | if (referredByNoteFilePath in this.map[noteFilePath]) { 82 | return this.map[noteFilePath][referredByNoteFilePath]; 83 | } 84 | } 85 | return []; 86 | } 87 | 88 | public noteHasReferences(filePath: string): boolean { 89 | return filePath in this.map; 90 | } 91 | 92 | public getReferredByNotesCount(noteFilePath: string): number { 93 | if (noteFilePath in this.map) { 94 | let length = Object.keys(this.map[noteFilePath]).length; 95 | if (noteFilePath in this.map[noteFilePath]) { 96 | length -= 1; 97 | } 98 | return length; 99 | } else { 100 | return 0; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/notebook/search.ts: -------------------------------------------------------------------------------- 1 | import MiniSearch, { SearchOptions, SearchResult } from 'minisearch'; 2 | 3 | export function slugify(str: string, separater = '-'): string { 4 | return str.replace( 5 | /[.\s!@#$%^&*()\-=_+~`[\]{}\\<>?/|()【】::,。]+/g, 6 | separater, 7 | ); 8 | } 9 | 10 | export interface SearchDoc { 11 | id: string; 12 | title: string; 13 | filePath: string; 14 | aliases: string[]; 15 | } 16 | 17 | export default class Search { 18 | private miniSearch: MiniSearch; 19 | 20 | /** 21 | * filePath -> SearchDoc 22 | */ 23 | private _cache: { [key: string]: SearchDoc }; 24 | constructor() { 25 | this.miniSearch = new MiniSearch({ 26 | fields: ['title', 'aliases', 'filePath'], 27 | storeFields: ['title', 'aliases', 'filePath'], 28 | extractField: (document, fieldName) => { 29 | if (fieldName === 'aliases') { 30 | return document['aliases'].join('|'); 31 | } else { 32 | return document[fieldName]; 33 | } 34 | }, 35 | tokenize: (string) => { 36 | // eslint-disable-next-line no-control-regex 37 | return slugify(string, ' ').match(/([^\x00-\x7F]|\w+)/g) ?? []; 38 | }, 39 | }); 40 | this._cache = {}; 41 | } 42 | 43 | add(filePath: string, title: string, aliases: string[] = []) { 44 | if (!(filePath in this._cache)) { 45 | const searchDoc = { 46 | id: filePath + '#' + title, 47 | filePath, 48 | title, 49 | aliases, 50 | }; 51 | this.miniSearch.add(searchDoc); 52 | this._cache[filePath] = searchDoc; 53 | } else { 54 | return; 55 | } 56 | } 57 | 58 | remove(filePath: string) { 59 | if (filePath in this._cache) { 60 | const doc = this._cache[filePath]; 61 | if (doc) { 62 | this.miniSearch.remove(doc); 63 | } 64 | delete this._cache[filePath]; 65 | } 66 | } 67 | 68 | search(queryString: string, options?: SearchOptions): SearchResult[] { 69 | return this.miniSearch.search(queryString, options); 70 | } 71 | 72 | addAlias(filePath: string, alias: string) { 73 | const searchDoc = this._cache[filePath]; 74 | if (!searchDoc) { 75 | return; 76 | } 77 | this.remove(filePath); 78 | this.add(filePath, searchDoc.title, searchDoc.aliases.concat(alias)); 79 | } 80 | 81 | deleteAlias(filePath: string, alias: string) { 82 | const searchDoc = this._cache[filePath]; 83 | if (!searchDoc) { 84 | return; 85 | } 86 | this.remove(filePath); 87 | this.add( 88 | filePath, 89 | searchDoc.title, 90 | searchDoc.aliases.filter((a) => a !== alias), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/notebook/slash.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/sindresorhus/slash 2 | export default function slash(path) { 3 | const isExtendedLengthPath = path.startsWith('\\\\?\\'); 4 | 5 | if (isExtendedLengthPath) { 6 | return path; 7 | } 8 | 9 | return path.replace(/\\/g, '/'); 10 | } 11 | -------------------------------------------------------------------------------- /src/prism/README.md: -------------------------------------------------------------------------------- 1 | The `prism.js` is downloaded from https://prismjs.com/download.html with all languages selected. 2 | 3 | NOTE: We have to disable the `_self = window` line in `prism.js` to make it work with VSCode web extension. 4 | 5 | Version: 0.12.9 6 | 7 | Nice theme generator: https://prism.dotenv.dev/ 8 | -------------------------------------------------------------------------------- /src/prism/iele.ts: -------------------------------------------------------------------------------- 1 | export default function (Prism) { 2 | Prism.languages.iele = { 3 | comment: [ 4 | { 5 | pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, 6 | lookbehind: true, 7 | }, 8 | { 9 | pattern: /(^|[^\\:])\/\/.*/, 10 | lookbehind: true, 11 | greedy: true, 12 | }, 13 | ], 14 | variable: { 15 | pattern: 16 | /(@|%)([A-Za-z0-9\\_\\.\\-\\$])[a-zA-Z\\.\\_\\$][0-9a-zA-Z\\.\\_\\-\\$]*/, 17 | greedy: true, 18 | }, 19 | string: { 20 | pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, 21 | greedy: true, 22 | }, 23 | keyword: 24 | /\b(?:load|store|sload|sstore|iszero|not|add|mul|sub|div|exp|mod|addmod|mulmod|expmod|byte|sext|twos|and|or|xor|shift|lt|le|gt|ge|eq|ne|cmp|sha3|br|call|staticcall|at|send|gaslimit|ret|void|revert|log|create|copycreate|selfdestruct|contract|external|define|public|log2|bswap|calladdress)\b/, 25 | label: /(?!\d)(?:[-$.\w]|\\[a-f\d]{2})+:/i, 26 | boolean: /\b(?:true|false)\b/, 27 | number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, 28 | operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, 29 | punctuation: /[{}[\];(),.:]/, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/prism/k.ts: -------------------------------------------------------------------------------- 1 | export default function (Prism) { 2 | Prism.languages.k = { 3 | 'comment': [ 4 | { 5 | pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, 6 | lookbehind: true, 7 | }, 8 | { 9 | pattern: /(^|[^\\:])\/\/.*/, 10 | lookbehind: true, 11 | greedy: true, 12 | }, 13 | ], 14 | 'string': { 15 | pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, 16 | greedy: true, 17 | }, 18 | 'class-name': 19 | /\b(?:strict|avoid|prefer|bracket|non-assoc|seqstrict|left|right|macro-rec|macro|token|notInRules|autoReject|structural|latex|binder|klabel|symbol|format)\b/, 20 | 'keyword': { 21 | pattern: 22 | /\b(?:syntax\s+(?:priority|priorities|left|right|non-assoc|lexical)|syntax|rule|Id|Int|Bool|String|Token|Lexer|Float|configuration|import|imports|require|requires|Kresult|context\s+alias|context|module|endmodule|claim)\b/, 23 | greedy: true, 24 | }, 25 | 'boolean': /\b(?:true|false)\b/, 26 | 'number': /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, 27 | 'operator': { 28 | pattern: /[<>]=|=>|~>|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, 29 | }, 30 | 'punctuation': /[{}[\];(),.:]/, 31 | // copied from prism-markup.js 32 | 'tag': { 33 | pattern: 34 | /<\/?(?!\d)[^\s>/=$<%]+(?:\s(?:\s*[^\s>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, 35 | greedy: true, 36 | inside: { 37 | 'tag': { 38 | pattern: /^<\/?[^\s>/]+/, 39 | inside: { 40 | punctuation: /^<\/?/, 41 | namespace: /^[^\s>/:]+:/, 42 | }, 43 | }, 44 | 'attr-value': { 45 | pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, 46 | inside: { 47 | punctuation: [ 48 | { 49 | pattern: /^=/, 50 | alias: 'attr-equals', 51 | }, 52 | /"|'/, 53 | ], 54 | }, 55 | }, 56 | 'punctuation': /\/?>/, 57 | 'attr-name': { 58 | pattern: /[^\s>/]+/, 59 | inside: { 60 | namespace: /^[^\s>/:]+:/, 61 | }, 62 | }, 63 | }, 64 | }, 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/render-enhancers/embedded-local-images.ts: -------------------------------------------------------------------------------- 1 | import { extname } from 'path'; 2 | import { Notebook } from '../notebook'; 3 | import { removeFileProtocol } from '../utility'; 4 | 5 | /** 6 | * Embed local images. Load the image file and display it in base64 format 7 | */ 8 | export default async function enhance( 9 | $: CheerioStatic, 10 | notebook: Notebook, 11 | resolveFilePath: (path: string, useRelativeFilePath: boolean) => string, 12 | ): Promise { 13 | const asyncFunctions: Promise[] = []; 14 | 15 | $('img').each((i, img) => { 16 | const $img = $(img); 17 | let src = resolveFilePath($img.attr('src'), false); 18 | 19 | const fileProtocolMatch = src.match(/^(file|vscode-resource):\/\/+/); 20 | if (fileProtocolMatch) { 21 | src = removeFileProtocol(src); 22 | src = src.replace(/\?(\.|\d)+$/, ''); // remove cache 23 | const imageType = extname(src).slice(1); 24 | if (imageType === 'svg') { 25 | return; 26 | } 27 | asyncFunctions.push( 28 | new Promise((resolve) => { 29 | notebook.fs 30 | .readFile(decodeURI(src), 'base64') 31 | .then((base64) => { 32 | // const base64 = new Buffer(data).toString('base64'); 33 | $img.attr( 34 | 'src', 35 | `data:image/${imageType};charset=utf-8;base64,${base64}`, 36 | ); 37 | return resolve(base64); 38 | }) 39 | .catch((error) => { 40 | console.error(error); 41 | return resolve(null); 42 | }); 43 | }), 44 | ); 45 | } 46 | }); 47 | 48 | await Promise.all(asyncFunctions); 49 | } 50 | -------------------------------------------------------------------------------- /src/render-enhancers/embedded-svgs.ts: -------------------------------------------------------------------------------- 1 | import { extname } from 'path'; 2 | import { Notebook } from '../notebook'; 3 | import { removeFileProtocol } from '../utility'; 4 | 5 | /** 6 | * Load local svg files and embed them into html directly. 7 | * @param $ 8 | */ 9 | export default async function enhance( 10 | $: CheerioStatic, 11 | notebook: Notebook, 12 | resolveFilePath: (path: string, useRelativeFilePath: boolean) => string, 13 | ): Promise { 14 | const asyncFunctions: Promise[] = []; 15 | $('img').each((i, img) => { 16 | const $img = $(img); 17 | let src = resolveFilePath($img.attr('src'), false); 18 | 19 | const fileProtocolMatch = src.match(/^(file|vscode-resource):\/\/+/); 20 | if (fileProtocolMatch) { 21 | src = removeFileProtocol(src); 22 | src = src.replace(/\?(\.|\d)+$/, ''); // remove cache 23 | const imageType = extname(src).slice(1); 24 | if (imageType !== 'svg') { 25 | return; 26 | } 27 | asyncFunctions.push( 28 | new Promise((resolve) => { 29 | notebook.fs 30 | .readFile(decodeURI(src), 'base64') 31 | .then((base64) => { 32 | $img.attr( 33 | 'src', 34 | `data:image/svg+xml;charset=utf-8;base64,${base64}`, 35 | ); 36 | return resolve(base64); 37 | }) 38 | .catch(() => { 39 | return resolve(null); 40 | }); 41 | }), 42 | ); 43 | } 44 | }); 45 | 46 | await Promise.all(asyncFunctions); 47 | } 48 | -------------------------------------------------------------------------------- /src/render-enhancers/extended-table-syntax.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend table syntax to support colspan and rowspan for merging cells 3 | * @param $ 4 | */ 5 | export default async function enhance($: CheerioStatic): Promise { 6 | const rowspans: [Cheerio, Cheerio][] = []; // ^ 7 | const colspans: [Cheerio, Cheerio][] = []; // > 8 | const colspans2: [Cheerio, Cheerio][] = []; // empty 9 | $('table').each((i, table) => { 10 | const $table = $(table); 11 | let $prevRow: Cheerio | null = null; 12 | $table.children().each((a, headBody) => { 13 | const $headBody = $(headBody); 14 | $headBody.children().each((i2, row) => { 15 | const $row = $(row); 16 | $row.children().each((j, col) => { 17 | const $col = $(col); 18 | const text = $col.text(); 19 | const html = $col.html(); 20 | if (!text.length && !html?.length) { 21 | // merge to left 22 | const $prev = $col.prev(); 23 | if ($prev.length) { 24 | colspans2.push([$prev, $col]); 25 | // const colspan = parseInt($prev.attr('colspan')) || 1 26 | // $prev.attr('colspan', colspan+1) 27 | // $col.remove() 28 | } 29 | } else if (text.trim() === '^' && $prevRow) { 30 | // merge to top 31 | const $prev = $($prevRow.children()[j]); 32 | if ($prev.length) { 33 | rowspans.push([$prev, $col]); 34 | // const rowspan = parseInt($prev.attr('rowspan')) || 1 35 | // $prev.attr('rowspan', rowspan+1) 36 | // $col.remove() 37 | } 38 | } else if (text.trim() === '>') { 39 | // merge to right 40 | const $next = $col.next(); 41 | if ($next.length) { 42 | // const colspan = parseInt($next.attr('colspan')) || 1 43 | // $next.attr('colspan', colspan+1) 44 | // $col.remove() 45 | colspans.push([$col, $next]); 46 | } 47 | } 48 | }); 49 | $prevRow = $row; 50 | }); 51 | }); 52 | }); 53 | 54 | for (let i = rowspans.length - 1; i >= 0; i--) { 55 | const [$prev, $col] = rowspans[i]; 56 | const rowspan = 57 | (parseInt($prev.attr('rowspan'), 10) || 1) + 58 | (parseInt($col.attr('rowspan'), 10) || 1); 59 | $prev.attr('rowspan', rowspan.toString()); 60 | $col.remove(); 61 | } 62 | // tslint:disable-next-line prefer-for-of 63 | for (let i = 0; i < colspans.length; i++) { 64 | const [$prev, $col] = colspans[i]; 65 | const colspan = 66 | (parseInt($prev.attr('colspan'), 10) || 1) + 67 | (parseInt($col.attr('colspan'), 10) || 1); 68 | $col.attr('colspan', colspan.toString()); 69 | $prev.remove(); 70 | } 71 | for (let i = colspans2.length - 1; i >= 0; i--) { 72 | const [$prev, $col] = colspans2[i]; 73 | const colspan = 74 | (parseInt($prev.attr('colspan'), 10) || 1) + 75 | (parseInt($col.attr('colspan'), 10) || 1); 76 | $prev.attr('colspan', colspan.toString()); 77 | $col.remove(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/render-enhancers/fenced-math.ts: -------------------------------------------------------------------------------- 1 | import { escape } from 'html-escaper'; 2 | import { KatexOptions } from 'katex'; 3 | import { stringifyBlockAttributes } from '../lib/block-attributes'; 4 | import { BlockInfo } from '../lib/block-info'; 5 | import { MathRenderingOption } from '../notebook'; 6 | import parseMath from '../renderers/parse-math'; 7 | 8 | const supportedLanguages = ['math']; 9 | 10 | /** 11 | * Enhances the document with literate fenced math 12 | * Attributes supported: 13 | * - literate [=true] if false, no math rendering happens 14 | * - hide [=true] if set to false, both code and output are shown 15 | * - output_first [=false] if true, math output shows before the code block (requires hide=false) 16 | * 17 | * @param renderingOption which math engine to use 18 | * @param $ cheerio element containing the entire document 19 | */ 20 | export default async function enhance( 21 | $, 22 | renderingOption: MathRenderingOption, 23 | mathBlockDelimiters: string[][], 24 | katexConfig: KatexOptions, 25 | ): Promise { 26 | $('[data-role="codeBlock"]').each((i, container) => { 27 | const $container = $(container); 28 | if ($container.data('executor')) { 29 | return; 30 | } 31 | 32 | const normalizedInfo: BlockInfo = $container.data('normalizedInfo'); 33 | if ( 34 | normalizedInfo.attributes['literate'] === false || 35 | normalizedInfo.attributes['cmd'] === false || 36 | supportedLanguages.indexOf(normalizedInfo.language) === -1 37 | ) { 38 | return; 39 | } 40 | 41 | $container.data('executor', 'math'); 42 | 43 | if (normalizedInfo.attributes['literate'] === false) { 44 | return; 45 | } 46 | 47 | const code = $container.text(); 48 | const $renderedMath = renderMath( 49 | code, 50 | normalizedInfo, 51 | renderingOption, 52 | mathBlockDelimiters, 53 | katexConfig, 54 | ); 55 | normalizedInfo.attributes['output_first'] === true 56 | ? $container.before($renderedMath) 57 | : $container.after($renderedMath); 58 | 59 | if (normalizedInfo.attributes['hide'] !== false) { 60 | $container.data('hiddenByEnhancer', true); 61 | } 62 | }); 63 | return $; 64 | } 65 | 66 | const renderMath = ( 67 | code: string, 68 | normalizedInfo: BlockInfo, 69 | renderingOption: MathRenderingOption, 70 | mathBlockDelimiters: string[][], 71 | katexConfig: KatexOptions, 72 | ): string => { 73 | let $output: string | null = null; 74 | try { 75 | const mathHtml = parseMath({ 76 | content: code, 77 | displayMode: true, 78 | openTag: mathBlockDelimiters.length ? mathBlockDelimiters[0][0] : '', 79 | closeTag: mathBlockDelimiters.length ? mathBlockDelimiters[0][1] : '', 80 | renderingOption, 81 | katexConfig, 82 | }); 83 | $output = `

${mathHtml}

`; 86 | } catch (error) { 87 | $output = `
${escape(
88 |       error.toString(),
89 |     )}
`; 90 | } 91 | return $output; 92 | }; 93 | -------------------------------------------------------------------------------- /src/render-enhancers/resolved-image-paths.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownEngineRenderOption } from '../markdown-engine'; 2 | 3 | /** 4 | * This function resolves image paths 5 | * @param $ cheerio object that we will analyze 6 | * @return cheerio object 7 | */ 8 | export default async function enhance( 9 | $, 10 | options: MarkdownEngineRenderOption, 11 | resolveFilePath: ( 12 | path: string, 13 | useRelativeFilePath: boolean, 14 | fileDirectoryPath?: string, 15 | ) => string, 16 | ): Promise { 17 | // resolve image paths 18 | $('img, a').each((i, imgElement) => { 19 | let srcTag = 'src'; 20 | if (imgElement.name === 'a') { 21 | srcTag = 'href'; 22 | } 23 | 24 | const img = $(imgElement); 25 | const src = img.attr(srcTag); 26 | 27 | img.attr( 28 | srcTag, 29 | resolveFilePath( 30 | src, 31 | options.useRelativeFilePath, 32 | options.fileDirectoryPath, 33 | ), 34 | ); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/renderers/bitfield.ts: -------------------------------------------------------------------------------- 1 | import { escape } from 'html-escaper'; 2 | import { JsonObject } from 'type-fest'; 3 | import { interpretJS } from '../utility'; 4 | 5 | export async function renderBitfield(code: string, options: JsonObject) { 6 | try { 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const render = require('bit-field/lib/render'); 9 | // eslint-disable-next-line @typescript-eslint/no-var-requires 10 | const onml = require('onml'); 11 | 12 | const reg = interpretJS(code); 13 | const jsonml = render(reg, options); 14 | const html = onml.stringify(jsonml); 15 | return html; 16 | } catch (error) { 17 | return `
${escape(
18 |       error.toString(),
19 |     )}
`; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/renderers/parse-math.ts: -------------------------------------------------------------------------------- 1 | import { escape } from 'html-escaper'; 2 | // https://github.com/KaTeX/KaTeX/blob/main/contrib/mhchem/README.md 3 | import katex from 'katex'; 4 | import 'katex/contrib/mhchem'; 5 | import { MathRenderingOption } from '../notebook'; 6 | 7 | // tslint:disable-next-line interface-over-type-literal 8 | export type ParseMathArgs = { 9 | content: string; 10 | openTag: string; 11 | closeTag: string; 12 | displayMode?: boolean; 13 | renderingOption: MathRenderingOption; 14 | katexConfig: katex.KatexOptions; 15 | }; 16 | 17 | /** 18 | * 19 | * @param content the math expression 20 | * @param openTag the open tag, eg: '\(' 21 | * @param closeTag the close tag, eg: '\)' 22 | * @param displayMode whether to be rendered in display mode 23 | * @param renderingOption the math engine to use: KaTeX | MathJax | None 24 | */ 25 | export default ({ 26 | content, 27 | openTag, 28 | closeTag, 29 | displayMode = false, 30 | renderingOption, 31 | katexConfig, 32 | }: ParseMathArgs) => { 33 | if (!content) { 34 | return ''; 35 | } 36 | if (renderingOption === 'KaTeX') { 37 | try { 38 | return katex.renderToString( 39 | content, 40 | Object.assign( 41 | {}, 42 | // NOTE: strucutredClone is necessary here: https://github.com/shd101wyy/vscode-markdown-preview-enhanced/issues/1853 43 | // it seems like KaTeX will modify the config object, 44 | // which will cause `JSON.stringify` in `generateHTMLTemplateForPreview` function in `markdown-engine/index.ts` to fail 45 | structuredClone(katexConfig), 46 | { displayMode }, 47 | ), 48 | ); 49 | } catch (error) { 50 | return `${error.toString()}`; 51 | } 52 | } else if (renderingOption === 'MathJax') { 53 | const text = (openTag + content + closeTag).replace(/\n/g, ' '); 54 | const tag = displayMode ? 'div' : 'span'; 55 | return `<${tag} class="mathjax-exps">${escape(text)}`; 56 | } else { 57 | return ''; 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /src/renderers/puml-server.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | import plantumlEncoder from 'plantuml-encoder'; 3 | 4 | export default class PlantUMLServerTask { 5 | private serverURL: string; 6 | 7 | constructor(serverURL: string) { 8 | this.serverURL = serverURL; 9 | } 10 | 11 | public generateSVG(content: string): Promise { 12 | if (this.serverURL.match(/^https?:\/\/(www\.)?plantuml\.com\/plantuml/)) { 13 | // NOTE: The official plantuml server doesn't support POST method, 14 | // so we fallback to encode the content and send it as a GET request. 15 | const encoded = plantumlEncoder.encode(content); 16 | return fetch(`http://www.plantuml.com/plantuml/svg/${encoded}`).then( 17 | (res) => res.text(), 18 | ); 19 | } else { 20 | // const contentStream = new Readable(); 21 | // contentStream.setEncoding('utf-8'); 22 | // contentStream.push(content); 23 | // contentStream.push(null); // Mark end of stream 24 | return fetch(this.serverURL, { 25 | method: 'POST', 26 | body: content, 27 | headers: { 'Content-Type': 'text/plain; charset=utf-8' }, 28 | }).then((res) => res.text()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/renderers/vega-lite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert vega-lite to vega first, then render to svg. 3 | */ 4 | import * as YAML from 'yaml'; 5 | import * as vl from '../../dependencies/vega-lite/vega-lite.min.js'; 6 | import * as utility from '../utility'; 7 | import * as vega from './vega'; 8 | 9 | export async function toSVG(spec: string = '', baseURL: string = '') { 10 | spec = spec.trim(); 11 | let d; 12 | if (spec[0] !== '{') { 13 | d = YAML.parse(spec); 14 | } else { 15 | // json 16 | d = JSON.parse(spec); 17 | } 18 | 19 | return utility.allowUnsafeEval(() => { 20 | return utility.allowUnsafeNewFunction(() => { 21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 22 | return vega.toSVG(JSON.stringify((vl as any).compile(d).spec), baseURL); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/renderers/vega.ts: -------------------------------------------------------------------------------- 1 | import { loader } from 'vega-loader'; 2 | import * as YAML from 'yaml'; 3 | import * as utility from '../utility'; 4 | 5 | async function renderVega(spec: object, baseURL): Promise { 6 | const svgHeader = 7 | '\n' + 8 | '\n'; 10 | 11 | if (baseURL && baseURL[baseURL.length - 1] !== '/') { 12 | baseURL += '/'; 13 | } 14 | 15 | async function helper(): Promise { 16 | // eslint-disable-next-line @typescript-eslint/no-var-requires 17 | const vega = require('../../dependencies/vega/vega.min.js'); 18 | 19 | const view = new vega.View(vega.parse(spec), { 20 | loader: loader({ baseURL }), 21 | // logLevel: vega.Warn, // <= this will cause Atom unsafe eval error. 22 | renderer: 'none', 23 | }).initialize(); 24 | return svgHeader + (await view.toSVG()); 25 | } 26 | 27 | return await utility.allowUnsafeEvalAndUnsafeNewFunctionAsync(helper); 28 | } 29 | 30 | /** 31 | * Modifed from the `vg2svg` file. 32 | * @param spec The vega code. 33 | */ 34 | export async function toSVG( 35 | spec: string = '', 36 | baseURL: string = '', 37 | ): Promise { 38 | spec = spec.trim(); 39 | let d; 40 | if (spec[0] !== '{') { 41 | // yaml 42 | d = YAML.parse(spec); 43 | } else { 44 | // json 45 | d = JSON.parse(spec); 46 | } 47 | return renderVega(d, baseURL); 48 | } 49 | -------------------------------------------------------------------------------- /src/renderers/viz.ts: -------------------------------------------------------------------------------- 1 | import { instance, RenderOptions } from '@viz-js/viz'; 2 | 3 | type ValueType = T extends Promise ? U : T; 4 | 5 | let viz: ValueType> | null = null; 6 | 7 | /* 8 | * @param renderOption https://github.com/mdaines/viz.js/wiki/API#render-options 9 | */ 10 | export async function Viz(digraph: string, renderOption: RenderOptions) { 11 | try { 12 | if (!viz) { 13 | viz = await instance(); 14 | } 15 | return await viz.renderString(digraph, { format: 'svg', ...renderOption }); 16 | } catch (error) { 17 | // Create a new Viz instance (@see Caveats page for more info) 18 | viz = null; 19 | throw error; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/tools/latex.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import * as PDF from './pdf'; 5 | 6 | function cleanUpFiles(texFilePath: string) { 7 | const directoryPath = path.dirname(texFilePath); 8 | const extensionName = path.extname(texFilePath); 9 | const filePrefix = path 10 | .basename(texFilePath) 11 | .replace(new RegExp(extensionName + '$'), ''); 12 | 13 | fs.readdir(directoryPath, (error, items) => { 14 | if (error) { 15 | return; 16 | } 17 | 18 | items.forEach((fileName) => { 19 | if (fileName.startsWith(filePrefix) && !fileName.match(/\.(la)?tex/)) { 20 | fs.unlink(path.resolve(directoryPath, fileName), () => { 21 | return; 22 | }); 23 | } 24 | }); 25 | }); 26 | } 27 | 28 | export function toSVGMarkdown( 29 | texFilePath: string, 30 | { 31 | latexEngine = 'pdflatex', 32 | svgDirectoryPath, 33 | markdownDirectoryPath, 34 | svgZoom, 35 | svgWidth, 36 | svgHeight, 37 | }: { 38 | latexEngine: string; 39 | svgDirectoryPath?: string; 40 | markdownDirectoryPath: string; 41 | svgZoom?: string; 42 | svgWidth?: string; 43 | svgHeight?: string; 44 | }, 45 | ): Promise { 46 | return new Promise((resolve, reject) => { 47 | const task = spawn(latexEngine, [`"${texFilePath}"`], { 48 | cwd: path.dirname(texFilePath), 49 | shell: true, 50 | }); 51 | 52 | const chunks: Buffer[] = []; 53 | task.stdout.on('data', (chunk) => { 54 | chunks.push(chunk); 55 | }); 56 | 57 | const errorChunks: Buffer[] = []; 58 | task.stderr.on('data', (chunk) => { 59 | errorChunks.push(chunk); 60 | }); 61 | 62 | task.on('error', (error) => { 63 | errorChunks.push(Buffer.from(error.toString(), 'utf-8')); 64 | }); 65 | 66 | task.on('close', () => { 67 | if (errorChunks.length) { 68 | cleanUpFiles(texFilePath); 69 | return reject(Buffer.concat(errorChunks).toString()); 70 | } else { 71 | const output = Buffer.concat(chunks).toString(); 72 | if (output.indexOf('LaTeX Error') >= 0) { 73 | // meet error 74 | cleanUpFiles(texFilePath); 75 | return reject(output); 76 | } 77 | 78 | const pdfFilePath = texFilePath.replace(/\.(la)?tex$/, '.pdf'); 79 | 80 | PDF.toSVGMarkdown(pdfFilePath, { 81 | svgDirectoryPath, 82 | markdownDirectoryPath, 83 | svgZoom, 84 | svgWidth, 85 | svgHeight, 86 | }) 87 | .then((svgMarkdown) => { 88 | cleanUpFiles(texFilePath); 89 | return resolve(svgMarkdown); 90 | }) 91 | .catch((error) => { 92 | cleanUpFiles(texFilePath); 93 | return reject(error); 94 | }); 95 | } 96 | }); 97 | 98 | task.stdin.end(); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /src/tools/magick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ImageMagick magick command wrapper 3 | */ 4 | import { execFileSync } from 'child_process'; 5 | import * as fs from 'fs'; 6 | import imagemagickCli from 'imagemagick-cli'; 7 | import { tempOpen } from '../utility'; 8 | 9 | export async function svgElementToPNGFile( 10 | svgElement: string, 11 | pngFilePath: string, 12 | imageMagickPath: string = '', 13 | ): Promise { 14 | const info = await tempOpen({ prefix: 'crossnote-svg', suffix: '.svg' }); 15 | fs.writeFileSync(info.fd, svgElement); // write svgElement to temp .svg file 16 | const args = [info.path, pngFilePath]; 17 | try { 18 | if (imageMagickPath && imageMagickPath.length) { 19 | await execFileSync(imageMagickPath, args); 20 | } else { 21 | await imagemagickCli.exec(`convert ${args.join(' ')}`); 22 | } 23 | } catch (error) { 24 | throw new Error( 25 | 'imagemagick-cli failure\n' + 26 | error.toString() + 27 | '\n\nPlease make sure you have ImageMagick installed.', 28 | ); 29 | } 30 | 31 | return pngFilePath; 32 | } 33 | -------------------------------------------------------------------------------- /src/tools/mermaid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A wrapper of mermaid CLI 3 | * https://github.com/mermaid-js/mermaid-cli 4 | */ 5 | 6 | import { execFileSync } from 'child_process'; 7 | import * as fs from 'fs'; 8 | import { tempOpen } from '../utility'; 9 | 10 | export async function mermaidToPNG( 11 | mermaidCode: string, 12 | pngFilePath: string, 13 | projectDirectoryPath: string, 14 | themeName, 15 | ): Promise { 16 | const info = await tempOpen({ 17 | prefix: 'crossnote-mermaid', 18 | suffix: '.mmd', 19 | }); 20 | fs.writeFileSync(info.fd, mermaidCode); 21 | if (!themeName) { 22 | themeName = 'null'; 23 | } 24 | try { 25 | execFileSync( 26 | 'npx', 27 | [ 28 | '-p', 29 | '@mermaid-js/mermaid-cli', 30 | 'mmdc', 31 | '--theme', 32 | themeName, 33 | '--input', 34 | info.path, 35 | '--output', 36 | pngFilePath, 37 | ], 38 | { 39 | shell: true, 40 | cwd: projectDirectoryPath, 41 | }, 42 | ); 43 | return pngFilePath; 44 | } catch (error) { 45 | throw new Error( 46 | 'mermaid CLI is required to be installed.\nCheck https://github.com/mermaid-js/mermaid-cli for more information.\n\n' + 47 | error.toString(), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tools/pdf.ts: -------------------------------------------------------------------------------- 1 | // `pdf2svg` is required to be installed 2 | // http://www.cityinthesky.co.uk/opensource/pdf2svg/ 3 | // 4 | 5 | import { spawn } from 'child_process'; 6 | import * as fs from 'fs'; 7 | import * as path from 'path'; 8 | import * as temp from 'temp'; 9 | import computeChecksum from '../lib/compute-checksum'; 10 | 11 | let SVG_DIRECTORY_PATH: string | undefined; 12 | 13 | export function toSVGMarkdown( 14 | pdfFilePath: string, 15 | { 16 | svgDirectoryPath, 17 | markdownDirectoryPath, 18 | svgZoom, 19 | svgWidth, 20 | svgHeight, 21 | }: { 22 | markdownDirectoryPath: string; 23 | svgDirectoryPath?: string; 24 | svgZoom?: string; 25 | svgWidth?: string; 26 | svgHeight?: string; 27 | }, 28 | ): Promise { 29 | return new Promise((resolve, reject) => { 30 | if (!svgDirectoryPath) { 31 | if (!SVG_DIRECTORY_PATH) { 32 | SVG_DIRECTORY_PATH = temp.mkdirSync('crossnote_pdf'); 33 | } 34 | svgDirectoryPath = SVG_DIRECTORY_PATH; 35 | } 36 | 37 | const svgFilePrefix = computeChecksum(pdfFilePath) + '_'; 38 | 39 | const task = spawn( 40 | 'pdf2svg', 41 | [ 42 | `"${pdfFilePath}"`, 43 | `"${path.resolve( 44 | svgDirectoryPath ?? `/tmp/crossnote_pdf`, 45 | svgFilePrefix + '%d.svg', 46 | )}"`, 47 | 'all', 48 | ], 49 | { shell: true }, 50 | ); 51 | const chunks: string[] = []; 52 | task.stdout.on('data', (chunk) => { 53 | chunks.push(chunk); 54 | }); 55 | 56 | const errorChunks: Buffer[] = []; 57 | task.stderr.on('data', (chunk) => { 58 | errorChunks.push(chunk); 59 | }); 60 | 61 | task.on('error', (error) => { 62 | errorChunks.push(Buffer.from(error.toString(), 'utf-8')); 63 | }); 64 | 65 | task.on('close', () => { 66 | if (errorChunks.length) { 67 | return reject(Buffer.concat(errorChunks).toString()); 68 | } else { 69 | fs.readdir(svgDirectoryPath ?? '', (error, items) => { 70 | if (error) { 71 | return reject(error.toString()); 72 | } 73 | 74 | items = items.sort((a, b) => { 75 | const offsetA = parseInt((a.match(/_(\d+)\.svg$/) ?? [])[1], 10); 76 | const offsetB = parseInt((b.match(/_(\d+)\.svg$/) ?? [])[1], 10); 77 | return offsetA - offsetB; 78 | }); 79 | 80 | let svgMarkdown = ''; 81 | const r = Math.random(); 82 | 83 | items.forEach((fileName) => { 84 | const match = fileName.match( 85 | new RegExp(`^${svgFilePrefix}(\\d+).svg`), 86 | ); 87 | if (match) { 88 | let svgFilePath = path.relative( 89 | markdownDirectoryPath, 90 | path.resolve(svgDirectoryPath ?? '', fileName), 91 | ); 92 | 93 | // nvm, the converted result looks so ugly 94 | /* 95 | const pngFilePath = svgFilePath.replace(/\.svg$/, '.png') 96 | 97 | // convert svg to png 98 | gm(svgFilePath) 99 | .noProfile() 100 | .write(pngFilePath, function(error) { 101 | console.log(error, pngFilePath) 102 | }) 103 | */ 104 | svgFilePath = svgFilePath 105 | .replace(/\.\.\\/g, '../') 106 | .replace( 107 | /\\/g, 108 | '/', 109 | ); /* Windows file path issue. "..\..\blabla" doesn't work */ 110 | 111 | if (svgZoom || svgWidth || svgHeight) { 112 | svgMarkdown += ``; 117 | } else { 118 | svgMarkdown += `![](${svgFilePath}?${r})\n`; 119 | } 120 | } 121 | }); 122 | return resolve(svgMarkdown); 123 | }); 124 | } 125 | }); 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /src/tools/sharp.ts: -------------------------------------------------------------------------------- 1 | export async function svgElementToPNGFile( 2 | svgElement: string, 3 | pngFilePath: string, 4 | ): Promise { 5 | try { 6 | const sharp = (await import('sharp')).default; 7 | await sharp( 8 | Buffer.from( 9 | svgElement.replace( 10 | /font-family="[\w|\-|,|\s]+"/g, 11 | 'font-family="Arial, sans-serif"', 12 | ), 13 | ), 14 | ) 15 | .png() 16 | .toFile(pngFilePath); 17 | } catch (error) { 18 | throw new Error( 19 | 'sharp conversion failure\n' + 20 | error.toString() + 21 | '\n\nPlease make sure you have libvips installed.', 22 | ); 23 | } 24 | 25 | return pngFilePath; 26 | } 27 | -------------------------------------------------------------------------------- /src/tools/wavedrom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A wrapper of wavedrom CLI 3 | * https://github.com/wavedrom/cli 4 | */ 5 | 6 | import { execFileSync } from 'child_process'; 7 | import * as fs from 'fs'; 8 | import { tempOpen } from '../utility'; 9 | 10 | export async function render( 11 | wavedromCode: string, 12 | projectDirectoryPath: string, 13 | ): Promise { 14 | const info = await tempOpen({ 15 | prefix: 'crossnote-wavedrom', 16 | suffix: '.js', 17 | }); 18 | await fs.writeFileSync(info.fd, wavedromCode); 19 | try { 20 | const svg = ( 21 | await execFileSync('npx', ['wavedrom-cli', '-i', info.path], { 22 | shell: true, 23 | cwd: projectDirectoryPath, 24 | }) 25 | ).toString('utf-8'); 26 | return svg; 27 | } catch (error) { 28 | throw new Error( 29 | 'wavedrom CLI is required to be installed.\nCheck http://github.com/wavedrom/cli for more information.\n\n' + 30 | error.toString(), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/webview/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:react/recommended', 10 | 'plugin:react-hooks/recommended', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | plugins: ['react', 'react-hooks'], 20 | rules: { 21 | //add customize rules here as per your project's needs 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/webview/backlinks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './index.css'; 4 | 5 | // Clear the existing HTML content 6 | document.body.innerHTML = '
'; 7 | 8 | // Render your React component instead 9 | const root = createRoot(document.body); 10 | root.render(
Hello, world
); 11 | -------------------------------------------------------------------------------- /src/webview/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Bars3Icon, LinkIcon } from '@heroicons/react/24/outline'; 2 | import classNames from 'classnames'; 3 | import React, { useEffect, useState } from 'react'; 4 | import readingTime from 'reading-time/lib/reading-time'; 5 | import PreviewContainer from '../containers/preview'; 6 | import { getElementBackgroundColor } from '../lib/utility'; 7 | 8 | export default function Footer() { 9 | const { 10 | showContextMenu, 11 | setShowBacklinks, 12 | isPresentationMode, 13 | isMouseOverPreview, 14 | isMobile, 15 | showBacklinks, 16 | theme, 17 | markdown, 18 | enablePreviewZenMode, 19 | } = PreviewContainer.useContainer(); 20 | const [readingTimeEstimation, setReadingTimeEstimation] = useState< 21 | | { 22 | minutes: number; 23 | words: number; 24 | text: string; 25 | } 26 | | undefined 27 | >(undefined); 28 | 29 | useEffect(() => { 30 | const readingTimeEstimation = readingTime(markdown); 31 | setReadingTimeEstimation(readingTimeEstimation); 32 | }, [markdown]); 33 | 34 | return ( 35 |
42 |
51 | {!enablePreviewZenMode && readingTimeEstimation && ( 52 |
53 | {readingTimeEstimation.text} 54 |
55 | )} 56 |
62 |
{ 69 | setShowBacklinks((x) => !x); 70 | }} 71 | > 72 | 73 |
74 |
{ 78 | showContextMenu({ 79 | event, 80 | }); 81 | }} 82 | > 83 | 84 |
85 |
86 |
87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /src/webview/components/LoadingIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import PreviewContainer from '../containers/preview'; 3 | 4 | export default function LoadingIcon() { 5 | const { theme } = PreviewContainer.useContainer(); 6 | const [countdown, setCountdown] = useState(5); 7 | 8 | useEffect(() => { 9 | if (countdown > 0) { 10 | setTimeout(() => { 11 | setCountdown(countdown - 1); 12 | }, 1000); 13 | } 14 | }, [countdown]); 15 | 16 | return ( 17 |
24 |
25 |
26 | 27 | {countdown === 0 && ( 28 | 29 | Something is wrong. 30 |
Please close and open the preview again. 31 |
32 | )} 33 |
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/webview/components/Preview.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React from 'react'; 3 | import PreviewContainer from '../containers/preview'; 4 | import Backlinks from './Backlinks'; 5 | import ContextMenu from './ContextMenu'; 6 | import FloatingActions from './FloatingActions'; 7 | import Footer from './Footer'; 8 | import ImageHelper from './ImageHelper'; 9 | import LoadingIcon from './LoadingIcon'; 10 | import MarkdownEditor from './MarkdownEditor'; 11 | import RefreshingIcon from './RefreshingIcon'; 12 | import SidebarToc from './SidebarToc'; 13 | import { Topbar } from './Topbar'; 14 | 15 | export default function Preview() { 16 | const { 17 | enablePreviewZenMode, 18 | hiddenPreviewElement, 19 | isPresentationMode, 20 | isLoadingPreview, 21 | isRefreshingPreview, 22 | previewElement, 23 | setIsMouseOverPreview, 24 | showContextMenu, 25 | showBacklinks, 26 | highlightElementBeingEdited, 27 | } = PreviewContainer.useContainer(); 28 | 29 | return ( 30 |
{ 32 | setIsMouseOverPreview(true); 33 | }} 34 | onMouseOut={() => { 35 | setIsMouseOverPreview(false); 36 | }} 37 | className={classNames( 38 | 'w-full min-h-screen', 39 | isPresentationMode ? 'h-full' : 'h-auto', 40 | )} 41 | onContextMenu={(event) => { 42 | showContextMenu({ 43 | event, 44 | }); 45 | }} 46 | > 47 | {/** Background */} 48 |
49 | {/** Top bar */} 50 | 51 | {/** The hidden preview */} 52 |
58 | {/** The real preview 59 | * NOTE: the className only accepts `crossnote markdown-preview` 60 | */} 61 |
69 | {/** Backlinks */} 70 | {showBacklinks && !isPresentationMode && } 71 | {/** Footer */} 72 |
73 | {/** Sidebar TOC */} 74 | 75 | {/** Loading Preview */} 76 | {isLoadingPreview && } 77 | {/** Refreshing Preview */} 78 | {isRefreshingPreview && } 79 | {/** Image helper */} 80 | 81 | {/** Context menu */} 82 | 83 | {/** Floating Actions */} 84 | {!enablePreviewZenMode && } 85 | {/** Markdown Editor */} 86 | {!enablePreviewZenMode && highlightElementBeingEdited && ( 87 | 88 | )} 89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/webview/components/RefreshingIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PreviewContainer from '../containers/preview'; 3 | 4 | export default function RefreshingIcon() { 5 | const { theme } = PreviewContainer.useContainer(); 6 | 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/webview/components/SidebarToc.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React from 'react'; 3 | import PreviewContainer from '../containers/preview'; 4 | import { getElementBackgroundColor } from '../lib/utility'; 5 | 6 | export default function SidebarToc() { 7 | const { sidebarTocElement, showSidebarToc } = PreviewContainer.useContainer(); 8 | return ( 9 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/webview/components/Topbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ArrowPathIcon, 3 | ChevronUpIcon, 4 | ListBulletIcon, 5 | } from '@heroicons/react/24/outline'; 6 | import classNames from 'classnames'; 7 | import React, { useCallback } from 'react'; 8 | import PreviewContainer from '../containers/preview'; 9 | 10 | export function Topbar() { 11 | const { 12 | clickSidebarTocButton, 13 | isMobile, 14 | isMouseOverPreview, 15 | isPresentationMode, 16 | postMessage, 17 | sourceUri, 18 | showSidebarToc, 19 | theme, 20 | } = PreviewContainer.useContainer(); 21 | 22 | const backToTop = useCallback(() => { 23 | if (isPresentationMode) { 24 | return window['Reveal'].slide(0); 25 | } else { 26 | document.documentElement.scrollTop = 0; 27 | } 28 | }, [isPresentationMode]); 29 | 30 | const refreshPreview = useCallback(() => { 31 | postMessage('refreshPreview', [sourceUri.current]); 32 | }, [postMessage, sourceUri]); 33 | 34 | return ( 35 |
43 |
44 |
49 | 50 |
51 |
56 | 57 |
58 |
66 | 67 |
68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/webview/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .btn { 6 | border: none; 7 | } 8 | -------------------------------------------------------------------------------- /src/webview/lib/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | export type WebviewMessageEvent = { 4 | command: 'updateHtml'; 5 | totalLineCount: number; 6 | sidebarTOCHTML: string; 7 | sourceUri: string; 8 | sourceScheme: string; 9 | html: string; 10 | id: string; 11 | class: string; 12 | }; 13 | 14 | export type WebviewMessageType = WebviewMessageEvent['command']; 15 | 16 | export enum BacklinksOrderRecord { 17 | CreatedAt = 'createdAt', 18 | ModifiedAt = 'modifiedAt', 19 | } 20 | 21 | export enum BacklinksOrderDirection { 22 | Asc = 'asc', 23 | Desc = 'desc', 24 | } 25 | -------------------------------------------------------------------------------- /src/webview/lib/utility.ts: -------------------------------------------------------------------------------- 1 | export function getElementBackgroundColor(element: HTMLElement) { 2 | const computedStyle = window.getComputedStyle(element); 3 | const bgColor = computedStyle.backgroundColor; 4 | return bgColor; 5 | } 6 | 7 | export function isBackgroundColorLight(element) { 8 | // Get the computed background color 9 | const bgColor = getElementBackgroundColor(element); 10 | 11 | // Function to calculate luminance from a color string 12 | function calculateLuminance(color) { 13 | // Remove any spaces and convert to lowercase 14 | color = color.replace(/\s+/g, '').toLowerCase(); 15 | 16 | // If the color starts with "rgb", extract the RGB values 17 | if (color.startsWith('rgb')) { 18 | const rgb = color.match(/\d+/g).map(Number); 19 | const r = rgb[0]; 20 | const g = rgb[1]; 21 | const b = rgb[2]; 22 | return 0.299 * r + 0.587 * g + 0.114 * b; 23 | } 24 | 25 | // If the color starts with "#" (hexadecimal), parse it 26 | if (color.startsWith('#')) { 27 | const hex = color.slice(1); 28 | const bigint = parseInt(hex, 16); 29 | const r = (bigint >> 16) & 255; 30 | const g = (bigint >> 8) & 255; 31 | const b = bigint & 255; 32 | return 0.299 * r + 0.587 * g + 0.114 * b; 33 | } 34 | 35 | // If the color is not recognized, return a default value 36 | return 0; 37 | } 38 | 39 | // Calculate the luminance 40 | const luminance = calculateLuminance(bgColor); 41 | 42 | // Compare the luminance to a threshold (e.g., 128) 43 | return luminance > 128; 44 | } 45 | 46 | export function copyTextToClipboard(text: string) { 47 | const input = document.createElement('textarea'); 48 | input.style.position = 'fixed'; 49 | input.style.opacity = '0'; 50 | input.value = text; 51 | document.body.appendChild(input); 52 | input.select(); 53 | document.execCommand('copy'); 54 | document.body.removeChild(input); 55 | } 56 | 57 | export function copyBlobToClipboard(blob: Blob) { 58 | navigator.clipboard 59 | .write([ 60 | new ClipboardItem({ 61 | [blob.type]: blob, 62 | }), 63 | ]) 64 | .catch((error) => { 65 | console.error(error); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /src/webview/preview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import Preview from './components/Preview'; 4 | import PreviewContainer from './containers/preview'; 5 | import './index.css'; 6 | 7 | // Clear the existing HTML content 8 | document.body.innerHTML = '
'; 9 | 10 | // Render your React component instead 11 | const root = createRoot(document.body); 12 | root.render( 13 | 14 | 15 | , 16 | ); 17 | -------------------------------------------------------------------------------- /styles/octocat-spinner-128.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/styles/octocat-spinner-128.gif -------------------------------------------------------------------------------- /styles/preview_theme/atom-dark.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#c5c8c6, #1d1f21, #ddd); 5 | 6 | body { 7 | a { 8 | text-decoration: underline; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /styles/preview_theme/atom-light.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#555, #fff, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/atom-material.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#EEFFFF, #263238, #82aaff); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/github-dark.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#ccc, #24292e, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/github-light.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#333, #fff, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/monokai.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#F8F8F2, #282828, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/none.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shd101wyy/crossnote/fd6ac5421298ca6e09a0520ac566fe4611a13ac0/styles/preview_theme/none.less -------------------------------------------------------------------------------- /styles/preview_theme/one-dark.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | // Config ----------------------------------- 4 | @syntax-hue: 220; 5 | @syntax-saturation: 13%; 6 | @syntax-brightness: 18%; 7 | 8 | // Monochrome ----------------------------------- 9 | @mono-1: hsl(@syntax-hue, 14%, 71%); // default text 10 | 11 | // Base colors ----------------------------------- 12 | @syntax-fg: @mono-1; 13 | @syntax-bg: hsl(@syntax-hue, @syntax-saturation, @syntax-brightness); 14 | 15 | html { 16 | .github(@syntax-fg, @syntax-bg, #56b6c2); 17 | } 18 | -------------------------------------------------------------------------------- /styles/preview_theme/one-light.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | // Config ----------------------------------- 4 | @syntax-hue: 230; 5 | @syntax-saturation: 1%; 6 | @syntax-brightness: 98%; 7 | 8 | // Monochrome ----------------------------------- 9 | @mono-1: hsl(@syntax-hue, 8%, 24%); 10 | 11 | // Base colors ----------------------------------- 12 | @syntax-fg: @mono-1; 13 | @syntax-bg: hsl(@syntax-hue, @syntax-saturation, @syntax-brightness); 14 | 15 | html { 16 | .github(@syntax-fg, @syntax-bg, #0184bc); 17 | } 18 | -------------------------------------------------------------------------------- /styles/preview_theme/solarized-dark.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#839496, #002b36, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/preview_theme/solarized-light.less: -------------------------------------------------------------------------------- 1 | @import 'github.less'; 2 | 3 | html { 4 | .github(#657b83, #fdf6e3, #08c); 5 | } 6 | -------------------------------------------------------------------------------- /styles/prism_theme/github-dark.less: -------------------------------------------------------------------------------- 1 | /** 2 | * @credit to: https://github.com/katorlys/prism-theme-github/tree/main/themes 3 | * But I slightly modified it to fit my needs. 4 | */ 5 | /* General */ 6 | pre[class*='language-'], 7 | code[class*='language-'] { 8 | color: #c9d1d9; 9 | text-shadow: none; 10 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 11 | direction: ltr; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | line-height: 1.5; 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | -webkit-hyphens: none; 21 | -moz-hyphens: none; 22 | -ms-hyphens: none; 23 | hyphens: none; 24 | } 25 | pre[class*='language-']::selection, 26 | code[class*='language-']::selection, 27 | pre[class*='language-']::mozselection, 28 | code[class*='language-']::mozselection { 29 | text-shadow: none; 30 | background: #234879; 31 | } 32 | @media print { 33 | pre[class*='language-'], 34 | code[class*='language-'] { 35 | text-shadow: none; 36 | } 37 | } 38 | pre[class*='language-'] { 39 | padding: 1em; 40 | margin: 0.5em 0; 41 | overflow: auto; 42 | background: #161b22; 43 | } 44 | :not(pre) > code[class*='language-'] { 45 | padding: 0.1em 0.3em; 46 | border-radius: 0.3em; 47 | color: #c9d1d9; 48 | background: #343942; 49 | } 50 | /* Line highlighting */ 51 | pre[data-line] { 52 | position: relative; 53 | } 54 | pre[class*='language-'] > code[class*='language-'] { 55 | position: relative; 56 | z-index: 1; 57 | } 58 | .line-highlight { 59 | position: absolute; 60 | left: 0; 61 | right: 0; 62 | padding: inherit 0; 63 | margin-top: 1em; 64 | background: #2f2a1e; 65 | box-shadow: inset 5px 0 0 #674c16; 66 | z-index: 0; 67 | pointer-events: none; 68 | line-height: inherit; 69 | white-space: pre; 70 | } 71 | /* Tokens */ 72 | .namespace { 73 | opacity: 0.7; 74 | } 75 | .token.comment, 76 | .token.prolog, 77 | .token.doctype, 78 | .token.cdata { 79 | color: #8b949e; 80 | } 81 | .token.punctuation { 82 | color: #c9d1d9; 83 | } 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #79c0ff; 92 | } 93 | .token.selector, 94 | .token.attr-name, 95 | .token.string, 96 | .token.char, 97 | .token.builtin, 98 | .token.inserted { 99 | color: #a5d6ff; 100 | } 101 | .token.operator, 102 | .token.entity, 103 | .token.url, 104 | .language-css .token.string, 105 | .style .token.string { 106 | color: #a5d6ff; 107 | } 108 | .token.atrule, 109 | .token.attr-value, 110 | .token.keyword { 111 | color: #f97583; 112 | } 113 | .token.function { 114 | color: #d2a8ff; 115 | } 116 | .token.regex, 117 | .token.important, 118 | .token.variable { 119 | color: #79b8ff; 120 | } 121 | .token.important, 122 | .token.bold { 123 | font-weight: bold; 124 | } 125 | .token.italic { 126 | font-style: italic; 127 | } 128 | .token.entity { 129 | cursor: help; 130 | } 131 | -------------------------------------------------------------------------------- /styles/prism_theme/hopscotch.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Fira+Mono); 2 | /* 3 | * Hopscotch 4 | * by Jan T. Sott 5 | * https://github.com/idleberg/Hopscotch 6 | * 7 | * This work is licensed under the Creative Commons CC0 1.0 Universal License 8 | */ 9 | 10 | code[class*="language-"], 11 | pre[class*="language-"] { 12 | color: #ffffff; 13 | font-family: "Fira Mono", Menlo, Monaco, "Lucida Console","Courier New", Courier, monospace; 14 | font-size: 16px; 15 | line-height: 1.375; 16 | direction: ltr; 17 | text-align: left; 18 | word-spacing: normal; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | white-space: pre; 29 | white-space: pre-wrap; 30 | word-break: break-all; 31 | word-wrap: break-word; 32 | background: #322931; 33 | color: #b9b5b8; 34 | } 35 | 36 | /* Code blocks */ 37 | pre[class*="language-"] { 38 | padding: 1em; 39 | margin: .5em 0; 40 | overflow: auto; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .1em; 46 | border-radius: .3em; 47 | } 48 | 49 | .token.comment, 50 | .token.prolog, 51 | .token.doctype, 52 | .token.cdata { 53 | color: #797379; 54 | } 55 | 56 | .token.punctuation { 57 | color: #b9b5b8; 58 | } 59 | 60 | .namespace { 61 | opacity: .7; 62 | } 63 | 64 | .token.null, 65 | .token.operator, 66 | .token.boolean, 67 | .token.number { 68 | color: #fd8b19; 69 | } 70 | .token.property { 71 | color: #fdcc59; 72 | } 73 | .token.tag { 74 | color: #1290bf; 75 | } 76 | .token.string { 77 | color: #149b93; 78 | } 79 | .token.selector { 80 | color: #c85e7c; 81 | } 82 | .token.attr-name { 83 | color: #fd8b19; 84 | } 85 | .token.entity, 86 | .token.url, 87 | .language-css .token.string, 88 | .style .token.string { 89 | color: #149b93; 90 | } 91 | 92 | .token.attr-value, 93 | .token.keyword, 94 | .token.control, 95 | .token.directive, 96 | .token.unit { 97 | color: #8fc13e; 98 | } 99 | 100 | .token.statement, 101 | .token.regex, 102 | .token.atrule { 103 | color: #149b93; 104 | } 105 | 106 | .token.placeholder, 107 | .token.variable { 108 | color: #1290bf; 109 | } 110 | 111 | .token.important { 112 | color: #dd464c; 113 | font-weight: bold; 114 | } 115 | 116 | .token.entity { 117 | cursor: help; 118 | } 119 | 120 | pre > code.highlight { 121 | outline: .4em solid red; 122 | outline-offset: .4em; 123 | } 124 | 125 | /* highlight */ 126 | pre[data-line] { 127 | position: relative; 128 | padding: 1em 0 1em 3em; 129 | } 130 | pre[data-line] .line-highlight-wrapper { 131 | position: absolute; 132 | top: 0; 133 | left: 0; 134 | background-color: transparent; 135 | display: block; 136 | width: 100%; 137 | } 138 | 139 | pre[data-line] .line-highlight { 140 | position: absolute; 141 | left: 0; 142 | right: 0; 143 | padding: inherit 0; 144 | margin-top: 1em; 145 | background: hsla(24, 20%, 50%,.08); 146 | background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 147 | pointer-events: none; 148 | line-height: inherit; 149 | white-space: pre; 150 | } 151 | 152 | pre[data-line] .line-highlight:before, 153 | pre[data-line] .line-highlight[data-end]:after { 154 | content: attr(data-start); 155 | position: absolute; 156 | top: .4em; 157 | left: .6em; 158 | min-width: 1em; 159 | padding: 0 .5em; 160 | background-color: hsla(24, 20%, 50%,.4); 161 | color: hsl(24, 20%, 95%); 162 | font: bold 65%/1.5 sans-serif; 163 | text-align: center; 164 | vertical-align: .3em; 165 | border-radius: 999px; 166 | text-shadow: none; 167 | box-shadow: 0 1px white; 168 | } 169 | 170 | pre[data-line] .line-highlight[data-end]:after { 171 | content: attr(data-end); 172 | top: auto; 173 | bottom: .4em; 174 | } -------------------------------------------------------------------------------- /styles/prism_theme/okaidia.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+abap+actionscript+ada+apacheconf+apl+applescript+asciidoc+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bro+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+django+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+jolie+json+julia+keyman+kotlin+latex+less+livescript+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+properties+protobuf+puppet+pure+python+q+qore+r+jsx+reason+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+vbnet+verilog+vhdl+vim+wiki+xojo+yaml */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | /* highlight */ 125 | pre[data-line] { 126 | position: relative; 127 | padding: 1em 0 1em 3em; 128 | } 129 | pre[data-line] .line-highlight-wrapper { 130 | position: absolute; 131 | top: 0; 132 | left: 0; 133 | background-color: transparent; 134 | display: block; 135 | width: 100%; 136 | } 137 | 138 | pre[data-line] .line-highlight { 139 | position: absolute; 140 | left: 0; 141 | right: 0; 142 | padding: inherit 0; 143 | margin-top: 1em; 144 | background: hsla(24, 20%, 50%,.08); 145 | background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 146 | pointer-events: none; 147 | line-height: inherit; 148 | white-space: pre; 149 | } 150 | 151 | pre[data-line] .line-highlight:before, 152 | pre[data-line] .line-highlight[data-end]:after { 153 | content: attr(data-start); 154 | position: absolute; 155 | top: .4em; 156 | left: .6em; 157 | min-width: 1em; 158 | padding: 0 .5em; 159 | background-color: hsla(24, 20%, 50%,.4); 160 | color: hsl(24, 20%, 95%); 161 | font: bold 65%/1.5 sans-serif; 162 | text-align: center; 163 | vertical-align: .3em; 164 | border-radius: 999px; 165 | text-shadow: none; 166 | box-shadow: 0 1px white; 167 | } 168 | 169 | pre[data-line] .line-highlight[data-end]:after { 170 | content: attr(data-end); 171 | top: auto; 172 | bottom: .4em; 173 | } -------------------------------------------------------------------------------- /styles/twemoji.css: -------------------------------------------------------------------------------- 1 | .emoji { 2 | height: 0.8em; 3 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/webview/**/*.{html,js,jsx,ts,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | corePlugins: { 8 | // Disable this because it will pollute the none.css. 9 | preflight: false, 10 | }, 11 | plugins: [require('daisyui')], 12 | daisyui: { 13 | themes: [ 14 | { 15 | light: { 16 | ...require('daisyui/src/theming/themes')['[data-theme=light]'], 17 | 'primary': '#95c258', 18 | 'primary-focus': '#88bb43', 19 | }, 20 | }, 21 | { 22 | dark: { 23 | ...require('daisyui/src/theming/themes')['[data-theme=dark]'], 24 | 'primary': '#6fa129', 25 | 'primary-focus': '#628f25', 26 | }, 27 | }, 28 | ], 29 | }, 30 | darkMode: ['class', '[data-theme="dark"]'], 31 | }; 32 | -------------------------------------------------------------------------------- /test/header-id-generator.test.ts: -------------------------------------------------------------------------------- 1 | import HeadingIdGenerator from '../src/markdown-engine/heading-id-generator'; 2 | 3 | const testCasesForHeaderIdGenerator: { 4 | input: string; 5 | expected: string; 6 | }[] = [ 7 | { 8 | input: 'Hello world!', 9 | expected: 'hello-world', 10 | }, 11 | { 12 | input: ' Leading space world!', 13 | expected: 'leading-space-world', 14 | }, 15 | { 16 | input: 'Trailing space world! ', 17 | expected: 'trailing-space-world', 18 | }, 19 | { 20 | input: 'Hello, world!', 21 | expected: 'hello-world-1', 22 | }, 23 | { 24 | input: 'foo0 ~ bar', 25 | expected: 'foo0---bar', 26 | }, 27 | { 28 | input: 'foo1 `,` bar', 29 | expected: 'foo1--bar', 30 | }, 31 | { 32 | input: 'foo2 `` bar', 33 | expected: 'foo2--bar', 34 | }, 35 | { 36 | input: 'foo3 `` ` `` bar', 37 | expected: 'foo3--bar', 38 | }, 39 | { 40 | input: 'foo4 `` abc`def `` bar', 41 | expected: 'foo4-abc-def-bar', 42 | }, 43 | { 44 | input: 'foo5 `` `` bar', 45 | expected: 'foo5---bar', 46 | }, 47 | { 48 | input: 'foo6 `,-!(){}[]` bar', 49 | expected: 'foo6---bar', 50 | }, 51 | { 52 | input: 'foo7 `abc-def` bar', 53 | expected: 'foo7-abc-def-bar', 54 | }, 55 | { 56 | input: 'foo8 `.` `.` `.` `.` `.` bar', 57 | expected: 'foo8------bar', 58 | }, 59 | { 60 | input: 'foo9 `` `` `` `` `` bar', 61 | expected: 'foo9------bar', 62 | }, 63 | { 64 | input: 'foo10` , `bar', 65 | expected: 'foo10bar', 66 | }, 67 | { 68 | input: 'foo11!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~bar', 69 | expected: 'foo11-_bar', 70 | }, 71 | { 72 | input: 'The _Real_ Deal', 73 | expected: 'the-real-deal', 74 | }, 75 | { 76 | input: 'The __Real__ Deal', 77 | expected: 'the-real-deal-1', 78 | }, 79 | { 80 | input: 'test_test_test', 81 | expected: 'test_test_test', 82 | }, 83 | { 84 | input: 'test _test_ test', 85 | expected: 'test-test-test', 86 | }, 87 | { 88 | input: 'test__test__test', 89 | expected: 'test__test__test', 90 | }, 91 | { 92 | input: 'test __test__ test', 93 | expected: 'test-test-test-1', 94 | }, 95 | ]; 96 | 97 | describe('header-id-generator', () => { 98 | const headerIdGenerator = new HeadingIdGenerator(); 99 | 100 | testCasesForHeaderIdGenerator.map(({ input, expected }) => { 101 | it(`generates header ID for |${input}| correctly`, () => { 102 | const actual: string = headerIdGenerator.generateId(input); 103 | expect(actual).toEqual(expected); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/integration/fixtures/basics.md: -------------------------------------------------------------------------------- 1 | Here is some `inline` code! 2 | 3 | --- 4 | 5 | spaced code block 6 | 7 | var greeting = 'Hello world!'; 8 | console.log(greeting); 9 | 10 | --- 11 | 12 | fenced code block 13 | 14 | ``` 15 | var greeting = 'Hello world!'; 16 | console.log(greeting); 17 | ``` 18 | 19 | --- 20 | 21 | fenced plus language `js` 22 | 23 | ```js 24 | var greeting = 'Hello world!'; 25 | console.log(greeting); 26 | ``` 27 | 28 | --- 29 | 30 | `js .line-numbers` 31 | 32 | ```js .line-numbers 33 | var greeting = 'Hello world!'; 34 | console.log(greeting); 35 | 36 | var greeting2 = 'Hello world2!'; 37 | console.log(greeting2); 38 | 39 | var greeting3 = 'Hello world3!'; 40 | console.log(greeting3); 41 | 42 | var greeting4 = 'Hello world4!'; 43 | console.log(greeting4); 44 | ``` 45 | 46 | --- 47 | 48 | `js {hide=true}` 49 | 50 | ```js {hide=true} 51 | this should not be seen 52 | ``` 53 | 54 | --- 55 | 56 | `js {cmd=false}` 57 | 58 | ```js {cmd=false} 59 | var greeting = 'Hello world!'; 60 | console.log(greeting); 61 | ``` 62 | 63 | --- 64 | 65 | `js {literate=false}` 66 | 67 | ```js {literate=false} 68 | var greeting = 'Hello world!'; 69 | console.log(greeting); 70 | ``` 71 | -------------------------------------------------------------------------------- /test/integration/fixtures/code-chunks.md: -------------------------------------------------------------------------------- 1 | ## Table of contents with TOC {ignore=true} 2 | 3 | The above header should not appear in TOC 4 | 5 | [TOC] 6 | 7 | ## Table of contents with code chunk {ignore=true} 8 | 9 | The above header should not appear in TOC 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ## Bash 18 | 19 | `bash {cmd=true}` 20 | 21 | ```bash {cmd=true} 22 | ls . 23 | ``` 24 | 25 | --- 26 | 27 | ## JavaScript 28 | 29 | `js {cmd=node output=html}` 30 | 31 | ```js {cmd=node output=html} 32 | const date = Date.now(); 33 | console.log(date.toString()); 34 | ``` 35 | 36 | --- 37 | 38 | `js {cmd=node output=markdown}` 39 | 40 | ```js {cmd=node output=markdown} 41 | var greeting = 'Hello _world_'; 42 | console.log(greeting); 43 | ``` 44 | 45 | --- 46 | 47 | `js {cmd=node output=markdown output_first}` 48 | 49 | ```js {cmd=node output=markdown output_first} 50 | var greeting = 'Hello _world_'; 51 | console.log(greeting); 52 | ``` 53 | 54 | --- 55 | 56 | `js {cmd=node output=none}` 57 | 58 | ```js {cmd=node output=none} 59 | var greeting = 'Hello world!'; 60 | console.log(greeting); 61 | ``` 62 | 63 | --- 64 | 65 | `js {cmd=node output=txt modify_source}` 66 | 67 | ```js {cmd=node output=txt modify_source} 68 | var greeting = 'Hello world!'; 69 | console.log(greeting); 70 | ``` 71 | 72 | --- 73 | 74 | `js {cmd=node output=txt modify_source run_on_save}` 75 | 76 | ```js {cmd=node output=txt modify_source run_on_save} 77 | var greeting = 'Hello world!!!'; 78 | console.log(greeting); 79 | ``` 80 | 81 | --- 82 | 83 | ## Python 84 | 85 | `gnuplot {cmd=true output="html"}` 86 | 87 | ```gnuplot {cmd=true output="html"} 88 | set terminal svg 89 | set title "Simple Plots" font ",20" 90 | set key left box 91 | set samples 50 92 | set style data points 93 | 94 | plot [-10:10] sin(x),atan(x),cos(atan(x)) 95 | ``` 96 | 97 | --- 98 | 99 | `python {cmd=true args=["-v"]}` 100 | 101 | ```python {cmd=true args=["-v"]} 102 | print("Verbose will be printed first") 103 | ``` 104 | 105 | --- 106 | 107 | `python {hide=true}` 108 | 109 | ```python {hide=true} 110 | print('you can see this output message, but not this code') 111 | ``` 112 | 113 | --- 114 | 115 | `python {cmd=true id="izdlk700"}` 116 | 117 | ```python {cmd=true id="izdlk700"} 118 | x = 1 119 | ``` 120 | 121 | `python {cmd=true id="izdlkdim"}` 122 | 123 | ```python {cmd=true id="izdlkdim"} 124 | x = 2 125 | ``` 126 | 127 | `python {cmd=true continue="izdlk700" id="izdlkhso"}` 128 | 129 | ```python {cmd=true continue="izdlk700" id="izdlkhso"} 130 | print(x) # will print 1 131 | ``` 132 | 133 | --- 134 | 135 | `js {cmd=node output=text .line-numbers}` 136 | 137 | ```js {cmd=node output=text .line-numbers} 138 | const date = Date.now(); 139 | console.log(date.toString()); 140 | ``` 141 | 142 | --- 143 | 144 | ## LaTeX 145 | 146 | `latex {cmd=true}` 147 | 148 | ```latex {cmd=true} 149 | \documentclass{standalone} 150 | \begin{document} 151 | Hello world! 152 | \end{document} 153 | ``` 154 | 155 | --- 156 | 157 | `latex {cmd latex_zoom=2}` 158 | 159 | ```latex {cmd latex_zoom=2} 160 | \documentclass{standalone} 161 | \begin{document} 162 | Hello world! 163 | \end{document} 164 | ``` 165 | 166 | --- 167 | 168 | `erd {cmd=true output="html" args=["-i", "$input_file" "-f", "svg"]}` 169 | 170 | ```erd {cmd=true output="html" args=["-i", "$input_file" "-f", "svg"]} 171 | [Person] 172 | *name 173 | height 174 | weight 175 | +birth_location_id 176 | 177 | [Location] 178 | *id 179 | city 180 | state 181 | country 182 | 183 | Person *--1 Location 184 | ``` 185 | -------------------------------------------------------------------------------- /test/integration/fixtures/data/sp500.csv: -------------------------------------------------------------------------------- 1 | date,price 2 | Jan 1 2000,1394.46 3 | Feb 1 2000,1366.42 4 | Mar 1 2000,1498.58 5 | Apr 1 2000,1452.43 6 | May 1 2000,1420.6 7 | Jun 1 2000,1454.6 8 | Jul 1 2000,1430.83 9 | Aug 1 2000,1517.68 10 | Sep 1 2000,1436.51 11 | Oct 1 2000,1429.4 12 | Nov 1 2000,1314.95 13 | Dec 1 2000,1320.28 14 | Jan 1 2001,1366.01 15 | Feb 1 2001,1239.94 16 | Mar 1 2001,1160.33 17 | Apr 1 2001,1249.46 18 | May 1 2001,1255.82 19 | Jun 1 2001,1224.38 20 | Jul 1 2001,1211.23 21 | Aug 1 2001,1133.58 22 | Sep 1 2001,1040.94 23 | Oct 1 2001,1059.78 24 | Nov 1 2001,1139.45 25 | Dec 1 2001,1148.08 26 | Jan 1 2002,1130.2 27 | Feb 1 2002,1106.73 28 | Mar 1 2002,1147.39 29 | Apr 1 2002,1076.92 30 | May 1 2002,1067.14 31 | Jun 1 2002,989.82 32 | Jul 1 2002,911.62 33 | Aug 1 2002,916.07 34 | Sep 1 2002,815.28 35 | Oct 1 2002,885.76 36 | Nov 1 2002,936.31 37 | Dec 1 2002,879.82 38 | Jan 1 2003,855.7 39 | Feb 1 2003,841.15 40 | Mar 1 2003,848.18 41 | Apr 1 2003,916.92 42 | May 1 2003,963.59 43 | Jun 1 2003,974.5 44 | Jul 1 2003,990.31 45 | Aug 1 2003,1008.01 46 | Sep 1 2003,995.97 47 | Oct 1 2003,1050.71 48 | Nov 1 2003,1058.2 49 | Dec 1 2003,1111.92 50 | Jan 1 2004,1131.13 51 | Feb 1 2004,1144.94 52 | Mar 1 2004,1126.21 53 | Apr 1 2004,1107.3 54 | May 1 2004,1120.68 55 | Jun 1 2004,1140.84 56 | Jul 1 2004,1101.72 57 | Aug 1 2004,1104.24 58 | Sep 1 2004,1114.58 59 | Oct 1 2004,1130.2 60 | Nov 1 2004,1173.82 61 | Dec 1 2004,1211.92 62 | Jan 1 2005,1181.27 63 | Feb 1 2005,1203.6 64 | Mar 1 2005,1180.59 65 | Apr 1 2005,1156.85 66 | May 1 2005,1191.5 67 | Jun 1 2005,1191.33 68 | Jul 1 2005,1234.18 69 | Aug 1 2005,1220.33 70 | Sep 1 2005,1228.81 71 | Oct 1 2005,1207.01 72 | Nov 1 2005,1249.48 73 | Dec 1 2005,1248.29 74 | Jan 1 2006,1280.08 75 | Feb 1 2006,1280.66 76 | Mar 1 2006,1294.87 77 | Apr 1 2006,1310.61 78 | May 1 2006,1270.09 79 | Jun 1 2006,1270.2 80 | Jul 1 2006,1276.66 81 | Aug 1 2006,1303.82 82 | Sep 1 2006,1335.85 83 | Oct 1 2006,1377.94 84 | Nov 1 2006,1400.63 85 | Dec 1 2006,1418.3 86 | Jan 1 2007,1438.24 87 | Feb 1 2007,1406.82 88 | Mar 1 2007,1420.86 89 | Apr 1 2007,1482.37 90 | May 1 2007,1530.62 91 | Jun 1 2007,1503.35 92 | Jul 1 2007,1455.27 93 | Aug 1 2007,1473.99 94 | Sep 1 2007,1526.75 95 | Oct 1 2007,1549.38 96 | Nov 1 2007,1481.14 97 | Dec 1 2007,1468.36 98 | Jan 1 2008,1378.55 99 | Feb 1 2008,1330.63 100 | Mar 1 2008,1322.7 101 | Apr 1 2008,1385.59 102 | May 1 2008,1400.38 103 | Jun 1 2008,1280 104 | Jul 1 2008,1267.38 105 | Aug 1 2008,1282.83 106 | Sep 1 2008,1166.36 107 | Oct 1 2008,968.75 108 | Nov 1 2008,896.24 109 | Dec 1 2008,903.25 110 | Jan 1 2009,825.88 111 | Feb 1 2009,735.09 112 | Mar 1 2009,797.87 113 | Apr 1 2009,872.81 114 | May 1 2009,919.14 115 | Jun 1 2009,919.32 116 | Jul 1 2009,987.48 117 | Aug 1 2009,1020.62 118 | Sep 1 2009,1057.08 119 | Oct 1 2009,1036.19 120 | Nov 1 2009,1095.63 121 | Dec 1 2009,1115.1 122 | Jan 1 2010,1073.87 123 | Feb 1 2010,1104.49 124 | Mar 1 2010,1140.45 125 | -------------------------------------------------------------------------------- /test/integration/fixtures/file-imports.md: -------------------------------------------------------------------------------- 1 | # [File imports](https://shd101wyy.github.io/markdown-preview-enhanced/#/file-imports) 2 | 3 | `@import "file-imports/markdown-logo.jpg"` 4 | @import "file-imports/markdown-logo.jpg" 5 | 6 | --- 7 | 8 | `@import "file-imports/markdown-logo.png"` 9 | @import "file-imports/markdown-logo.png" 10 | 11 | --- 12 | 13 | `@import "file-imports/markdown-logo.png" {width="100px" height="62px" title="my title" alt="my alt"}` 14 | @import "file-imports/markdown-logo.png" {width="100px" height="62px" title="my title" alt="my alt"} 15 | 16 | --- 17 | 18 | `@import "file-imports/diagram.mermaid"` 19 | @import "file-imports/diagram.mermaid" 20 | 21 | --- 22 | 23 | `@import "file-imports/diagram.mermaid" {hide=false}` 24 | @import "file-imports/diagram.mermaid" {hide=false} 25 | -------------------------------------------------------------------------------- /test/integration/fixtures/math.md: -------------------------------------------------------------------------------- 1 | ## [Math](https://shd101wyy.github.io/markdown-preview-enhanced/#/math) 2 | 3 | `hello $inline$ math` 4 | hello $inline$ math 5 | 6 | --- 7 | 8 | `$$ E = mc^22 $$` 9 | $$ E = mc^22 $$ 10 | 11 | --- 12 | 13 | `math` 14 | 15 | ```math 16 | E = mc^2 17 | ``` 18 | 19 | --- 20 | 21 | `math hide=false` 22 | 23 | ```math hide=false 24 | E = mc^2 25 | ``` 26 | 27 | --- 28 | 29 | `math hide=false output_first` 30 | 31 | ```math hide=false output_first 32 | E = mc^2 33 | ``` 34 | 35 | --- 36 | 37 | `math literate=false` 38 | 39 | ```math literate=false 40 | E = mc^2 41 | ``` 42 | -------------------------------------------------------------------------------- /test/lib/block-info.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BlockInfo, 3 | normalizeBlockInfo, 4 | parseBlockInfo, 5 | } from '../../src/lib/block-info'; 6 | 7 | const testCasesForParseBlockInfo: { 8 | expect: BlockInfo; 9 | input: string; 10 | }[] = [ 11 | { 12 | input: '', 13 | expect: { language: '', attributes: { class: '' } }, 14 | }, 15 | { 16 | input: '{}', 17 | expect: { language: '', attributes: { class: '' } }, 18 | }, 19 | { 20 | input: '{#id}', 21 | expect: { language: '', attributes: { id: 'id', class: '' } }, 22 | }, 23 | { 24 | input: 'js cmd=true', 25 | expect: { language: 'js', attributes: { class: 'js', cmd: true } }, 26 | }, 27 | { 28 | input: 'js {cmd=true}', 29 | expect: { language: 'js', attributes: { class: 'js', cmd: true } }, 30 | }, 31 | { 32 | input: 'js { cmd=true } ', 33 | expect: { language: 'js', attributes: { class: 'js', cmd: true } }, 34 | }, 35 | { 36 | input: 'js{cmd=True}', 37 | expect: { language: 'js', attributes: { class: 'js', cmd: true } }, 38 | }, 39 | { 40 | input: '{.js}', 41 | expect: { language: 'js', attributes: { class: 'js' } }, 42 | }, 43 | { 44 | input: '{.js .text}', 45 | expect: { language: 'js', attributes: { class: 'js text' } }, 46 | }, 47 | { 48 | input: '{.js .text cmd=true}', 49 | expect: { language: 'js', attributes: { class: 'js text', cmd: true } }, 50 | }, 51 | { 52 | input: '{.js .text cmd=true hello=world}', 53 | expect: { 54 | language: 'js', 55 | attributes: { class: 'js text', cmd: true, hello: 'world' }, 56 | }, 57 | }, 58 | { 59 | input: 'hello', 60 | expect: { language: 'hello', attributes: { class: 'hello' } }, 61 | }, 62 | { 63 | input: ' hello ', 64 | expect: { language: 'hello', attributes: { class: 'hello' } }, 65 | }, 66 | { 67 | input: 'hello {}', 68 | expect: { language: 'hello', attributes: { class: 'hello' } }, 69 | }, 70 | { 71 | input: 'hello {.text}', 72 | expect: { language: 'hello', attributes: { class: 'hello text' } }, 73 | }, 74 | { 75 | input: 'hello { }', 76 | expect: { language: 'hello', attributes: { class: 'hello' } }, 77 | }, 78 | { 79 | input: ' {just=attribute}', 80 | expect: { 81 | language: '', 82 | attributes: { just: 'attribute', class: '' }, 83 | }, 84 | }, 85 | { 86 | input: ' {just=attribute .python}', 87 | expect: { 88 | language: 'python', 89 | attributes: { just: 'attribute', class: 'python' }, 90 | }, 91 | }, 92 | { 93 | input: ' {just=attribute .python .js}', 94 | expect: { 95 | language: 'python', 96 | attributes: { just: 'attribute', class: 'python js' }, 97 | }, 98 | }, 99 | { 100 | input: 'html {just=attribute .python .js}', 101 | expect: { 102 | language: 'html', 103 | attributes: { just: 'attribute', class: 'html python js' }, 104 | }, 105 | }, 106 | ]; 107 | 108 | const testCasesForNormalizeCodeBlockInfo: { 109 | input: object; 110 | expect: BlockInfo; 111 | }[] = [ 112 | { 113 | input: { language: '', attributes: {} }, 114 | expect: { language: '', attributes: {} }, 115 | }, 116 | { 117 | input: { language: 'js', attributes: { cmd: true } }, 118 | expect: { language: 'js', attributes: { cmd: true } }, 119 | }, 120 | { 121 | input: { language: 'js', attributes: { Cmd: true } }, 122 | expect: { language: 'js', attributes: { cmd: true } }, 123 | }, 124 | { 125 | input: { language: 'js', attributes: { CMD: true } }, 126 | expect: { language: 'js', attributes: { cmd: true } }, 127 | }, 128 | { 129 | input: { language: 'vega' }, 130 | expect: { language: 'vega', attributes: {} }, 131 | }, 132 | { 133 | input: { language: 'VEGA', attributes: {} }, 134 | expect: { language: 'vega', attributes: {} }, 135 | }, 136 | ]; 137 | 138 | describe('lib/block-info', () => { 139 | testCasesForParseBlockInfo.map(({ input, expect: expect_ }) => { 140 | it(`parseBlockInfo() correctly parses ${input}`, () => { 141 | const result: object = parseBlockInfo(input); 142 | expect(result).toEqual(expect_); 143 | }); 144 | }); 145 | 146 | testCasesForNormalizeCodeBlockInfo.map(({ input, expect: expect_ }) => { 147 | it(`normalizeCodeBlockInfo() correctly normalizes ${JSON.stringify( 148 | input, 149 | )}`, () => { 150 | const result: object = normalizeBlockInfo(input as BlockInfo); 151 | expect(result).toEqual(expect_); 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/markdown/test-files/test1.expect.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Hi there {#hi-there data-source-line="4"} 5 | 6 | haha 7 | -------------------------------------------------------------------------------- /test/markdown/test-files/test1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Test" 3 | --- 4 | # Hi there 5 | 6 | haha 7 | -------------------------------------------------------------------------------- /test/markdown/test-files/test2.expect.md: -------------------------------------------------------------------------------- 1 | ```text cmd="toc" depthFrom=1 depthTo=6 orderedList=false data-source-line=1 hide=true run_on_save=true modify_source=true code_chunk_offset=0 2 | ``` 3 | 4 | 5 | - [Heading 1](#heading-1) 6 | - [Heading 2](#heading-2) 7 | 8 | 9 | 10 | # Heading 1 {#heading-1 data-source-line="10"} 11 | 12 | paragraph 13 | 14 | ## Heading 2 {#heading-2 data-source-line="14"} 15 | 16 | paragraph 17 | -------------------------------------------------------------------------------- /test/markdown/test-files/test2.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | - [Heading 1](#heading-1) 6 | - [Heading 2](#heading-2) 7 | 8 | 9 | 10 | # Heading 1 11 | 12 | paragraph 13 | 14 | ## Heading 2 15 | 16 | paragraph 17 | -------------------------------------------------------------------------------- /test/markdown/test-files/test3.expect.md: -------------------------------------------------------------------------------- 1 | # This is test3.md {#this-is-test3md data-source-line="1"} 2 | 3 | ![@embedding](test2.md){x=1 y=2 data-source-line=3 embedding="JTBBJTYwJTYwJTYwdGV4dCUyMGNtZCUzRCUyMnRvYyUyMiUyMGRlcHRoRnJvbSUzRDElMjBkZXB0aFRvJTNENiUyMG9yZGVyZWRMaXN0JTNEZmFsc2UlMjBoaWRlJTNEdHJ1ZSUyMHJ1bl9vbl9zYXZlJTNEdHJ1ZSUyMG1vZGlmeV9zb3VyY2UlM0R0cnVlJTIwJTIwJTBBJTYwJTYwJTYwJTIwJTIwJTBBJTBBJTBBLSUyMCU1QkhlYWRpbmclMjAxJTVEKCUyM2hlYWRpbmctMSklMEElMjAlMjAtJTIwJTVCSGVhZGluZyUyMDIlNUQoJTIzaGVhZGluZy0yKSUwQSUwQSUwQSUwQSUyMyUyMEhlYWRpbmclMjAxJTIwJTdCJTIzaGVhZGluZy0xJTIwJTdEJTBBJTBBcGFyYWdyYXBoJTBBJTBBJTIzJTIzJTIwSGVhZGluZyUyMDIlMjAlN0IlMjNoZWFkaW5nLTIlMjAlN0QlMEElMEFwYXJhZ3JhcGglMEElMjAlMjA="} 4 | -------------------------------------------------------------------------------- /test/markdown/test-files/test3.md: -------------------------------------------------------------------------------- 1 | # This is test3.md 2 | 3 | @import "test2.md" {x=1 y=2} 4 | -------------------------------------------------------------------------------- /test/markdown/test-files/test4.expect.md: -------------------------------------------------------------------------------- 1 | `[test](test.md)` 2 | **[test](test.md)** 3 | 4 | `![test](test.png)` 5 | **![test](test.md)** 6 | 7 | ![test](test.png){data-source-line=7} 8 | ![test](https://test.png){data-source-line=8} 9 | ![test](test.png#hash){data-source-line=9} 10 | ![This is alt](test.png "This is title"){data-source-line=10} 11 | ![test](test.png){title="This is title" alt="This is alt" data-source-line=11} 12 | ![test](test.png){x=1 y=2 data-source-line=12} 13 | 14 | ![](/test.png?12345){data-source-line=14} 15 | ![](https:/test.png){data-source-line=15} 16 | ![](/test.png?12345){data-source-line=16} 17 | ![This is alt](/test.png?12345 "This is title"){data-source-line=17} 18 | ![](/test.png?12345){x=1 y=2 data-source-line=18} 19 | 20 | ![](/test.png?12345){data-source-line=20} 21 | ![](https://test.png){data-source-line=21} 22 | ![](/test.png?12345){data-source-line=22} 23 | ![This is alt](/test.png?12345 "This is title"){data-source-line=23} 24 | ![](/test.png?12345){x=1 y=2 data-source-line=24} 25 | -------------------------------------------------------------------------------- /test/markdown/test-files/test4.md: -------------------------------------------------------------------------------- 1 | `[test](test.md)` 2 | **[test](test.md)** 3 | 4 | `![test](test.png)` 5 | **![test](test.md)** 6 | 7 | ![test](test.png) 8 | ![test](https://test.png) 9 | ![test](test.png#hash) 10 | ![This is alt](test.png "This is title") 11 | ![test](test.png){title="This is title" alt="This is alt"} 12 | ![test](test.png){x=1 y=2} 13 | 14 | ![[test.png]] 15 | ![[https://test.png]] 16 | ![[test.png#hash]] 17 | ![[test.png]]{title="This is title" alt="This is alt"} 18 | ![[test.png]]{x=1 y=2} 19 | 20 | @import "test.png" 21 | @import "https://test.png" 22 | @import "test.png#hash" 23 | @import "test.png" {title="This is title" alt="This is alt"} 24 | @import "test.png" {x=1 y=2} 25 | -------------------------------------------------------------------------------- /test/markdown/test-files/test5.expect.md: -------------------------------------------------------------------------------- 1 | ![$N_d$ vs $f_l$. The pulsar's DM is assumed to be 750 pc cm$^{-3}$ with a Nyquist complex-sampling bandwidth of 16 MHz.](assets/Multi-Segment%20Coherent%20Dedispersion%20Method%20Based%20on%20Overlap-Save/2.png){id="fig:dispersion-smearing-time" width="75%" data-source-line=1} 2 | -------------------------------------------------------------------------------- /test/markdown/test-files/test5.md: -------------------------------------------------------------------------------- 1 | ![$N_d$ vs $f_l$. The pulsar's DM is assumed to be 750 pc cm$^{-3}$ with a Nyquist complex-sampling bandwidth of 16 MHz.](assets/Multi-Segment%20Coherent%20Dedispersion%20Method%20Based%20on%20Overlap-Save/2.png){#fig:dispersion-smearing-time width=75%} 2 | -------------------------------------------------------------------------------- /test/markdown/test-files/test6.expect.md: -------------------------------------------------------------------------------- 1 | # Title {#title data-source-line="1"} 2 | 3 | ```example```: let's see what happens 4 | 5 | ```javascript {data-source-line="5"} 6 | console.log("Hello, world"); 7 | ``` 8 | -------------------------------------------------------------------------------- /test/markdown/test-files/test6.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | ```example```: let's see what happens 4 | 5 | ```javascript 6 | console.log("Hello, world"); 7 | ``` 8 | -------------------------------------------------------------------------------- /test/markdown/test-files/test7.expect.md: -------------------------------------------------------------------------------- 1 | # How are you {#how-are-you data-source-line="1"} 2 | 3 | #tag shouldn't be header 4 | -------------------------------------------------------------------------------- /test/markdown/test-files/test7.md: -------------------------------------------------------------------------------- 1 | # How are you 2 | 3 | #tag shouldn't be header 4 | -------------------------------------------------------------------------------- /test/markdown/test-files/test8.expect.md: -------------------------------------------------------------------------------- 1 | # Code blocks {#code-blocks data-source-line="1"} 2 | 3 | ``` {data-source-line="3"} 4 | This is code block without language selector 5 | ``` 6 | 7 | ```text {data-source-line="7"} 8 | This is another text code block 9 | ``` 10 | 11 | ``` {.python data-source-line="11"} 12 | def hello(): 13 | println("Hello, world") 14 | ``` 15 | 16 | ```python {data-source-line="16"} 17 | def hello(): 18 | println("Hello, world") 19 | ``` 20 | -------------------------------------------------------------------------------- /test/markdown/test-files/test8.md: -------------------------------------------------------------------------------- 1 | # Code blocks 2 | 3 | ``` 4 | This is code block without language selector 5 | ``` 6 | 7 | ```text 8 | This is another text code block 9 | ``` 10 | 11 | ```{.python} 12 | def hello(): 13 | println("Hello, world") 14 | ``` 15 | 16 | ```python 17 | def hello(): 18 | println("Hello, world") 19 | ``` 20 | -------------------------------------------------------------------------------- /test/markdown/transformer.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, readdirSync } from 'fs'; 2 | import * as path from 'path'; 3 | import { transformMarkdown } from '../../src/markdown-engine/transformer'; 4 | import { Notebook } from '../../src/notebook/index'; 5 | 6 | const testCases = readdirSync(path.join(__dirname, './test-files')) 7 | .map((fileName) => { 8 | if (fileName.match(/\.expect\.md/)) { 9 | return [fileName.replace(/\.expect\./, '.'), fileName]; 10 | } 11 | }) 12 | .filter((x) => !!x); 13 | 14 | describe('test markdown transformer', () => { 15 | testCases.forEach((arr) => { 16 | if (!arr) { 17 | return; 18 | } 19 | const [inputFileName, expectFileName] = arr; 20 | 21 | test(`transformMarkdown() correctly transforms ${inputFileName}`, async () => { 22 | const notebook = await Notebook.init({ 23 | notebookPath: path.resolve(__dirname, './test-files'), 24 | config: { 25 | usePandocParser: false, 26 | }, 27 | }); 28 | const transform = async (markdown: string) => { 29 | return await transformMarkdown(markdown, { 30 | notebook, 31 | forPreview: true, 32 | fileDirectoryPath: path.join(__dirname, './test-files'), 33 | projectDirectoryPath: path.join(__dirname, './test-files'), 34 | filesCache: {}, 35 | useRelativeFilePath: false, 36 | protocolsWhiteListRegExp: /^(https?)/, 37 | forJest: true, 38 | timestamp: 12345, 39 | }); 40 | }; 41 | 42 | const markdown = readFileSync( 43 | path.join(__dirname, './test-files', inputFileName), 44 | 'utf-8', 45 | ); 46 | const expected = readFileSync( 47 | path.join(__dirname, './test-files', expectFileName), 48 | 'utf-8', 49 | ); 50 | const { outputString } = await transform(markdown); 51 | expect(outputString.trim()).toEqual(expected.trim()); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "module": "CommonJS", 5 | "target": "ES2020", 6 | "sourceMap": true, 7 | "skipLibCheck": true, 8 | "lib": ["ES2020", "DOM"], 9 | "rootDir": ".", 10 | "declaration": true, 11 | "noUnusedLocals": true, 12 | "removeComments": true, 13 | "strictNullChecks": true, 14 | "esModuleInterop": true, 15 | "incremental": true, 16 | "emitDeclarationOnly": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "outDir": "./out/types/", 19 | "resolveJsonModule": true, 20 | "allowJs": true 21 | // "declarationMap": true 22 | // "outFile": "./out/types/index.d.ts" 23 | // "strict": true 24 | }, 25 | "include": ["src/**/*", "test/**/*"], 26 | "exclude": [ 27 | "node_modules", 28 | "out", 29 | "build.js", 30 | "gulpfile.js", 31 | "tailwind.config.js" 32 | ] 33 | } 34 | --------------------------------------------------------------------------------