├── .npmrc ├── .eslintignore ├── screenshots ├── out.gif ├── inline.png ├── codeblock.png ├── multiline.png └── insert-symbol.png ├── src ├── mathjax.d.ts ├── utils.ts ├── confirm-modal.ts ├── commands.ts ├── symbol-search │ ├── modal-instance.ts │ ├── modal.ts │ └── symbols.json ├── settings.ts ├── main.ts └── convertion.ts ├── manifest.json ├── .gitignore ├── versions.json ├── tsconfig.json ├── styles.css ├── version-bump.mjs ├── .eslintrc ├── package.json ├── LICENSE ├── esbuild.config.mjs ├── biome.json ├── .github └── workflows │ └── release.yml ├── README.md └── pnpm-lock.yaml /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build 3 | *.json 4 | main.js 5 | -------------------------------------------------------------------------------- /screenshots/out.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widcardw/obsidian-asciimath/HEAD/screenshots/out.gif -------------------------------------------------------------------------------- /screenshots/inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widcardw/obsidian-asciimath/HEAD/screenshots/inline.png -------------------------------------------------------------------------------- /screenshots/codeblock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widcardw/obsidian-asciimath/HEAD/screenshots/codeblock.png -------------------------------------------------------------------------------- /screenshots/multiline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widcardw/obsidian-asciimath/HEAD/screenshots/multiline.png -------------------------------------------------------------------------------- /screenshots/insert-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/widcardw/obsidian-asciimath/HEAD/screenshots/insert-symbol.png -------------------------------------------------------------------------------- /src/mathjax.d.ts: -------------------------------------------------------------------------------- 1 | interface IMathJax { 2 | tex2chtml: (source: string, r: { display: boolean }) => any 3 | } 4 | declare const MathJax: IMathJax 5 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-asciimath", 3 | "name": "asciimath", 4 | "version": "0.8.1", 5 | "minAppVersion": "0.15.0", 6 | "description": "Add asciimath support for Obsidian.", 7 | "author": "widcardw", 8 | "authorUrl": "https://widcard.win", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.6.5": "0.15.0", 3 | "0.6.6": "0.15.0", 4 | "0.6.7": "0.15.0", 5 | "0.6.8": "0.15.0", 6 | "0.6.9": "0.15.0", 7 | "0.6.10": "0.15.0", 8 | "0.7.0": "0.15.0", 9 | "0.7.1": "0.15.0", 10 | "0.7.2": "0.15.0", 11 | "0.7.3": "0.15.0", 12 | "0.7.4": "0.15.0", 13 | "0.7.5": "0.15.0", 14 | "0.7.6": "0.15.0", 15 | "0.7.7": "0.15.0", 16 | "0.7.8": "0.15.0", 17 | "0.8.0": "0.15.0", 18 | "0.8.1": "0.15.0" 19 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "resolveJsonModule": true, 13 | "esModuleInterop": true, 14 | "isolatedModules": true, 15 | "strictNullChecks": true, 16 | "lib": ["DOM", "ES5", "ES6", "ES7", "ESNext"] 17 | }, 18 | "include": ["**/*.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | .__asciimath_settings_custom-symbols { 10 | width: 300px; 11 | height: 100px; 12 | font-family: var(--font-monospace), 'Courier New', Courier, monospace; 13 | } 14 | 15 | .__asciimath-symbol-search-result { 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | 20 | .__asciimath-symbol-search-preview { 21 | font-size: 1.5rem; 22 | display: flex; 23 | align-items: center; 24 | } 25 | 26 | .__asciimath-symbol-search-placeholder { 27 | opacity: 0.5; 28 | } 29 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs' 2 | 3 | const targetVersion = process.env.npm_package_version 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | const manifest = JSON.parse(readFileSync('manifest.json', 'utf8')) 7 | const { minAppVersion } = manifest 8 | manifest.version = targetVersion 9 | writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t')) 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | const versions = JSON.parse(readFileSync('versions.json', 'utf8')) 13 | versions[targetVersion] = minAppVersion 14 | writeFileSync('versions.json', JSON.stringify(versions, null, '\t')) 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "@antfu/eslint-config-ts", 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/eslint-recommended", 12 | "plugin:@typescript-eslint/recommended" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module" 16 | }, 17 | "rules": { 18 | "no-unused-vars": "off", 19 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 20 | "@typescript-eslint/ban-ts-comment": "off", 21 | "no-prototype-builtins": "off", 22 | "@typescript-eslint/no-empty-function": "off", 23 | "no-new": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-asciimath", 3 | "version": "0.8.1", 4 | "description": "Add asciimath support for Obsidian.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json", 10 | "lint": "eslint ." 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@biomejs/biome": "^2.1.4", 17 | "@codemirror/language": "6.3.0", 18 | "@codemirror/state": "6.1.2", 19 | "@codemirror/view": "6.4.1", 20 | "@types/node": "^20.19.10", 21 | "asciimath-parser": "^0.6.10", 22 | "builtin-modules": "3.3.0", 23 | "esbuild": "0.14.47", 24 | "obsidian": "latest", 25 | "ts-dedent": "^2.2.0", 26 | "tslib": "2.4.0", 27 | "typescript": "4.7.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 [widcardw] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import process from 'process' 2 | import esbuild from 'esbuild' 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | ` 10 | 11 | const prod = process.argv[2] === 'production' 12 | 13 | esbuild 14 | .build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['./src/main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins, 35 | ], 36 | format: 'cjs', 37 | watch: !prod, 38 | target: 'es2018', 39 | logLevel: 'info', 40 | sourcemap: prod ? false : 'inline', 41 | treeShaking: true, 42 | outfile: 'main.js', 43 | }) 44 | .catch(() => process.exit(1)) 45 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "a11y": { 11 | "useButtonType": "off", 12 | "useKeyWithClickEvents": "off", 13 | "noSvgWithoutTitle": "off", 14 | "useAltText": "off" 15 | }, 16 | "style": { 17 | "noNonNullAssertion": "off", 18 | "noParameterAssign": "off", 19 | "useImportType": "warn" 20 | }, 21 | "complexity": { 22 | "noForEach": "off", 23 | "useArrowFunction": "off" 24 | }, 25 | "suspicious": { 26 | "noExplicitAny": "off", 27 | "noEmptyInterface": "off" 28 | } 29 | }, 30 | "ignore": ["node_modules/**", "dist/**", ".vscode/**"] 31 | }, 32 | "formatter": { 33 | "indentStyle": "space", 34 | "indentWidth": 2, 35 | "ignore": ["node_modules/**", "dist/**", ".vscode/**"], 36 | "lineEnding": "lf" 37 | }, 38 | "javascript": { 39 | "formatter": { 40 | "semicolons": "asNeeded", 41 | "quoteStyle": "single" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { AsciiMath } from "asciimath-parser" 2 | 3 | 4 | interface FormulaMatch { 5 | type: "inline" | "block"; 6 | start: number; 7 | end: number; 8 | content: string; 9 | } 10 | 11 | 12 | function normalizeEscape(escape_: string) { 13 | return escape_.replace(/([$^\\.()[\]{}*?|])/g, '\\$1') 14 | } 15 | 16 | // This function checks if the given code contains LaTeX code, but it's not AsciiMath embed. 17 | function isLatexCode(code: string): boolean { 18 | const latexRegex = /\\([A-Za-z0-9]){2,}/gm 19 | const simpleLatexSupSubRegex = /[\^_]\{\s*[a-zA-Z0-9 ]+\s*\}/g 20 | const texEmbedRegex = /tex".*"/ 21 | 22 | const hasTrueLatex = latexRegex.test(code) 23 | const hasSimpleLatexSupSub = simpleLatexSupSubRegex.test(code) 24 | const hasTexEmbed = texEmbedRegex.test(code) 25 | 26 | return (hasTrueLatex || (hasSimpleLatexSupSub && !hasTrueLatex)) && !hasTexEmbed 27 | } 28 | 29 | function toTex(am: AsciiMath, content: string, displayMode: boolean): string { 30 | const tex = am.toTex(content, { display: displayMode }) 31 | return tex.replace(/(\{|\})(\1+)/g, (...args) => 32 | Array(args[2].length + 1) 33 | .fill(args[1]) 34 | .join(' '), 35 | ) 36 | } 37 | 38 | 39 | export { normalizeEscape, isLatexCode, toTex } 40 | export type { FormulaMatch } 41 | -------------------------------------------------------------------------------- /src/confirm-modal.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'obsidian' 2 | import { Modal, Setting } from 'obsidian' 3 | 4 | // Confirm modal is used to confirm the action. It'll call onConfirm callback if the action submit button is pressed. 5 | export class ConfirmModal extends Modal { 6 | message: string 7 | enableDisplayMode: boolean 8 | confirmHandler: () => void 9 | 10 | // biome-ignore lint/complexity/noUselessConstructor: 11 | constructor(app: App) { 12 | super(app) 13 | } 14 | 15 | setMessage(message: string): ConfirmModal { 16 | this.message = message 17 | return this 18 | } 19 | 20 | setEnableDisplayMode(enableDisplayMode: boolean): ConfirmModal { 21 | this.enableDisplayMode = enableDisplayMode 22 | return this 23 | } 24 | 25 | onConfirm(f: (v: boolean) => void): ConfirmModal { 26 | this.confirmHandler = () => f(this.enableDisplayMode) 27 | return this 28 | } 29 | 30 | onOpen() { 31 | const { contentEl, titleEl } = this 32 | 33 | titleEl.setText('Are you sure?') 34 | 35 | new Setting(contentEl).setDesc(this.message) 36 | new Setting(contentEl) 37 | .setName('Enable display mode for each formula') 38 | .setDesc('This option will insert \\display{ ... } for each formula.') 39 | .addToggle((toggle) => 40 | toggle 41 | .setValue(this.enableDisplayMode) 42 | .onChange((value) => { 43 | this.enableDisplayMode = value 44 | }), 45 | ) 46 | new Setting(contentEl) 47 | .addButton((btn) => 48 | btn.setButtonText('Cancel').onClick(() => { 49 | this.close() 50 | }), 51 | ) 52 | .addButton((btn) => 53 | btn 54 | .setButtonText('Continue') 55 | .setCta() 56 | .onClick(() => { 57 | this.close() 58 | this.confirmHandler() 59 | }), 60 | ) 61 | } 62 | 63 | onClose() { 64 | const { contentEl } = this 65 | contentEl.empty() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | import type { Command, Editor, MarkdownView } from "obsidian"; 2 | import { createModalInstance } from "./symbol-search/modal-instance"; 3 | import type AsciiMathPlugin from "./main"; 4 | import { ConfirmModal } from "./confirm-modal"; 5 | import dedent from "ts-dedent"; 6 | import { isLatexCode } from "./utils"; 7 | import { actionConvertActiveFile, actionConvertEntireVault } from "./convertion"; 8 | 9 | function initCommands(plugin: AsciiMathPlugin) { 10 | const commands: Command[] = [ 11 | { 12 | id: 'asciimath-insert-symbol', 13 | icon: 'sigma', 14 | name: 'View AsciiMath symbols', 15 | editorCallback: (editor) => { 16 | createModalInstance(editor) 17 | }, 18 | }, 19 | { 20 | id: 'insert-asciimath-block', 21 | name: 'Insert asciimath block', 22 | editorCallback: (editor: Editor, _view: MarkdownView) => { 23 | editor.replaceSelection( 24 | `\`\`\`${plugin.settings.blockPrefix[0] || 'asciimath'}\n${editor 25 | .getDoc() 26 | .getSelection()}\n\`\`\``, 27 | ) 28 | const cursor = editor.getCursor() 29 | editor.setCursor(cursor.line - 1) 30 | }, 31 | }, 32 | { 33 | id: 'convert-selected-to-latex', 34 | name: 'Convert exact selection into LaTeX', 35 | editorCallback: (editor: Editor, _view: MarkdownView) => { 36 | const cursorStart = editor.getCursor('from') 37 | const cursorEnd = editor.getCursor('to') 38 | const amCode = editor.getSelection() 39 | const doConvert = () => 40 | editor.replaceRange(plugin.AM.toTex(amCode), cursorStart, cursorEnd) 41 | 42 | if (amCode.length > 1000) { 43 | new ConfirmModal(plugin.app) 44 | .setMessage( 45 | dedent`The selection is over 1000 chars. 46 | Please confirm that you have selected the exact AsciiMath expression. 47 | Click the Continue button to convert though.`, 48 | ) 49 | .onConfirm(doConvert) 50 | .open() 51 | } else if (isLatexCode(amCode)) { 52 | new ConfirmModal(plugin.app) 53 | .setMessage( 54 | dedent`The selection may be already LaTeX. 55 | Click the Continue buttom to convert though.`, 56 | ) 57 | .onConfirm(doConvert) 58 | .open() 59 | } else { 60 | doConvert() 61 | } 62 | }, 63 | }, 64 | { 65 | id: 'convert-am-block-into-mathjax-in-current-file', 66 | name: 'Convert AsciiMath to LaTeX (active file)', 67 | callback: actionConvertActiveFile( 68 | plugin, 69 | 'This will replace all AsciiMath blocks with LaTeX math blocks in the active file. THIS ACTION CANNOT BE UNDONE.', 70 | ), 71 | }, 72 | { 73 | id: 'convert-am-block-into-mathjax-in-vault', 74 | name: 'Convert AsciiMath to LaTeX (entire vault)', 75 | callback: actionConvertEntireVault( 76 | plugin, 77 | 'This will replace all AsciiMath formulas with LaTeX math blocks in the entire vault. THIS ACTION CANNOT BE UNDONE.', 78 | ), 79 | } 80 | ] 81 | 82 | commands.forEach((command) => { 83 | plugin.addCommand(command) 84 | }) 85 | } 86 | 87 | export { initCommands } -------------------------------------------------------------------------------- /src/symbol-search/modal-instance.ts: -------------------------------------------------------------------------------- 1 | import { Editor } from "obsidian" 2 | import { SymbolSearchModal } from "./modal" 3 | 4 | function createModalInstance(editor: Editor) { 5 | const sel = editor.getSelection() 6 | const modal = new SymbolSearchModal(this.app, sel, this.AM) 7 | modal.setPlaceholder('Start typing AsciiMath or LaTeX symbol name') 8 | 9 | modal.onSelected((sym) => { 10 | const { am } = sym 11 | if ('placeholder' in sym) { 12 | const { placeholder, fill } = sym 13 | 14 | // build template like `($1) ()()` 15 | let tempExceptFirst = placeholder 16 | for (let i = 2; i <= fill.length; i++) 17 | tempExceptFirst = tempExceptFirst.replace(`$${i}`, '') 18 | 19 | // remove the first dollar 20 | const temp = tempExceptFirst.replace('$1', '') 21 | if (!sel) { 22 | // No selection, then place the cursor at `$1`. 23 | const cur = editor.getCursor() 24 | const placeholder_a_pos = placeholder.indexOf('$1') 25 | const spacesBefore$1 = 26 | placeholder 27 | .substring(0, placeholder_a_pos) 28 | .match(/(\$\d+?)/g) 29 | ?.join('').length || 0 30 | editor.replaceSelection(am + temp) 31 | editor.setCursor({ 32 | line: cur.line, 33 | ch: cur.ch + am.length + placeholder_a_pos - spacesBefore$1, 34 | }) 35 | } else { 36 | // There is a selection, then replace `$1` with the selection, and put the cursor at `$2`. 37 | const placeholder_b_pos = placeholder.indexOf('$2') 38 | const cur = editor.getCursor('to') 39 | editor.replaceSelection(am + tempExceptFirst.replace('$1', sel)) 40 | if (placeholder_b_pos !== -1) { 41 | // Calculate how many `(\$\d+)`s are before `$2`, 42 | // then we should move the cursor to the location of `$2`. 43 | // This code is specially for `pp` and `dd` syntax sugar, which covers common cases. 44 | /** 45 | * abc 46 | * ^ cursor here 47 | * 48 | * pp ^$3 ($1)($2) 49 | * ^^ $spacesBefore$2 = 2 50 | * 51 | * pp ^ (abc)() 52 | * ^^ cursor should be here 53 | */ 54 | const $before$2 = placeholder 55 | .substring(0, placeholder_b_pos) 56 | .match(/(\$\d+?)/g) 57 | const $spacesBefore$2 = $before$2?.join('').length || 0 58 | // if $1 is located after $2, then the cursor should move back 59 | /** 60 | * abc 61 | * ^ cursor here 62 | * 63 | * color($2)($1) 64 | * ^^ $2before$1 65 | * 66 | * color()(abc) 67 | * ^^ cursor should be here, it will be moved back the length of `abc` 68 | */ 69 | const $2before$1 = 70 | !$before$2 || !$before$2.includes('$1') ? sel.length : 0 71 | editor.setCursor({ 72 | line: cur.line, 73 | ch: 74 | cur.ch + 75 | am.length + 76 | placeholder_b_pos - 77 | $spacesBefore$2 - 78 | $2before$1, 79 | }) 80 | } else { 81 | editor.setCursor({ 82 | line: cur.line, 83 | ch: cur.ch + am.length + placeholder.length - 2, 84 | }) 85 | } 86 | } 87 | } else { 88 | editor.replaceSelection(am) 89 | } 90 | }) 91 | modal.open() 92 | } 93 | 94 | export { 95 | createModalInstance 96 | } 97 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | PLUGIN_NAME: obsidian-asciimath 10 | 11 | jobs: 12 | release-plugin: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - uses: pnpm/action-setup@v2.0.1 21 | name: Install pnpm 22 | id: pnpm-install 23 | with: 24 | version: 8 25 | 26 | - name: Get pnpm store directory 27 | id: pnpm-cache 28 | run: | 29 | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT 30 | 31 | - uses: actions/cache@v3 32 | name: Setup pnpm cache 33 | with: 34 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 35 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pnpm-store- 38 | 39 | - name: Install dependencies and build 40 | id: build 41 | run: | 42 | pnpm install 43 | pnpm run build 44 | mkdir ${{ env.PLUGIN_NAME }} 45 | cp main.js manifest.json ${{ env.PLUGIN_NAME }} 46 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 47 | ls 48 | echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT 49 | 50 | - name: Create Release 51 | id: create_release 52 | uses: actions/create-release@v1 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | VERSION: ${{ github.ref }} 56 | with: 57 | tag_name: ${{ github.ref }} 58 | release_name: ${{ github.ref }} 59 | draft: false 60 | prerelease: false 61 | 62 | - name: Upload zip file 63 | id: upload-zip 64 | uses: actions/upload-release-asset@v1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 70 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 71 | asset_content_type: application/zip 72 | 73 | - name: Upload main.js 74 | id: upload-main 75 | uses: actions/upload-release-asset@v1 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | with: 79 | upload_url: ${{ steps.create_release.outputs.upload_url }} 80 | asset_path: ./main.js 81 | asset_name: main.js 82 | asset_content_type: text/javascript 83 | 84 | - name: Upload manifest.json 85 | id: upload-manifest 86 | uses: actions/upload-release-asset@v1 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | with: 90 | upload_url: ${{ steps.create_release.outputs.upload_url }} 91 | asset_path: ./manifest.json 92 | asset_name: manifest.json 93 | asset_content_type: application/json 94 | 95 | - name: Upload styles.css 96 | id: upload-css 97 | uses: actions/upload-release-asset@v1 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | upload_url: ${{ steps.create_release.outputs.upload_url }} 102 | asset_path: ./styles.css 103 | asset_name: styles.css 104 | asset_content_type: text/css 105 | 106 | -------------------------------------------------------------------------------- /src/symbol-search/modal.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'obsidian' 2 | import { SuggestModal, finishRenderMath, renderMath } from 'obsidian' 3 | import type { AsciiMath } from 'asciimath-parser' 4 | import symbols from './symbols.json' 5 | 6 | // First is the AsciiMath symbol, second is LaTeX alternative 7 | // The third element is rendered as a preview 8 | 9 | type AsciiMathSymbol = 10 | | { am: string; tex: string; rendered?: string } 11 | | { 12 | am: string 13 | tex: string 14 | rendered?: string 15 | placeholder: string 16 | fill: Array 17 | } 18 | 19 | export class SymbolSearchModal extends SuggestModal { 20 | private sel: string 21 | private am: AsciiMath 22 | private callback: (sym: AsciiMathSymbol) => void 23 | private renderCount: number 24 | private renderMax: number 25 | 26 | constructor(app: App, sel: string, am: AsciiMath) { 27 | super(app) 28 | this.sel = sel 29 | this.am = am 30 | this.renderCount = 0 31 | this.renderMax = 0 32 | } 33 | 34 | // Returns all available suggestions. 35 | getSuggestions(query: string): AsciiMathSymbol[] { 36 | query = query.toLowerCase() 37 | const suggestions = symbols.filter((sym) => 38 | [sym.am, sym.tex].some((v) => v.toLocaleLowerCase().includes(query)), 39 | ) as AsciiMathSymbol[] 40 | // to avoid calling `finishRenderMath` too many times 41 | this.renderCount = 0 42 | this.renderMax = Math.min(suggestions.length, 100) 43 | 44 | return suggestions 45 | } 46 | 47 | // Renders each suggestion item. 48 | renderSuggestion(sym: AsciiMathSymbol, el: HTMLElement) { 49 | this.renderCount++ 50 | let { am, tex, rendered } = sym 51 | el.classList.add('__asciimath-symbol-search-result') 52 | 53 | const text = el.createDiv() 54 | const amLine = text.createDiv() 55 | amLine.createSpan({ text: am }) 56 | 57 | let toBeRendered = typeof rendered !== 'undefined' ? rendered : tex 58 | 59 | if ('placeholder' in sym) { 60 | const { placeholder, fill } = sym 61 | // build template like `^(a)_(b)` 62 | let template = placeholder 63 | if (this.sel) { 64 | // if `am` is embedded LaTeX, then just render the selection as LaTeX, otherwise render the parsed tex. 65 | const selToTex = 66 | am === 'tex' || am === 'text' 67 | ? this.sel 68 | : this.am.toTex(this.sel, { display: false }) 69 | template = template.replace('$1', this.sel) 70 | 71 | tex = tex.replace('$1', selToTex) 72 | toBeRendered = toBeRendered.replace('$1', selToTex) 73 | } 74 | 75 | fill.forEach((x, i) => { 76 | template = template.replace(`$${i + 1}`, x) 77 | toBeRendered = toBeRendered.replaceAll(`$${i + 1}`, x) 78 | tex = tex.replaceAll(`$${i + 1}`, x) 79 | }) 80 | amLine.createSpan({ 81 | text: ` ${template}`, 82 | cls: '__asciimath-symbol-search-placeholder', 83 | }) 84 | } 85 | 86 | text.createEl('small', { text: `LaTeX alternative: ${tex}` }) 87 | 88 | el.createDiv('__asciimath-symbol-search-preview math', (el) => { 89 | if (am === 'tex') toBeRendered = `tex"${toBeRendered}"` 90 | 91 | el.innerHTML = ` 92 | 93 | ${renderMath(toBeRendered, true).innerHTML} 94 | 95 | ` 96 | // flush math css when all suggestions are created 97 | if (this.renderCount >= this.renderMax) finishRenderMath() 98 | }) 99 | } 100 | 101 | onSelected(cb: (sym: AsciiMathSymbol) => void) { 102 | this.callback = cb 103 | } 104 | 105 | // Perform action on the selected suggestion. 106 | onChooseSuggestion(sym: AsciiMathSymbol) { 107 | this.callback(sym) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { AsciiMath } from 'asciimath-parser' 2 | import type { App } from 'obsidian' 3 | import { Notice, PluginSettingTab, Setting, debounce } from 'obsidian' 4 | import type AsciiMathPlugin from './main' 5 | 6 | type RuleType = string[][] 7 | 8 | export interface AsciiMathSettings { 9 | blockPrefix: string[] 10 | replaceMathBlock: boolean 11 | disableDeprecationWarning: boolean 12 | customSymbols: RuleType 13 | } 14 | 15 | export class AsciiMathSettingTab extends PluginSettingTab { 16 | plugin: AsciiMathPlugin 17 | 18 | constructor(app: App, plugin: AsciiMathPlugin) { 19 | super(app, plugin) 20 | this.plugin = plugin 21 | } 22 | 23 | display(): void { 24 | const { containerEl } = this 25 | 26 | containerEl.empty() 27 | 28 | containerEl.createEl('h2', { text: 'Settings for asciimath' }) 29 | 30 | new Setting(containerEl) 31 | .setName('Code block prefix aliases') 32 | .setDesc('Seperate different aliases with comma.') 33 | .addText((text) => 34 | text 35 | .setPlaceholder('asciimath, am') 36 | .setValue(this.plugin.settings.blockPrefix.join(', ')) 37 | .onChange( 38 | debounce((value) => { 39 | this.plugin.settings.blockPrefix = value 40 | .split(',') 41 | .map((s) => s.trim()) 42 | .filter(Boolean) 43 | }, 1000), 44 | ), 45 | ) 46 | 47 | new Setting(containerEl) 48 | .setName('Replace math blocks') 49 | .setDesc( 50 | 'Enable this if you want to use AsciiMath but keep using default math blocks (dollar-sign blocks). This will not affect your previous notes that are written in LaTeX because the plugin will check which syntax to use before drawing the math.', 51 | ) 52 | .addToggle((toggle) => { 53 | toggle.setValue(this.plugin.settings.replaceMathBlock).onChange((v) => { 54 | this.plugin.settings.replaceMathBlock = v 55 | this.plugin.setupMathBlockRendering() 56 | }) 57 | }) 58 | 59 | new Setting(containerEl) 60 | .setName('Custom symbols') 61 | .setDesc( 62 | 'Transforms custom symbols into LaTeX symbols. One row for each rule.', 63 | ) 64 | .addTextArea((text) => { 65 | const el = text 66 | .setPlaceholder( 67 | 'symbol1, \\LaTeXSymbol1\nsymbol2, \\LaTeXSymbol2\n...', 68 | ) 69 | .setValue( 70 | this.plugin.settings.customSymbols 71 | .map((r) => r.join(', ')) 72 | .join('\n'), 73 | ) 74 | .onChange( 75 | debounce((value) => { 76 | this.plugin.settings.customSymbols = value 77 | .split('\n') 78 | .map((r) => 79 | r 80 | .split(',') 81 | .map((s) => s.trim()) 82 | .filter(Boolean), 83 | ) 84 | .filter((l) => l.length) 85 | }, 1000), 86 | ) 87 | el.inputEl.addClass('__asciimath_settings_custom-symbols') 88 | }) 89 | 90 | new Setting(containerEl) 91 | .setName("Don't forget to save and reload settings →") 92 | .addButton((btn) => 93 | btn.setButtonText('Save').onClick(async () => { 94 | const valid = validateSettings(this.plugin.settings) 95 | if (!valid.isValid) { 96 | new Notice(valid.message) 97 | return 98 | } 99 | await this.plugin.saveSettings() 100 | await this.plugin.loadSettings() 101 | this.plugin.settings.blockPrefix.forEach((prefix: string) => { 102 | if (!this.plugin.existPrefixes.includes(prefix)) 103 | this.plugin.registerAsciiMathCodeBlock(prefix) 104 | }) 105 | this.plugin.AM = new AsciiMath({ 106 | symbols: this.plugin.calcSymbols(), 107 | }) 108 | new Notice('Asciimath settings reloaded successfully!') 109 | }), 110 | ) 111 | } 112 | } 113 | 114 | function validateSettings(settings: AsciiMathSettings): { 115 | isValid: boolean 116 | message: string 117 | } { 118 | if (settings.blockPrefix.length < 1) { 119 | return { 120 | isValid: false, 121 | message: 'You should add at least 1 block prefix!', 122 | } 123 | } 124 | const { customSymbols } = settings 125 | if (customSymbols.find((pair) => pair.length !== 2)) { 126 | return { 127 | isValid: false, 128 | message: 'Custom rule should be two string split with a comma!', 129 | } 130 | } 131 | 132 | return { 133 | isValid: true, 134 | message: 'OK', 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MarkdownPostProcessor, 3 | MarkdownPostProcessorContext, 4 | } from 'obsidian' 5 | import { 6 | MarkdownPreviewRenderer, 7 | Notice, 8 | Plugin, 9 | finishRenderMath, 10 | loadMathJax, 11 | renderMath, 12 | } from 'obsidian' 13 | 14 | import { AsciiMath, TokenTypes } from 'asciimath-parser' 15 | import { AsciiMathSettingTab, type AsciiMathSettings } from './settings' 16 | import { isLatexCode } from './utils' 17 | import { createModalInstance } from './symbol-search/modal-instance' 18 | import { initCommands } from './commands' 19 | 20 | const DEFAULT_SETTINGS: AsciiMathSettings = { 21 | blockPrefix: ['asciimath', 'am'], 22 | disableDeprecationWarning: false, 23 | replaceMathBlock: true, 24 | customSymbols: [], 25 | } 26 | 27 | export default class AsciiMathPlugin extends Plugin { 28 | settings: AsciiMathSettings 29 | existPrefixes: string[] = [] 30 | tex2chtml: (source: string, r: { display: boolean }) => any 31 | 32 | postProcessors: Map = new Map() 33 | 34 | AM: AsciiMath 35 | 36 | calcSymbols() { 37 | return this.settings.customSymbols.map(([k, v]) => { 38 | return [k, { type: TokenTypes.Const, tex: v }] as [ 39 | string, 40 | { type: TokenTypes; tex: string }, 41 | ] 42 | }) 43 | } 44 | 45 | onunload() { 46 | // eslint-disable-next-line no-console 47 | console.log('Obsidian asciimath unloaded') 48 | 49 | // Resetting mathjax rendering function to default 50 | MathJax.tex2chtml = this.tex2chtml 51 | 52 | // this.postProcessors = null 53 | this.unregister() 54 | } 55 | 56 | unregister() { 57 | this.postProcessors.forEach((value) => { 58 | MarkdownPreviewRenderer.unregisterPostProcessor(value) 59 | }) 60 | this.postProcessors.clear() 61 | } 62 | 63 | async loadSettings() { 64 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) 65 | } 66 | 67 | async saveSettings() { 68 | await this.saveData(this.settings) 69 | } 70 | 71 | // This will hijack function that is used by obsidian for rendering math. 72 | setupMathBlockRendering() { 73 | this.tex2chtml = MathJax.tex2chtml 74 | 75 | if (this.settings.replaceMathBlock) 76 | MathJax.tex2chtml = (s, r) => this.convertMathCode(s, r) 77 | else MathJax.tex2chtml = this.tex2chtml 78 | } 79 | 80 | // Converts AsciiMath (if used in the current block) to tex and then calls default tex2chtml function 81 | convertMathCode(source: string, r: { display: boolean }) { 82 | if (this.settings.replaceMathBlock && !isLatexCode(source)) 83 | source = this.AM.toTex(source) 84 | 85 | return this.tex2chtml(source, r) 86 | } 87 | 88 | registerAsciiMathCodeBlock(prefix: string) { 89 | this.postProcessors.set( 90 | prefix, 91 | this.registerMarkdownCodeBlockProcessor(prefix, (src, el, ctx) => 92 | this.postProcessor(prefix, src, el, ctx), 93 | ), 94 | ) 95 | } 96 | 97 | postProcessor( 98 | _prefix: string, 99 | src: string, 100 | el: HTMLElement, 101 | _?: MarkdownPostProcessorContext, 102 | ) { 103 | const mathEl = renderMath(src, true) 104 | el.appendChild(mathEl) 105 | finishRenderMath() 106 | } 107 | 108 | async onload() { 109 | await this.loadSettings() 110 | 111 | await loadMathJax() 112 | 113 | this.AM = new AsciiMath({ 114 | symbols: this.calcSymbols(), 115 | }) 116 | 117 | if (!MathJax) { 118 | console.warn('MathJax was not defined despite loading it.') 119 | new Notice('Error: MathJax was not defined despite loading it!') 120 | return 121 | } 122 | 123 | initCommands(this) 124 | 125 | // TODO: Should be removed in favor of default math blocks 126 | this.postProcessors = new Map() 127 | // register code block processors 128 | this.app.workspace.onLayoutReady(async () => { 129 | this.settings.blockPrefix.forEach((prefix) => { 130 | // console.log(prefix) 131 | this.registerAsciiMathCodeBlock(prefix) 132 | this.existPrefixes.push(prefix) 133 | }) 134 | }) 135 | // register processor in live preview mode 136 | // this.registerEditorExtension([inlinePlugin(this)]) 137 | // register processor in reading mode 138 | // this.registerMarkdownPostProcessor(this.postProcessorInline.bind(this)) 139 | 140 | // This will setup integration with obsidian dollar-sign math blocks so plugin can render them 141 | this.setupMathBlockRendering() 142 | 143 | // This adds a settings tab so the user can configure various aspects of the plugin 144 | this.addSettingTab(new AsciiMathSettingTab(this.app, this)) 145 | 146 | // eslint-disable-next-line no-console 147 | console.log('Obsidian asciimath loaded') 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Asciimath 2 | A handy plugin that integrates with default Obsidian math, bringing AsciiMath power to your vault. 3 | 4 | ## Table of contents 5 | - [Installation](#install) 6 | - [Advantages](#advantages) 7 | - [Features](#features) 8 | - [Dollar-sign math integration](#dollar-sign-math-integration) 9 | - [Code-block math](#code-block-math) 10 | - [Commands](#commands) 11 | - [Search & Insert AsciiMath symbol](#select-and-insert-asciimath-symbol) 12 | - [Insert math code block](#insert-asciimath-codeblock) 13 | - [Convert AsciiMath to LaTeX](#convertion-to-latex) 14 | - [Migration commands](#migration-commands) 15 | - [Support](#support) 16 | - [Development](#development) 17 | 18 | > [!IMPORTANT] 19 | > Follow link below if you keep getting depreciation warning from the plugin 20 | 21 | [Updating notes created with plugin version 0.6.3 and lower](#updating-old-notes) 22 | 23 | # Install 24 | 25 | - (Recommended) Goto Obsidian plugin market, search for `obsidian-asciimath` and install it. 26 | - (Manually) Goto the [release page](https://github.com/widcardw/obsidian-asciimath/releases), download the zip, unzip it and add it to your plugins folder. 27 | 28 | # Advantages 29 | [AsciiMath](http://asciimath.org) has simpler syntax compared to LaTeX, which is the default math language for Obsidian. With this plugin, you can easily bring power of AsciiMath to your vault, as well as speed up process of math editing. 30 | 31 | > [!NOTE] 32 | > Some of the rules are not exactly consistent with http://asciimath.org, especially the matrix. For more information, please refer to https://asciimath.widcard.win. 33 | 34 | 35 | # Features 36 | Obsidian AsciiMath ships with set of features that will help to get more productive when writing math in Obsidian. 37 | 38 | ### Dollar-sign math integration 39 | This plugin integrates with Obsidian's dollar-sign math blocks. It is fully compatible with your previous notes written in LaTeX. 40 | 41 | Just start writing AsciiMath as you would write LaTeX formulas and enjoy simpler syntax. 42 | 43 | #### Examples 44 | Inline math: 45 | ```text 46 | $lim_(a->oo) a_n = 0$ 47 | ``` 48 | ![](screenshots/inline.png) 49 | 50 | Display-style math: 51 | ~~~text 52 | $$ 53 | sum_(n=1)^oo 1/n^2 = pi^2/6 54 | $$ 55 | ~~~ 56 | 57 | ![](screenshots/codeblock.png) 58 | 59 | ### Code-block math 60 | If you do not want to use dollar-sign math blocks, you can use code-block for AsciiMath. 61 | 62 | This will be rendered the same as double dollar sign blocks. 63 | ~~~text 64 | ```am (or asciimath) 65 | sum_(n=1)^oo 1/n^2 = pi^2/6 66 | ``` 67 | ~~~ 68 | > [!NOTE] 69 | > Inline math is only available via dollar-sign blocks. 70 | 71 | ### Commands 72 | The plugin ships with some handy commands for you: 73 | 74 | #### Select and insert AsciiMath symbol 75 | ##### How to use 76 | 1. Hit `Ctrl + P` 77 | 2. Search for `Insert AsciiMath symbol` 78 | 3. Hit Enter 79 | 4. Search for desired symbol 80 | 5. Select it & hit enter. 81 | 82 | > [!Note] 83 | > Pro tip: You can assign a hotkey for this command to do this faster. My personal preference is `Ctrl + M` 84 | 85 | #### Insert asciimath codeblock. 86 | This will insert [code block](#code-block-math) around current selection. 87 | 88 | #### Convertion to LaTeX 89 | - Convert AsciiMath into LaTeX in current file. 90 | - Convert AsciiMath into LaTeX in the entire vault. 91 | 92 | #### Notes update commands 93 | - Update asciimath blocks to new syntax in current file. 94 | - Update asciimath blocks to new syntax in the entire vault. 95 | 96 |
97 | Click to see showcase for all of the commands above 98 | 99 | ![](./screenshots/out.gif) 100 |
101 | 102 | ### Updating old notes 103 | 104 | In previous versions of the plugin users had to use special syntax for inline math. This feature is deprecated and will be removed in the future. 105 | > Note: default code blocks with three backticks will be supported as usual. 106 | 107 | New syntax integrates with default Obsidian [math blocks](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Math) (dollar-sign blocks), which fully compatible when using both LaTeX and AsciiMath. 108 | 109 | From now on, you should create inline blocks like this: 110 | ```diff 111 | ++ $$ 112 | instead of 113 | -- `$ $` 114 | ``` 115 | > For more on new syntax, refer to [this section](#dollar-sign-math-integration) 116 | 117 | To prepare your notes for the newer version of the plugin, you must convert your old AsciiMath notes to new syntax. This can be easily done with plugin commands: 118 | - Hit `Ctrl + P` to open up command pallet. 119 | - Search for "Update old AsciiMath". Choose one of the two available commands by the "obsidian-asciimath" plugin. 120 | - Confirm the changes. 121 | - You're good to go! 122 | 123 | 124 | # Development 125 | 126 | ```sh 127 | git clone git@github.com:widcardw/obsidian-asciimath.git 128 | pnpm i 129 | pnpm run dev 130 | ``` 131 | 132 | # Support 133 | 134 | During my use, this plugin often causes Obsidian rendering problems (especially in live preview mode). If you are interested in helping me to improve it, please feel free to give suggestions on github [issues](https://github.com/widcardw/obsidian-asciimath/issues) or [pull requests](https://github.com/widcardw/obsidian-asciimath/pulls). Thank you! 135 | -------------------------------------------------------------------------------- /src/convertion.ts: -------------------------------------------------------------------------------- 1 | import { EditorChange, MarkdownView, Notice, Plugin, Pos, TFile } from "obsidian"; 2 | import { FormulaMatch, isLatexCode } from "./utils"; 3 | import type AsciiMathPlugin from "./main"; 4 | import { toTex } from "./utils"; 5 | import { ConfirmModal } from "./confirm-modal"; 6 | 7 | async function convertAsciiMathInFile(plugin: AsciiMathPlugin, file: TFile, display: boolean) { 8 | // get the editor instance 9 | const view = plugin.app.workspace.getActiveViewOfType(MarkdownView); 10 | if (!view) return { block: 0, inline: 0 }; 11 | 12 | const { editor } = view; 13 | 14 | // get the parsed block structure of Obsidian 15 | const cache = plugin.app.metadataCache.getFileCache(file); 16 | if (!cache || !cache.sections) return { block: 0, inline: 0 }; 17 | 18 | // get all the math blocks and inline math formulas 19 | const formulaBlocks: Array<{ content: string, position: Pos, isBlock: boolean }> = []; 20 | 21 | if (cache.sections) { 22 | for (const section of cache.sections) { 23 | const { start, end } = section.position 24 | if (section.type === 'math' || section.type === 'code') { 25 | let content = editor.getRange({ line: start.line, ch: start.col }, { line: end.line, ch: end.col }) 26 | if (section.type === 'math') { 27 | content = content.replace(/^\$\$\s*/, '').replace(/\s*\$\$$/, '') 28 | } 29 | if (section.type === 'code') { 30 | const blockReg = new RegExp( 31 | `((\`|~){3,})(${plugin.settings.blockPrefix.join('|')})([\\s\\S]*?)\\n\\1`, 32 | 'm', 33 | ) 34 | const match = content.match(blockReg) 35 | if (match) content = match[4].trim() 36 | else continue 37 | } 38 | 39 | formulaBlocks.push({ 40 | content, 41 | position: section.position, 42 | isBlock: true, 43 | }) 44 | } else { 45 | let content = editor.getRange({ line: start.line, ch: start.col }, { line: end.line, ch: end.col }) 46 | const inlineMathRegex = /(? { 73 | const { start, end } = block.position 74 | const res = toTex(plugin.AM, block.content, display) 75 | 76 | const replacement = block.isBlock 77 | ? `$$\n${res}\n$$` 78 | : `$${res}$` 79 | 80 | changes.push({ 81 | from: { line: start.line, ch: start.col }, 82 | to: { line: end.line, ch: end.col }, 83 | text: replacement, 84 | }) 85 | }) 86 | editor.transaction({ changes }) 87 | new Notice(`Conversion completed: ${formulaBlocks.length} formulas processed`) 88 | } 89 | return { 90 | block: formulaBlocks.filter(x => x.isBlock).length, 91 | inline: formulaBlocks.filter(x => !x.isBlock).length, 92 | } 93 | } 94 | 95 | async function extractFormulasInFile(plugin: AsciiMathPlugin, file: TFile) { 96 | const content = await plugin.app.vault.read(file); 97 | const formulas: FormulaMatch[] = []; 98 | const codeRanges: { start: number; end: number; isAm: boolean }[] = []; 99 | const codeBlockRegex = /(^|\n)(```|~~~)[\s\S]*?\2/g; 100 | const amCodeBlockRegex = new RegExp( 101 | `([\`~]{3,})(${plugin.settings.blockPrefix.join('|')})([\\s\\S]*?)\\n\\1`, 'm' 102 | ) 103 | const inlineCodeRegex = /`[^`\n]*`/g; 104 | 105 | // extract code blocks 106 | for (const match of content.matchAll(codeBlockRegex)) { 107 | const am = match[0].match(amCodeBlockRegex) 108 | codeRanges.push({ 109 | start: match.index!, 110 | end: match.index! + match[0].length, 111 | isAm: am !== null, 112 | }); 113 | if (am) { 114 | const amCode = am[3] 115 | let start = match.index! 116 | if (match[1] === '\n') { 117 | start += 1 118 | } 119 | formulas.push({ 120 | type: 'block', 121 | start, 122 | end: match.index! + match[0].length, 123 | content: amCode, 124 | }) 125 | } 126 | } 127 | 128 | // record inline code positions 129 | for (const match of content.matchAll(inlineCodeRegex)) { 130 | codeRanges.push({ 131 | start: match.index!, 132 | end: match.index! + match[0].length, 133 | isAm: false, 134 | }); 135 | } 136 | 137 | // 2. extract all formulas (ignore code blocks) 138 | // extract single $ 139 | const inlineRegex = /(? { 168 | return !codeRanges.some(range => 169 | formula.start >= range.start 170 | && formula.end <= range.end 171 | && !range.isAm 172 | ); 173 | }); 174 | } 175 | 176 | async function replaceFormulasInFile(plugin: AsciiMathPlugin, file: TFile, enableDisplayMode: boolean) { 177 | const content = await plugin.app.vault.read(file); 178 | const formulas = await extractFormulasInFile(plugin, file); 179 | 180 | console.log({ formulas }) 181 | 182 | // sort formulas by start position in reverse order (to avoid index error) 183 | formulas.sort((a, b) => b.start - a.start); 184 | 185 | let newContent = content; 186 | 187 | const convertedCnt = { block: 0, inline: 0 } 188 | 189 | for (const formula of formulas) { 190 | if (isLatexCode(formula.content)) { 191 | continue 192 | } 193 | const converted = toTex(plugin.AM, formula.content.trim(), enableDisplayMode); 194 | const replacement = formula.type === "inline" 195 | ? `$${converted}$` 196 | : `$$${converted}$$`; 197 | 198 | newContent = 199 | newContent.substring(0, formula.start) + 200 | replacement + 201 | newContent.substring(formula.end); 202 | 203 | convertedCnt[formula.type] += 1 204 | } 205 | 206 | await plugin.app.vault.modify(file, newContent); 207 | return convertedCnt 208 | } 209 | 210 | function actionConvertActiveFile(plugin: AsciiMathPlugin, message: string) { 211 | return async () => 212 | new ConfirmModal(plugin.app) 213 | .setMessage(message) 214 | .setEnableDisplayMode(false) 215 | .onConfirm(async (displayMode) => { 216 | const file = plugin.app.workspace.getActiveFile() 217 | if (!file) { 218 | new Notice('No active file found.') 219 | return 220 | } 221 | await convertAsciiMathInFile(plugin, file, displayMode) 222 | }) 223 | .open() 224 | } 225 | 226 | function actionConvertEntireVault(plugin: AsciiMathPlugin, message: string) { 227 | return async () => 228 | new ConfirmModal(plugin.app) 229 | .setMessage(message) 230 | .setEnableDisplayMode(false) 231 | .onConfirm(async (displayMode) => { 232 | // convert all the asciimath formulas in vault 233 | const allConvertionRes = await Promise.all( 234 | plugin.app.vault.getMarkdownFiles().map(async (f) => { 235 | const convertionRes = await replaceFormulasInFile(plugin, f, displayMode) 236 | return { 237 | ...convertionRes, 238 | hasAsciimath: convertionRes.block || convertionRes.inline, 239 | } 240 | }), 241 | ) 242 | // calculate number of blocks and inline ones that converted in files 243 | const lo = { block: 0, inline: 0, fileNum: 0 } 244 | allConvertionRes.forEach((res) => { 245 | if (res.hasAsciimath) { 246 | lo.block += res.block 247 | lo.inline += res.inline 248 | lo.fileNum += 1 249 | } 250 | }) 251 | 252 | new Notice( 253 | `Converted ${lo.block} blocks and ${lo.inline} inline formulas in ${lo.fileNum} file${lo.fileNum > 1 ? 's' : '' 254 | }.`, 255 | ) 256 | }) 257 | .open() 258 | } 259 | 260 | export { 261 | convertAsciiMathInFile, 262 | extractFormulasInFile, 263 | replaceFormulasInFile, 264 | actionConvertActiveFile, 265 | actionConvertEntireVault, 266 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@biomejs/biome': 12 | specifier: ^2.1.4 13 | version: 2.1.4 14 | '@codemirror/language': 15 | specifier: 6.3.0 16 | version: 6.3.0 17 | '@codemirror/state': 18 | specifier: 6.1.2 19 | version: 6.1.2 20 | '@codemirror/view': 21 | specifier: 6.4.1 22 | version: 6.4.1 23 | '@types/node': 24 | specifier: ^20.19.10 25 | version: 20.19.10 26 | asciimath-parser: 27 | specifier: ^0.6.10 28 | version: 0.6.10 29 | builtin-modules: 30 | specifier: 3.3.0 31 | version: 3.3.0 32 | esbuild: 33 | specifier: 0.14.47 34 | version: 0.14.47 35 | obsidian: 36 | specifier: latest 37 | version: 1.4.11(@codemirror/state@6.1.2)(@codemirror/view@6.4.1) 38 | ts-dedent: 39 | specifier: ^2.2.0 40 | version: 2.2.0 41 | tslib: 42 | specifier: 2.4.0 43 | version: 2.4.0 44 | typescript: 45 | specifier: 4.7.4 46 | version: 4.7.4 47 | 48 | packages: 49 | 50 | '@biomejs/biome@2.1.4': 51 | resolution: {integrity: sha512-QWlrqyxsU0FCebuMnkvBIkxvPqH89afiJzjMl+z67ybutse590jgeaFdDurE9XYtzpjRGTI1tlUZPGWmbKsElA==} 52 | engines: {node: '>=14.21.3'} 53 | hasBin: true 54 | 55 | '@biomejs/cli-darwin-arm64@2.1.4': 56 | resolution: {integrity: sha512-sCrNENE74I9MV090Wq/9Dg7EhPudx3+5OiSoQOkIe3DLPzFARuL1dOwCWhKCpA3I5RHmbrsbNSRfZwCabwd8Qg==} 57 | engines: {node: '>=14.21.3'} 58 | cpu: [arm64] 59 | os: [darwin] 60 | 61 | '@biomejs/cli-darwin-x64@2.1.4': 62 | resolution: {integrity: sha512-gOEICJbTCy6iruBywBDcG4X5rHMbqCPs3clh3UQ+hRKlgvJTk4NHWQAyHOXvaLe+AxD1/TNX1jbZeffBJzcrOw==} 63 | engines: {node: '>=14.21.3'} 64 | cpu: [x64] 65 | os: [darwin] 66 | 67 | '@biomejs/cli-linux-arm64-musl@2.1.4': 68 | resolution: {integrity: sha512-nYr7H0CyAJPaLupFE2cH16KZmRC5Z9PEftiA2vWxk+CsFkPZQ6dBRdcC6RuS+zJlPc/JOd8xw3uCCt9Pv41WvQ==} 69 | engines: {node: '>=14.21.3'} 70 | cpu: [arm64] 71 | os: [linux] 72 | 73 | '@biomejs/cli-linux-arm64@2.1.4': 74 | resolution: {integrity: sha512-juhEkdkKR4nbUi5k/KRp1ocGPNWLgFRD4NrHZSveYrD6i98pyvuzmS9yFYgOZa5JhaVqo0HPnci0+YuzSwT2fw==} 75 | engines: {node: '>=14.21.3'} 76 | cpu: [arm64] 77 | os: [linux] 78 | 79 | '@biomejs/cli-linux-x64-musl@2.1.4': 80 | resolution: {integrity: sha512-lvwvb2SQQHctHUKvBKptR6PLFCM7JfRjpCCrDaTmvB7EeZ5/dQJPhTYBf36BE/B4CRWR2ZiBLRYhK7hhXBCZAg==} 81 | engines: {node: '>=14.21.3'} 82 | cpu: [x64] 83 | os: [linux] 84 | 85 | '@biomejs/cli-linux-x64@2.1.4': 86 | resolution: {integrity: sha512-Eoy9ycbhpJVYuR+LskV9s3uyaIkp89+qqgqhGQsWnp/I02Uqg2fXFblHJOpGZR8AxdB9ADy87oFVxn9MpFKUrw==} 87 | engines: {node: '>=14.21.3'} 88 | cpu: [x64] 89 | os: [linux] 90 | 91 | '@biomejs/cli-win32-arm64@2.1.4': 92 | resolution: {integrity: sha512-3WRYte7orvyi6TRfIZkDN9Jzoogbv+gSvR+b9VOXUg1We1XrjBg6WljADeVEaKTvOcpVdH0a90TwyOQ6ue4fGw==} 93 | engines: {node: '>=14.21.3'} 94 | cpu: [arm64] 95 | os: [win32] 96 | 97 | '@biomejs/cli-win32-x64@2.1.4': 98 | resolution: {integrity: sha512-tBc+W7anBPSFXGAoQW+f/+svkpt8/uXfRwDzN1DvnatkRMt16KIYpEi/iw8u9GahJlFv98kgHcIrSsZHZTR0sw==} 99 | engines: {node: '>=14.21.3'} 100 | cpu: [x64] 101 | os: [win32] 102 | 103 | '@codemirror/language@6.3.0': 104 | resolution: {integrity: sha512-6jOE5DEt6sKD46SXhn3xPbBehn+l48ACcA6Uxs2k+E2YNH9XGF5WdGMTYr2DlggfK4h0QZBK6zEb5S7lkTriWA==} 105 | 106 | '@codemirror/state@6.1.2': 107 | resolution: {integrity: sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==} 108 | 109 | '@codemirror/view@6.4.1': 110 | resolution: {integrity: sha512-QdBpD6E5HYx6YFXXhqwrRyQ83w7CxWZnchM4QpWBVkkmV7/oJT8N+yz2KAi2iRaLObc/aOf7C2RCQTO2yswF8A==} 111 | 112 | '@lezer/common@1.2.1': 113 | resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==} 114 | 115 | '@lezer/highlight@1.1.2': 116 | resolution: {integrity: sha512-CAun1WR1glxG9ZdOokTZwXbcwB7PXkIEyZRUMFBVwSrhTcogWq634/ByNImrkUnQhjju6xsIaOBIxvcRJtplXQ==} 117 | 118 | '@lezer/lr@1.2.4': 119 | resolution: {integrity: sha512-L/52/oMJBFXXx8qBYF4UgktLP2geQ/qn5Fd8+5L/mqlLLCB9+qdKktFAtejd9FdFMaFx6lrP5rmLz4sN3Kplcg==} 120 | 121 | '@types/codemirror@5.60.8': 122 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==} 123 | 124 | '@types/estree@1.0.0': 125 | resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} 126 | 127 | '@types/node@20.19.10': 128 | resolution: {integrity: sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==} 129 | 130 | '@types/tern@0.23.4': 131 | resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==} 132 | 133 | asciimath-parser@0.6.10: 134 | resolution: {integrity: sha512-U13okp+jiMLmDELT0DQ5sUX090E3mbOMDDHPSiozwevn3u+Y+Zr3Q90y3rBqxrJJJMRmnDVYT00a3MpoyYlabw==} 135 | 136 | builtin-modules@3.3.0: 137 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 138 | engines: {node: '>=6'} 139 | 140 | esbuild-android-64@0.14.47: 141 | resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} 142 | engines: {node: '>=12'} 143 | cpu: [x64] 144 | os: [android] 145 | 146 | esbuild-android-arm64@0.14.47: 147 | resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} 148 | engines: {node: '>=12'} 149 | cpu: [arm64] 150 | os: [android] 151 | 152 | esbuild-darwin-64@0.14.47: 153 | resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} 154 | engines: {node: '>=12'} 155 | cpu: [x64] 156 | os: [darwin] 157 | 158 | esbuild-darwin-arm64@0.14.47: 159 | resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} 160 | engines: {node: '>=12'} 161 | cpu: [arm64] 162 | os: [darwin] 163 | 164 | esbuild-freebsd-64@0.14.47: 165 | resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} 166 | engines: {node: '>=12'} 167 | cpu: [x64] 168 | os: [freebsd] 169 | 170 | esbuild-freebsd-arm64@0.14.47: 171 | resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} 172 | engines: {node: '>=12'} 173 | cpu: [arm64] 174 | os: [freebsd] 175 | 176 | esbuild-linux-32@0.14.47: 177 | resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} 178 | engines: {node: '>=12'} 179 | cpu: [ia32] 180 | os: [linux] 181 | 182 | esbuild-linux-64@0.14.47: 183 | resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} 184 | engines: {node: '>=12'} 185 | cpu: [x64] 186 | os: [linux] 187 | 188 | esbuild-linux-arm64@0.14.47: 189 | resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} 190 | engines: {node: '>=12'} 191 | cpu: [arm64] 192 | os: [linux] 193 | 194 | esbuild-linux-arm@0.14.47: 195 | resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} 196 | engines: {node: '>=12'} 197 | cpu: [arm] 198 | os: [linux] 199 | 200 | esbuild-linux-mips64le@0.14.47: 201 | resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} 202 | engines: {node: '>=12'} 203 | cpu: [mips64el] 204 | os: [linux] 205 | 206 | esbuild-linux-ppc64le@0.14.47: 207 | resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} 208 | engines: {node: '>=12'} 209 | cpu: [ppc64] 210 | os: [linux] 211 | 212 | esbuild-linux-riscv64@0.14.47: 213 | resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} 214 | engines: {node: '>=12'} 215 | cpu: [riscv64] 216 | os: [linux] 217 | 218 | esbuild-linux-s390x@0.14.47: 219 | resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} 220 | engines: {node: '>=12'} 221 | cpu: [s390x] 222 | os: [linux] 223 | 224 | esbuild-netbsd-64@0.14.47: 225 | resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} 226 | engines: {node: '>=12'} 227 | cpu: [x64] 228 | os: [netbsd] 229 | 230 | esbuild-openbsd-64@0.14.47: 231 | resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} 232 | engines: {node: '>=12'} 233 | cpu: [x64] 234 | os: [openbsd] 235 | 236 | esbuild-sunos-64@0.14.47: 237 | resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} 238 | engines: {node: '>=12'} 239 | cpu: [x64] 240 | os: [sunos] 241 | 242 | esbuild-windows-32@0.14.47: 243 | resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} 244 | engines: {node: '>=12'} 245 | cpu: [ia32] 246 | os: [win32] 247 | 248 | esbuild-windows-64@0.14.47: 249 | resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} 250 | engines: {node: '>=12'} 251 | cpu: [x64] 252 | os: [win32] 253 | 254 | esbuild-windows-arm64@0.14.47: 255 | resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} 256 | engines: {node: '>=12'} 257 | cpu: [arm64] 258 | os: [win32] 259 | 260 | esbuild@0.14.47: 261 | resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} 262 | engines: {node: '>=12'} 263 | hasBin: true 264 | 265 | moment@2.29.4: 266 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 267 | 268 | obsidian@1.4.11: 269 | resolution: {integrity: sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==} 270 | peerDependencies: 271 | '@codemirror/state': ^6.0.0 272 | '@codemirror/view': ^6.0.0 273 | 274 | style-mod@4.1.2: 275 | resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} 276 | 277 | ts-dedent@2.2.0: 278 | resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} 279 | engines: {node: '>=6.10'} 280 | 281 | tslib@2.4.0: 282 | resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} 283 | 284 | typescript@4.7.4: 285 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} 286 | engines: {node: '>=4.2.0'} 287 | hasBin: true 288 | 289 | undici-types@6.21.0: 290 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 291 | 292 | w3c-keyname@2.2.6: 293 | resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} 294 | 295 | snapshots: 296 | 297 | '@biomejs/biome@2.1.4': 298 | optionalDependencies: 299 | '@biomejs/cli-darwin-arm64': 2.1.4 300 | '@biomejs/cli-darwin-x64': 2.1.4 301 | '@biomejs/cli-linux-arm64': 2.1.4 302 | '@biomejs/cli-linux-arm64-musl': 2.1.4 303 | '@biomejs/cli-linux-x64': 2.1.4 304 | '@biomejs/cli-linux-x64-musl': 2.1.4 305 | '@biomejs/cli-win32-arm64': 2.1.4 306 | '@biomejs/cli-win32-x64': 2.1.4 307 | 308 | '@biomejs/cli-darwin-arm64@2.1.4': 309 | optional: true 310 | 311 | '@biomejs/cli-darwin-x64@2.1.4': 312 | optional: true 313 | 314 | '@biomejs/cli-linux-arm64-musl@2.1.4': 315 | optional: true 316 | 317 | '@biomejs/cli-linux-arm64@2.1.4': 318 | optional: true 319 | 320 | '@biomejs/cli-linux-x64-musl@2.1.4': 321 | optional: true 322 | 323 | '@biomejs/cli-linux-x64@2.1.4': 324 | optional: true 325 | 326 | '@biomejs/cli-win32-arm64@2.1.4': 327 | optional: true 328 | 329 | '@biomejs/cli-win32-x64@2.1.4': 330 | optional: true 331 | 332 | '@codemirror/language@6.3.0': 333 | dependencies: 334 | '@codemirror/state': 6.1.2 335 | '@codemirror/view': 6.4.1 336 | '@lezer/common': 1.2.1 337 | '@lezer/highlight': 1.1.2 338 | '@lezer/lr': 1.2.4 339 | style-mod: 4.1.2 340 | 341 | '@codemirror/state@6.1.2': {} 342 | 343 | '@codemirror/view@6.4.1': 344 | dependencies: 345 | '@codemirror/state': 6.1.2 346 | style-mod: 4.1.2 347 | w3c-keyname: 2.2.6 348 | 349 | '@lezer/common@1.2.1': {} 350 | 351 | '@lezer/highlight@1.1.2': 352 | dependencies: 353 | '@lezer/common': 1.2.1 354 | 355 | '@lezer/lr@1.2.4': 356 | dependencies: 357 | '@lezer/common': 1.2.1 358 | 359 | '@types/codemirror@5.60.8': 360 | dependencies: 361 | '@types/tern': 0.23.4 362 | 363 | '@types/estree@1.0.0': {} 364 | 365 | '@types/node@20.19.10': 366 | dependencies: 367 | undici-types: 6.21.0 368 | 369 | '@types/tern@0.23.4': 370 | dependencies: 371 | '@types/estree': 1.0.0 372 | 373 | asciimath-parser@0.6.10: {} 374 | 375 | builtin-modules@3.3.0: {} 376 | 377 | esbuild-android-64@0.14.47: 378 | optional: true 379 | 380 | esbuild-android-arm64@0.14.47: 381 | optional: true 382 | 383 | esbuild-darwin-64@0.14.47: 384 | optional: true 385 | 386 | esbuild-darwin-arm64@0.14.47: 387 | optional: true 388 | 389 | esbuild-freebsd-64@0.14.47: 390 | optional: true 391 | 392 | esbuild-freebsd-arm64@0.14.47: 393 | optional: true 394 | 395 | esbuild-linux-32@0.14.47: 396 | optional: true 397 | 398 | esbuild-linux-64@0.14.47: 399 | optional: true 400 | 401 | esbuild-linux-arm64@0.14.47: 402 | optional: true 403 | 404 | esbuild-linux-arm@0.14.47: 405 | optional: true 406 | 407 | esbuild-linux-mips64le@0.14.47: 408 | optional: true 409 | 410 | esbuild-linux-ppc64le@0.14.47: 411 | optional: true 412 | 413 | esbuild-linux-riscv64@0.14.47: 414 | optional: true 415 | 416 | esbuild-linux-s390x@0.14.47: 417 | optional: true 418 | 419 | esbuild-netbsd-64@0.14.47: 420 | optional: true 421 | 422 | esbuild-openbsd-64@0.14.47: 423 | optional: true 424 | 425 | esbuild-sunos-64@0.14.47: 426 | optional: true 427 | 428 | esbuild-windows-32@0.14.47: 429 | optional: true 430 | 431 | esbuild-windows-64@0.14.47: 432 | optional: true 433 | 434 | esbuild-windows-arm64@0.14.47: 435 | optional: true 436 | 437 | esbuild@0.14.47: 438 | optionalDependencies: 439 | esbuild-android-64: 0.14.47 440 | esbuild-android-arm64: 0.14.47 441 | esbuild-darwin-64: 0.14.47 442 | esbuild-darwin-arm64: 0.14.47 443 | esbuild-freebsd-64: 0.14.47 444 | esbuild-freebsd-arm64: 0.14.47 445 | esbuild-linux-32: 0.14.47 446 | esbuild-linux-64: 0.14.47 447 | esbuild-linux-arm: 0.14.47 448 | esbuild-linux-arm64: 0.14.47 449 | esbuild-linux-mips64le: 0.14.47 450 | esbuild-linux-ppc64le: 0.14.47 451 | esbuild-linux-riscv64: 0.14.47 452 | esbuild-linux-s390x: 0.14.47 453 | esbuild-netbsd-64: 0.14.47 454 | esbuild-openbsd-64: 0.14.47 455 | esbuild-sunos-64: 0.14.47 456 | esbuild-windows-32: 0.14.47 457 | esbuild-windows-64: 0.14.47 458 | esbuild-windows-arm64: 0.14.47 459 | 460 | moment@2.29.4: {} 461 | 462 | obsidian@1.4.11(@codemirror/state@6.1.2)(@codemirror/view@6.4.1): 463 | dependencies: 464 | '@codemirror/state': 6.1.2 465 | '@codemirror/view': 6.4.1 466 | '@types/codemirror': 5.60.8 467 | moment: 2.29.4 468 | 469 | style-mod@4.1.2: {} 470 | 471 | ts-dedent@2.2.0: {} 472 | 473 | tslib@2.4.0: {} 474 | 475 | typescript@4.7.4: {} 476 | 477 | undici-types@6.21.0: {} 478 | 479 | w3c-keyname@2.2.6: {} 480 | -------------------------------------------------------------------------------- /src/symbol-search/symbols.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "am": "alpha", "tex": "\\alpha" }, 3 | { "am": "beta", "tex": "\\beta" }, 4 | { "am": "gamma", "tex": "\\gamma" }, 5 | { "am": "Gamma", "tex": "\\Gamma" }, 6 | { "am": "delta", "tex": "\\delta" }, 7 | { "am": "Delta", "tex": "\\Delta" }, 8 | { "am": "epsi", "tex": "\\varepsilon" }, 9 | { "am": "epsilon", "tex": "\\epsilon" }, 10 | { "am": "varepsilon", "tex": "\\varepsilon" }, 11 | { "am": "zeta", "tex": "\\zeta" }, 12 | { "am": "eta", "tex": "\\eta" }, 13 | { "am": "theta", "tex": "\\theta" }, 14 | { "am": "Theta", "tex": "\\Theta" }, 15 | { "am": "vartheta", "tex": "\\vartheta" }, 16 | { "am": "iota", "tex": "\\iota" }, 17 | { "am": "kappa", "tex": "\\kappa" }, 18 | { "am": "lambda", "tex": "\\lambda" }, 19 | { "am": "Lambda", "tex": "\\Lambda" }, 20 | { "am": "mu", "tex": "\\mu" }, 21 | { "am": "nu", "tex": "\\nu" }, 22 | { "am": "xi", "tex": "\\xi" }, 23 | { "am": "Xi", "tex": "\\Xi" }, 24 | { "am": "pi", "tex": "\\pi" }, 25 | { "am": "Pi", "tex": "\\Pi" }, 26 | { "am": "rho", "tex": "\\rho" }, 27 | { "am": "sigma", "tex": "\\sigma" }, 28 | { "am": "Sigma", "tex": "\\Sigma" }, 29 | { "am": "tau", "tex": "\\tau" }, 30 | { "am": "upsilon", "tex": "\\upsilon" }, 31 | { "am": "phi", "tex": "\\phi" }, 32 | { "am": "varphi", "tex": "\\varphi" }, 33 | { "am": "varPhi", "tex": "\\varPhi" }, 34 | { "am": "Phi", "tex": "\\Phi" }, 35 | { "am": "chi", "tex": "\\chi" }, 36 | { "am": "psi", "tex": "\\psi" }, 37 | { "am": "Psi", "tex": "\\Psi" }, 38 | { "am": "omega", "tex": "\\omega" }, 39 | { "am": "Omega", "tex": "\\Omega" }, 40 | { "am": "***", "tex": "\\star" }, 41 | { "am": "star", "tex": "\\star" }, 42 | { "am": "**", "tex": "\\ast" }, 43 | { "am": "ast", "tex": "\\ast" }, 44 | { "am": "*", "tex": "\\cdot" }, 45 | { "am": "cdot", "tex": "\\cdot" }, 46 | { "am": "//", "tex": "{/}", "rendered": "a//b" }, 47 | { "am": "\\\\", "tex": "\\backslash" }, 48 | { "am": "setminus", "tex": "\\setminus" }, 49 | { "am": "xx", "tex": "\\times" }, 50 | { "am": "|><", "tex": "\\ltimes" }, 51 | { "am": "><|", "tex": "\\rtimes" }, 52 | { "am": "|><|", "tex": "\\bowtie" }, 53 | { "am": "-:", "tex": "\\div" }, 54 | { "am": "@", "tex": "\\circ" }, 55 | { "am": "o+", "tex": "\\oplus" }, 56 | { "am": "ox", "tex": "\\otimes" }, 57 | { "am": "o.", "tex": "\\odot" }, 58 | { "am": "sum", "tex": "\\sum" }, 59 | { "am": "prod", "tex": "\\prod" }, 60 | { "am": "^^", "tex": "\\wedge" }, 61 | { "am": "^^^", "tex": "\\bigwedge" }, 62 | { "am": "vv", "tex": "\\vee" }, 63 | { "am": "vvv", "tex": "\\bigvee" }, 64 | { "am": "nn", "tex": "\\cap" }, 65 | { "am": "nnn", "tex": "\\bigcap" }, 66 | { "am": "uu", "tex": "\\cup" }, 67 | { "am": "uuu", "tex": "\\bigcup" }, 68 | { "am": "!=", "tex": "\\ne" }, 69 | { "am": "lt", "tex": "<" }, 70 | { "am": "<=", "tex": "\\leqslant" }, 71 | { "am": "le", "tex": "\\le" }, 72 | { "am": "gt", "tex": ">" }, 73 | { "am": ">=", "tex": "\\geqslant" }, 74 | { "am": "ge", "tex": "\\ge" }, 75 | { "am": "-<", "tex": "\\prec" }, 76 | { "am": ">-", "tex": "\\succ" }, 77 | { "am": "-<=", "tex": "\\preceq" }, 78 | { "am": ">-=", "tex": "\\succeq" }, 79 | { "am": "in", "tex": "\\in" }, 80 | { "am": "!in", "tex": "\\notin" }, 81 | { "am": "sub", "tex": "\\subset" }, 82 | { "am": "sup", "tex": "\\supset" }, 83 | { "am": "sube", "tex": "\\subseteq" }, 84 | { "am": "supe", "tex": "\\supseteq" }, 85 | { "am": "-=", "tex": "\\equiv" }, 86 | { "am": "~=", "tex": "\\cong" }, 87 | { "am": "~", "tex": "\\sim" }, 88 | { "am": "~~", "tex": "\\approx" }, 89 | { "am": "\\#", "tex": "\\#" }, 90 | { "am": "\\&", "tex": "\\&" }, 91 | { "am": "\\@", "tex": "@" }, 92 | { "am": "\\%", "tex": "\\%" }, 93 | { "am": "%", "tex": "\\%" }, 94 | { "am": "\\$", "tex": "\\$" }, 95 | { "am": "\\,", "tex": "\\," }, 96 | { "am": "\\;", "tex": "\\;" }, 97 | { "am": "\\:", "tex": "\\:" }, 98 | { "am": "\\!", "tex": "\\!" }, 99 | { "am": "enspace", "tex": "a\\enspace b" }, 100 | { 101 | "am": "hspace", 102 | "tex": "\\hspace{$1}", 103 | "rendered": "a\\hspace{12pt}b", 104 | "placeholder": "($1)", 105 | "fill": ["12pt"] 106 | }, 107 | { "am": "prop", "tex": "\\propto" }, 108 | { "am": "comp", "tex": "\\complement" }, 109 | { "am": "complement", "tex": "\\complement" }, 110 | { "am": "if", "tex": "\\text{if}\\quad", "rendered": "\\text{if}" }, 111 | { 112 | "am": "otherwise", 113 | "tex": "\\text{otherwise}\\quad", 114 | "rendered": "\\text{otherwise}" 115 | }, 116 | { "am": "and", "tex": " and ", "rendered": "\\text{ and }" }, 117 | { "am": "or", "tex": " or ", "rendered": "\\text{ or }" }, 118 | { "am": "not", "tex": "\\neg" }, 119 | { "am": "=>", "tex": "\\implies" }, 120 | { "am": "~>", "tex": "\\rightsquigarrow" }, 121 | { "am": "-/->", "tex": "\\nrightarrow" }, 122 | { "am": "<-/-", "tex": "\\nleftarrow" }, 123 | { "am": "<-/->", "tex": "\\nleftrightarrow" }, 124 | { "am": "<=>", "tex": "\\iff" }, 125 | { "am": "iff", "tex": "\\iff" }, 126 | { "am": "AA", "tex": "\\forall" }, 127 | { "am": "EE", "tex": "\\exists" }, 128 | { "am": "_|_", "tex": "\\bot" }, 129 | { "am": "TT", "tex": "\\top" }, 130 | { "am": "|--", "tex": "\\vdash" }, 131 | { "am": "|==", "tex": "\\models" }, 132 | { "am": "int", "tex": "\\int" }, 133 | { "am": "oint", "tex": "\\oint" }, 134 | { "am": "del", "tex": "\\partial" }, 135 | { "am": "grad", "tex": "\\nabla" }, 136 | { "am": "+-", "tex": "\\pm" }, 137 | { "am": "-+", "tex": "\\mp" }, 138 | { "am": "O/", "tex": "\\varnothing" }, 139 | { "am": "oo", "tex": "\\infty" }, 140 | { "am": "aleph", "tex": "\\aleph" }, 141 | { "am": "...", "tex": "\\ldots" }, 142 | { "am": ":.", "tex": "\\therefore" }, 143 | { "am": ":'", "tex": "\\because" }, 144 | { "am": "/_", "tex": "\\angle" }, 145 | { "am": "/_\\", "tex": "\\triangle" }, 146 | { "am": "quad", "tex": "\\quad" }, 147 | { "am": "qquad", "tex": "\\qquad" }, 148 | { "am": "cdots", "tex": "\\cdots" }, 149 | { "am": "vdots", "tex": "\\vdots" }, 150 | { "am": "ddots", "tex": "\\ddots" }, 151 | { "am": "diamond", "tex": "\\diamond" }, 152 | { "am": "Lap", "tex": "\\mathscr{L}" }, 153 | { "am": "square", "tex": "\\square" }, 154 | { "am": "|__", "tex": "\\lfloor" }, 155 | { "am": "__|", "tex": "\\rfloor" }, 156 | { "am": "|~", "tex": "\\lceil" }, 157 | { "am": "~|", "tex": "\\rceil" }, 158 | { "am": "CC", "tex": "\\mathbb{C}" }, 159 | { "am": "NN", "tex": "\\mathbb{N}" }, 160 | { "am": "QQ", "tex": "\\mathbb{Q}" }, 161 | { "am": "RR", "tex": "\\mathbb{R}" }, 162 | { "am": "ZZ", "tex": "\\mathbb{Z}" }, 163 | { "am": "'", "tex": "^{\\prime}" }, 164 | { "am": "''", "tex": "^{\\prime\\prime}" }, 165 | { "am": "'''", "tex": "^{\\prime\\prime\\prime}" }, 166 | { 167 | "am": "lim", 168 | "tex": "\\lim_{$2} $1", 169 | "placeholder": "_($2) $1", 170 | "fill": ["f(x)", "x\\to 0"] 171 | }, 172 | { "am": "sin", "tex": "\\sin" }, 173 | { "am": "cos", "tex": "\\cos" }, 174 | { "am": "tan", "tex": "\\tan" }, 175 | { "am": "sinh", "tex": "\\sinh" }, 176 | { "am": "cosh", "tex": "\\cosh" }, 177 | { "am": "tanh", "tex": "\\tanh" }, 178 | { "am": "cot", "tex": "\\cot" }, 179 | { "am": "sec", "tex": "\\sec" }, 180 | { "am": "csc", "tex": "\\csc" }, 181 | { "am": "arcsin", "tex": "\\arcsin" }, 182 | { "am": "arccos", "tex": "\\arccos" }, 183 | { "am": "arctan", "tex": "\\arctan" }, 184 | { "am": "coth", "tex": "\\coth" }, 185 | { "am": "sech", "tex": "\\operatorname{sech}" }, 186 | { "am": "csch", "tex": "\\operatorname{csch}" }, 187 | { "am": "exp", "tex": "\\exp" }, 188 | { "am": "log", "tex": "\\log" }, 189 | { "am": "ln", "tex": "\\ln" }, 190 | { "am": "det", "tex": "\\det" }, 191 | { "am": "dim", "tex": "\\dim" }, 192 | { "am": "gcd", "tex": "\\gcd" }, 193 | { "am": "lcm", "tex": "\\operatorname{lcm}" }, 194 | { "am": "min", "tex": "\\min" }, 195 | { "am": "max", "tex": "\\max" }, 196 | { "am": "Sup", "tex": "\\sup" }, 197 | { "am": "inf", "tex": "\\inf" }, 198 | { "am": "mod", "tex": "\\operatorname{mod}" }, 199 | { "am": "sgn", "tex": "\\operatorname{sgn}" }, 200 | { 201 | "am": "abs", 202 | "tex": "\\left| $1 \\right|", 203 | "placeholder": "($1)", 204 | "fill": ["a"] 205 | }, 206 | { 207 | "am": "norm", 208 | "tex": "\\left\\| $1 \\right\\|", 209 | "placeholder": "($1)", 210 | "fill": ["a"] 211 | }, 212 | { 213 | "am": "floor", 214 | "tex": "\\left\\lfloor $1 \\right\\rfloor", 215 | "placeholder": "($1)", 216 | "fill": ["a"] 217 | }, 218 | { 219 | "am": "ceil", 220 | "tex": "\\left\\lceil $1 \\right\\rceil", 221 | "placeholder": "($1)", 222 | "fill": ["a"] 223 | }, 224 | { "am": "uarr", "tex": "\\uparrow" }, 225 | { "am": "uparrow", "tex": "\\uparrow" }, 226 | { "am": "darr", "tex": "\\downarrow" }, 227 | { "am": "downarrow", "tex": "\\downarrow" }, 228 | { "am": "rarr", "tex": "\\rightarrow" }, 229 | { "am": "rightarrow", "tex": "\\rightarrow" }, 230 | { "am": "to", "tex": "\\to" }, 231 | { "am": "->", "tex": "\\to" }, 232 | { "am": "<-", "tex": "\\gets" }, 233 | { "am": ">->", "tex": "\\rightarrowtail" }, 234 | { "am": "->>", "tex": "\\twoheadrightarrow" }, 235 | { "am": ">->>", "tex": "⤖" }, 236 | { "am": "|->", "tex": "\\mapsto" }, 237 | { "am": "larr", "tex": "\\leftarrow" }, 238 | { "am": "leftarrow", "tex": "\\leftarrow" }, 239 | { "am": "harr", "tex": "\\leftrightarrow" }, 240 | { "am": "rArr", "tex": "\\Rightarrow" }, 241 | { "am": "lArr", "tex": "\\Leftarrow" }, 242 | { "am": "hArr", "tex": "\\Leftrightarrow" }, 243 | { "am": "curvArrLt", "tex": "\\curvearrowleft" }, 244 | { "am": "curvArrRt", "tex": "\\curvearrowright" }, 245 | { "am": "circArrLt", "tex": "\\circlearrowleft" }, 246 | { "am": "circArrRt", "tex": "\\circlearrowright" }, 247 | { "am": "sqrt", "tex": "\\sqrt{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 248 | { 249 | "am": "root", 250 | "tex": "\\sqrt[ $1 ]{ $2 }", 251 | "placeholder": "($1)($2)", 252 | "fill": ["a", "b"] 253 | }, 254 | { 255 | "am": "frac", 256 | "tex": "\\frac{ $1 }{ $2 }", 257 | "placeholder": "($1)($2)", 258 | "fill": ["a", "b"] 259 | }, 260 | { 261 | "am": "/", 262 | "tex": "\\frac{ $1 }{ $2 }", 263 | "placeholder": "($1)($2)", 264 | "fill": ["a", "b"] 265 | }, 266 | { "am": "choose", "tex": "{ a \\choose b }" }, 267 | { 268 | "am": "_", 269 | "tex": "_{ $1 }", 270 | "rendered": "x_{ $1 }", 271 | "placeholder": "($1)", 272 | "fill": ["a"] 273 | }, 274 | { 275 | "am": "^", 276 | "tex": "^{ $1 }", 277 | "rendered": "x^{ $1 }", 278 | "placeholder": "($1)", 279 | "fill": ["a"] 280 | }, 281 | { 282 | "am": "stackrel", 283 | "tex": "\\stackrel{ $1 }{ $2 }", 284 | "placeholder": "($1)($2)", 285 | "fill": ["a", "b"] 286 | }, 287 | { 288 | "am": "overset", 289 | "tex": "\\overset{ $1 }{ $2 }", 290 | "placeholder": "($1)($2)", 291 | "fill": ["a", "b"] 292 | }, 293 | { 294 | "am": "underset", 295 | "tex": "\\underset{ $1 }{ $2 }", 296 | "placeholder": "($1)($2)", 297 | "fill": ["a", "b"] 298 | }, 299 | { "am": "hat", "tex": "\\hat{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 300 | { 301 | "am": "Hat", 302 | "tex": "\\widehat{ $1 }", 303 | "placeholder": "($1)", 304 | "fill": ["a"] 305 | }, 306 | { 307 | "am": "ol", 308 | "tex": "\\overline{ $1 }", 309 | "placeholder": "($1)", 310 | "fill": ["a"] 311 | }, 312 | { 313 | "am": "overline", 314 | "tex": "\\overline{ $1 }", 315 | "placeholder": "($1)", 316 | "fill": ["a"] 317 | }, 318 | { 319 | "am": "arc", 320 | "tex": "\\stackrel{\\frown}{ $1 }", 321 | "placeholder": "($1)", 322 | "fill": ["a"] 323 | }, 324 | { "am": "bar", "tex": "\\bar{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 325 | { "am": "vec", "tex": "\\vec{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 326 | { 327 | "am": "Vec", 328 | "tex": "\\overrightarrow{ $1 }", 329 | "placeholder": "($1)", 330 | "fill": ["a"] 331 | }, 332 | { 333 | "am": "tilde", 334 | "tex": "\\tilde{ $1 }", 335 | "placeholder": "($1)", 336 | "fill": ["a"] 337 | }, 338 | { 339 | "am": "Tilde", 340 | "tex": "\\widetilde{ $1 }", 341 | "placeholder": "($1)", 342 | "fill": ["a"] 343 | }, 344 | { "am": "dot", "tex": "\\dot{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 345 | { "am": "ddot", "tex": "\\ddot{ $1 }", "placeholder": "($1)", "fill": ["a"] }, 346 | { 347 | "am": "ul", 348 | "tex": "\\underline{ $1 }", 349 | "placeholder": "($1)", 350 | "fill": ["a"] 351 | }, 352 | { 353 | "am": "underline", 354 | "tex": "\\underline{ $1 }", 355 | "placeholder": "($1)", 356 | "fill": ["a"] 357 | }, 358 | { 359 | "am": "underbrace", 360 | "tex": "\\underbrace{ $1 }", 361 | "placeholder": "($1)", 362 | "fill": ["a"] 363 | }, 364 | { 365 | "am": "ubrace", 366 | "tex": "\\underbrace{ $1 }", 367 | "placeholder": "($1)", 368 | "fill": ["a"] 369 | }, 370 | { 371 | "am": "overbrace", 372 | "tex": "\\overbrace{ $1 }", 373 | "placeholder": "($1)", 374 | "fill": ["a"] 375 | }, 376 | { 377 | "am": "obrace", 378 | "tex": "\\overbrace{ $1 }", 379 | "placeholder": "($1)", 380 | "fill": ["a"] 381 | }, 382 | { 383 | "am": "color", 384 | "tex": "{ \\color{$2} $1 }", 385 | "placeholder": "($2)($1)", 386 | "fill": ["b", "red"] 387 | }, 388 | { 389 | "am": "phantom", 390 | "tex": "\\phantom{a}", 391 | "placeholder": "($1)", 392 | "fill": ["a"] 393 | }, 394 | { 395 | "am": "text", 396 | "tex": "\\text{$1}", 397 | "rendered": "", 398 | "placeholder": "\"$1\"", 399 | "fill": ["a"] 400 | }, 401 | { "am": "tex", "tex": "$1", "placeholder": "\"$1\"", "fill": ["a"] }, 402 | { "am": "mbox", "tex": "\\mbox{a}", "placeholder": "($1)", "fill": ["a"] }, 403 | { 404 | "am": "op", 405 | "tex": "\\operatorname{ $1 }", 406 | "placeholder": "($1)", 407 | "fill": ["a"] 408 | }, 409 | { 410 | "am": "cancel", 411 | "tex": "\\cancel{ $1 }", 412 | "placeholder": "($1)", 413 | "fill": ["a"] 414 | }, 415 | { "am": "bb", "tex": "\\mathbf{$1}", "placeholder": "($1)", "fill": ["A"] }, 416 | { "am": "sf", "tex": "\\mathsf{$1}", "placeholder": "($1)", "fill": ["A"] }, 417 | { "am": "bbb", "tex": "\\mathbb{$1}", "placeholder": "($1)", "fill": ["A"] }, 418 | { "am": "cc", "tex": "\\mathcal{$1}", "placeholder": "($1)", "fill": ["A"] }, 419 | { "am": "tt", "tex": "\\mathtt{$1}", "placeholder": "($1)", "fill": ["A"] }, 420 | { "am": "fr", "tex": "\\mathfrak{$1}", "placeholder": "($1)", "fill": ["A"] }, 421 | { 422 | "am": "bm", 423 | "tex": "\\boldsymbol{$1}", 424 | "placeholder": "($1)", 425 | "fill": ["A"] 426 | }, 427 | { "am": "rm", "tex": "\\mathrm{$1}", "placeholder": "($1)", "fill": ["A"] }, 428 | { "am": "scr", "tex": "\\mathscr{$1}", "placeholder": "($1)", "fill": ["A"] }, 429 | { 430 | "am": "limits", 431 | "tex": "\\mathop{ $1 }\\limits", 432 | "rendered": "\\mathop{ $1 }\\limits_{k=1}^n", 433 | "placeholder": "($1)_($2)^($3)", 434 | "fill": ["a", "k=1", "n"] 435 | }, 436 | { "am": "iint", "tex": "\\iint" }, 437 | { "am": "iiint", "tex": "\\iiint" }, 438 | { "am": "oiint", "tex": "∯" }, 439 | { "am": "oiiint", "tex": "∰" }, 440 | { "am": "laplace", "tex": "\\Delta" }, 441 | { 442 | "am": "==", 443 | "tex": "\\xlongequal[ $2 ]{ $1 }", 444 | "placeholder": "^($1)_($2)", 445 | "fill": ["a", "b"] 446 | }, 447 | { 448 | "am": "-->", 449 | "tex": "\\xrightarrow[ $2 ]{ $1 }", 450 | "placeholder": "^($1)_($2)", 451 | "fill": ["a", "b"] 452 | }, 453 | { "am": "||", "tex": "\\Vert", "rendered": "\\Vert" }, 454 | { "am": "!||", "tex": "∦" }, 455 | { "am": "S=", "tex": "≌" }, 456 | { "am": "S~", "tex": "∽" }, 457 | { "am": "!-=", "tex": "\\not\\equiv" }, 458 | { "am": "!|", "tex": "∤" }, 459 | { "am": "!", "tex": "{a !}" }, 460 | { "am": "!!", "tex": "{a !!}" }, 461 | { "am": "!sube", "tex": "\\not\\subseteq" }, 462 | { "am": "!supe", "tex": "\\not\\supseteq" }, 463 | { "am": "subne", "tex": "\\subsetneqq" }, 464 | { "am": "supne", "tex": "\\supsetneqq" }, 465 | { "am": "lhd", "tex": "\\lhd" }, 466 | { "am": "rhd", "tex": "\\rhd" }, 467 | { "am": "normal", "tex": "\\unlhd" }, 468 | { "am": "rnormal", "tex": "\\unrhd" }, 469 | { "am": "(", "tex": "(" }, 470 | { "am": ")", "tex": ")" }, 471 | { "am": "[", "tex": "[" }, 472 | { "am": "]", "tex": "]" }, 473 | { "am": "{", "tex": "\\lbrace" }, 474 | { "am": "}", "tex": "\\rbrace" }, 475 | { "am": "(:", "tex": "\\langle" }, 476 | { "am": ":)", "tex": "\\rangle" }, 477 | { "am": "{:", "tex": "{", "rendered": "" }, 478 | { "am": ":}", "tex": "}", "rendered": "" }, 479 | { "am": "|", "tex": "|" }, 480 | { "am": "&", "tex": "&" }, 481 | { "am": "&&", "tex": "&&" }, 482 | { "am": ",", "tex": "," }, 483 | { "am": ";", "tex": ";" }, 484 | { 485 | "am": "pp", 486 | "tex": "\\frac{ \\partial ^ $3 { $1 } }{ \\partial $2 ^ $3 }", 487 | "placeholder": "^$3 ($1)($2)", 488 | "fill": ["f", "x", "n"] 489 | }, 490 | { 491 | "am": "dd", 492 | "tex": "\\frac{ \\mathrm{d} ^ $3 { $1 } }{ \\mathrm{d} $2 ^ $3 }", 493 | "placeholder": "^$3 ($1)($2)", 494 | "fill": ["f", "x", "n"] 495 | }, 496 | { 497 | "am": "tiny", 498 | "tex": "{\\tiny $1 }", 499 | "placeholder": "($1)", 500 | "fill": ["\\text{text}"] 501 | }, 502 | { 503 | "am": "small", 504 | "tex": "{\\small $1 }", 505 | "placeholder": "($1)", 506 | "fill": ["\\text{text}"] 507 | }, 508 | { 509 | "am": "large", 510 | "tex": "{\\large $1 }", 511 | "placeholder": "($1)", 512 | "fill": ["\\text{text}"] 513 | }, 514 | { 515 | "am": "huge", 516 | "tex": "{\\huge $1 }", 517 | "placeholder": "($1)", 518 | "fill": ["\\text{text}"] 519 | } 520 | ] 521 | --------------------------------------------------------------------------------