├── .npmrc ├── .eslintignore ├── assets └── example.png ├── .editorconfig ├── pseudocode.d.ts ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── src ├── inline_macro.ts ├── setting.ts ├── latex_translator.ts ├── theme.ts ├── export_button.ts ├── auto_complete.ts └── setting_tab.ts ├── versions.json ├── package.json ├── LICENSE ├── esbuild.config.mjs ├── extract_katexcss.py ├── .github └── workflows │ └── release.yml ├── main.ts ├── README.md └── katex.min.css /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytliu74/obsidian-pseudocode/HEAD/assets/example.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | 12 | [*.{yml,yaml}] 13 | indent_size = 2 14 | tab_width = 2 15 | -------------------------------------------------------------------------------- /pseudocode.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'pseudocode' { 2 | export class ParseError extends Error { } 3 | export function render(input: string, baseDomEle?: Element, options?: any): Element; 4 | export function renderToString(input: string, options?: any): string; 5 | export function renderElement(elem: Element, options?: any): void; 6 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pseudocode-in-obs", 3 | "name": "Pseudocode", 4 | "version": "1.6.2", 5 | "minAppVersion": "0.15.0", 6 | "description": "This is an obsidian plugin that helps to render a LaTeX-style pseudocode inside a code block.", 7 | "author": "Yaotian Liu", 8 | "authorUrl": "https://github.com/ytliu74", 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 | 24 | tmp/ -------------------------------------------------------------------------------- /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 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /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 | let 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 | let 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 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off", 22 | "@typescript-eslint/no-inferrable-types": "off" 23 | } 24 | } -------------------------------------------------------------------------------- /src/inline_macro.ts: -------------------------------------------------------------------------------- 1 | import { 2 | translateUnsupportedMacrosPerf, 3 | checkTranslatedMacros, 4 | } from "./latex_translator"; 5 | 6 | export function extractInlineMacros(source: string): [string, string] { 7 | const sourceLines = source.split("\n"); 8 | const macroStartIndex = sourceLines.findIndex(line => line.includes("\\begin{algorithm}")); 9 | 10 | const macroLines = sourceLines.slice(0, macroStartIndex).join("\n"); 11 | const nonMacroLines = sourceLines.slice(macroStartIndex).join("\n"); 12 | 13 | let inlineMacros = ""; 14 | 15 | try { 16 | const translated = translateUnsupportedMacrosPerf(macroLines); 17 | inlineMacros = checkTranslatedMacros(translated); 18 | } catch (error) { 19 | 20 | console.error(error); 21 | } 22 | 23 | return [inlineMacros, nonMacroLines]; 24 | } 25 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0", 3 | "1.1.0": "0.15.0", 4 | "1.1.1": "0.15.0", 5 | "1.1.2": "0.15.0", 6 | "1.1.3": "0.15.0", 7 | "1.1.4": "0.15.0", 8 | "1.1.5": "0.15.0", 9 | "1.1.6": "0.15.0", 10 | "1.1.7": "0.15.0", 11 | "1.1.8": "0.15.0", 12 | "1.1.9": "0.15.0", 13 | "1.1.10": "0.15.0", 14 | "1.1.11": "0.15.0", 15 | "1.2.0": "0.15.0", 16 | "1.2.1": "0.15.0", 17 | "1.2.2": "0.15.0", 18 | "1.2.3": "0.15.0", 19 | "1.3.0": "0.15.0", 20 | "1.3.1": "0.15.0", 21 | "1.4.0": "0.15.0", 22 | "1.4.1": "0.15.0", 23 | "1.4.2": "0.15.0", 24 | "1.4.3": "0.15.0", 25 | "1.4.4": "0.15.0", 26 | "1.5.0": "0.15.0", 27 | "1.5.1": "0.15.0", 28 | "1.5.2": "0.15.0", 29 | "1.5.3": "0.15.0", 30 | "1.5.4": "0.15.0", 31 | "1.5.5": "0.15.0", 32 | "1.5.6": "0.15.0", 33 | "1.5.7": "0.15.0", 34 | "1.5.8": "0.15.0", 35 | "1.6.0": "0.15.0", 36 | "1.6.1": "0.15.0", 37 | "1.6.2": "0.15.0" 38 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pseudocode-in-obs", 3 | "version": "1.6.2", 4 | "description": "This is an obsidian plugin that helps to render a LaTeX-style pseudocode inside a code block.", 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" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4", 23 | "@types/katex": "0.11.1" 24 | }, 25 | "dependencies": { 26 | "pseudocode": "ytliu74/pseudocode.js#master", 27 | "katex": "0.11.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yaotian-Liu 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 esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === "production"); 13 | 14 | const context = await esbuild.context({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["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 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | }); 42 | 43 | if (prod) { 44 | await context.rebuild(); 45 | process.exit(0); 46 | } else { 47 | await context.watch(); 48 | } -------------------------------------------------------------------------------- /src/setting.ts: -------------------------------------------------------------------------------- 1 | // This is the setting for pseudocode.js 2 | export interface PseudocodeJsSettings { 3 | indentSize: string; 4 | commentDelimiter: string; 5 | lineNumber: boolean; 6 | lineNumberPunc: string; 7 | noEnd: boolean; 8 | captionCount: undefined; 9 | scopeLines: boolean; 10 | } 11 | 12 | // Setting for this plugin 13 | export interface PseudocodeSettings { 14 | blockSize: number; 15 | preambleEnabled: boolean; 16 | preamblePath: string; 17 | preambleLoadedNotice: boolean; 18 | followSystemTheme: boolean; 19 | jsSettings: PseudocodeJsSettings; 20 | } 21 | 22 | export const DEFAULT_SETTINGS: PseudocodeSettings = { 23 | blockSize: 99, 24 | preambleEnabled: false, 25 | preamblePath: "preamble.sty", 26 | preambleLoadedNotice: false, 27 | followSystemTheme: false, 28 | jsSettings: { 29 | indentSize: "1.2em", 30 | commentDelimiter: "//", 31 | lineNumber: false, 32 | lineNumberPunc: ":", 33 | noEnd: false, 34 | captionCount: undefined, 35 | scopeLines: false, 36 | }, 37 | }; 38 | 39 | export const PseudocodeBlockInit = 40 | "```pseudo\n" + 41 | "\t\\begin{algorithm}\n\t\\caption{Algo Caption}\n\t\\begin{algorithmic}" + 42 | "\n\n\t\\end{algorithmic}\n\t\\end{algorithm}" + 43 | "\n```"; 44 | 45 | export const BLOCK_NAME = "pseudo"; 46 | -------------------------------------------------------------------------------- /extract_katexcss.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.request 3 | import base64 4 | import os 5 | 6 | # define a function to encode a file as base64 7 | def encode_file_as_base64(filename): 8 | print("processing: ", filename) 9 | url_base = "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/fonts/" 10 | url = url_base + filename 11 | 12 | if not os.path.isfile("tmp/" + filename): 13 | urllib.request.urlretrieve(url, "tmp/" + filename) 14 | 15 | with open("tmp/" + filename, 'rb') as f: 16 | return base64.b64encode(f.read()).decode("utf-8") 17 | 18 | # read the CSS file into a string 19 | with open('katex.min.css', 'r') as f: 20 | css_string = f.read() 21 | 22 | # define the regular expression pattern 23 | pattern = r'url\((.*?)\) format\((.*?)\)' 24 | 25 | # define a function to replace the matched pattern with the base64-encoded data URI 26 | def replace_match(match): 27 | url = match.group(1) 28 | filename = url[6:] 29 | 30 | format = match.group(2) 31 | base64_code = encode_file_as_base64(filename) 32 | return f'url(data:application/{filename};charset=utf-8;base64,{base64_code}) format({format})' 33 | 34 | new_css_string = re.sub(pattern, replace_match, css_string) 35 | with open('new_styles.css', 'w') as f: 36 | f.write(new_css_string) -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | create_release: 10 | if: contains(github.event.head_commit.message, 'new release') 11 | permissions: 12 | contents: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: "18" 22 | 23 | - name: Install Dependencies 24 | run: | 25 | npm config set registry https://registry.npmjs.org/ 26 | npm install 27 | 28 | - name: Build 29 | run: npm run build 30 | 31 | - name: Get Release Version 32 | id: release-version 33 | run: | 34 | echo "VER=$(echo ${{ github.event.head_commit.message }} | cut -d' ' -f3)" >> $GITHUB_OUTPUT 35 | 36 | - name: Create Release 37 | uses: softprops/action-gh-release@v1 38 | with: 39 | tag_name: ${{ steps.release-version.outputs.VER }} 40 | body: "New release ${{ steps.release-version.outputs.VER }}" 41 | draft: false 42 | prerelease: false 43 | files: | 44 | main.js 45 | manifest.json 46 | styles.css 47 | -------------------------------------------------------------------------------- /src/latex_translator.ts: -------------------------------------------------------------------------------- 1 | import * as katex from "katex"; 2 | 3 | export function translateUnsupportedMacros(input: string): string { 4 | // handle \DeclarePairedDelimiter 5 | let output = input.replace( 6 | /\\DeclarePairedDelimiter\{(.*?)\}\{(.*?)\}\{(.*?)\}/g, 7 | "\\newcommand{$1}[1]{\\left$2 #1 \\right$3}" 8 | ); 9 | 10 | // handle \DeclareMathOperator 11 | output = output.replace( 12 | /\\DeclareMathOperator\*\{(.*?)\}\{(.*?)\}/g, 13 | "\\newcommand{$1}{\\mathop{\\mathrm{$2}}}" 14 | ); 15 | output = output.replace( 16 | /\\DeclareMathOperator\{(.*?)\}\{(.*?)\}/g, 17 | "\\newcommand{$1}{\\mathop{\\mathrm{$2}}}" 18 | ); 19 | return output; 20 | } 21 | 22 | // This is a performance improvement for translateUnsupportedMacros 23 | export function translateUnsupportedMacrosPerf(input: string): string { 24 | const stripped = input 25 | .replace(/(? line.trim() !== "") 28 | .join("\n"); 29 | 30 | return stripped.replace( 31 | /(\\DeclarePairedDelimiter\{(.*?)\}\{(.*?)\}\{(.*?)\})|(\\DeclareMathOperator\*\{(.*?)\}\{(.*?)\})|(\\DeclareMathOperator\{(.*?)\}\{(.*?)\})/g, 32 | (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) => { 33 | if (p1) { 34 | // It's a \DeclarePairedDelimiter 35 | return `\\newcommand{${p2}}[1]{\\left${p3} #1 \\right${p4}}`; 36 | } else if (p5) { 37 | // It's a \DeclareMathOperator* 38 | return `\\newcommand{${p6}}{\\mathop{\\mathrm{${p7}}}}`; 39 | } else if (p8) { 40 | // It's a \DeclareMathOperator 41 | return `\\newcommand{${p9}}{\\mathop{\\mathrm{${p10}}}}`; 42 | } else { 43 | // This should never happen if the regex is correct 44 | console.error(`Unexpected match: ${match}`); 45 | return match; 46 | } 47 | } 48 | ); 49 | } 50 | 51 | export function checkTranslatedMacros(input: string): string { 52 | const lines = input.split("\n"); 53 | for (let i = 0; i < lines.length; i++) { 54 | try { 55 | katex.renderToString(lines[i]); 56 | } catch (error) { 57 | if ( 58 | error instanceof katex.ParseError && 59 | /redefine/.test(error.message) 60 | ) { 61 | lines[i] = lines[i].replace("\\newcommand", "\\renewcommand"); 62 | console.log(`Redefining ${lines[i]}`); 63 | } else { 64 | throw error; 65 | } 66 | } 67 | } 68 | return lines.join("\n"); 69 | } 70 | -------------------------------------------------------------------------------- /src/theme.ts: -------------------------------------------------------------------------------- 1 | // Reference: https://forum.obsidian.md/t/obsidian-publish-api-how-to-track-changing-theme/73637/4 2 | 3 | export const themeObserver = new MutationObserver(function (mutations) { 4 | mutations.forEach(function (mutation) { 5 | const target = mutation.target as HTMLElement; 6 | if ( 7 | // dark -> dark & light -> light 8 | mutation.oldValue?.contains("theme-dark") && 9 | !mutation.oldValue?.contains("theme-light") && // key line, avoid calling twice 10 | target.classList.value.contains("theme-light") 11 | ) { 12 | console.log("light theme detected"); 13 | setPseudocodeTheme(); 14 | } else if ( 15 | // light -> empty -> dark 16 | mutation.oldValue?.contains("theme-light") && // key line, avoid calling twice 17 | !mutation.oldValue?.contains("theme-dark") && 18 | target.classList.value.contains("theme-dark") 19 | ) { 20 | console.log("dark theme detected"); 21 | setPseudocodeTheme(); 22 | } 23 | }); 24 | }); 25 | 26 | export function setPseudocodeTheme(psBlock?: Element) { 27 | 28 | const bodyElement = document.body; 29 | const backgroundValue = getComputedStyle(bodyElement) 30 | .getPropertyValue("--background-primary") 31 | .trim(); 32 | const fontValue = getComputedStyle(bodyElement) 33 | .getPropertyValue("--text-normal") 34 | .trim(); 35 | console.log(backgroundValue, fontValue); 36 | 37 | const psRootElements = psBlock 38 | ? psBlock.querySelectorAll(".ps-root") 39 | : document.querySelectorAll(".ps-root"); 40 | // console.log(psRootElements); 41 | 42 | // Loop through each element and modify the CSS properties 43 | psRootElements.forEach((element) => { 44 | const htmlElement = element as HTMLElement; 45 | htmlElement.style.backgroundColor = backgroundValue; 46 | htmlElement.style.opacity = "1"; 47 | htmlElement.style.color = fontValue; 48 | 49 | // Change border colors for .ps-algorithm and .ps-algorithm.with-caption > .ps-line:first-child 50 | const algorithmElements = htmlElement.querySelectorAll(".ps-algorithm"); 51 | algorithmElements.forEach((algElement) => { 52 | const algHtmlElement = algElement as HTMLElement; 53 | algHtmlElement.style.borderTopColor = fontValue; 54 | algHtmlElement.style.borderBottomColor = fontValue; 55 | }); 56 | 57 | const lineElements = htmlElement.querySelectorAll( 58 | ".ps-algorithm.with-caption > .ps-line:first-child" 59 | ); 60 | lineElements.forEach((lineElement) => { 61 | const lineHtmlElement = lineElement as HTMLElement; 62 | lineHtmlElement.style.borderBottomColor = fontValue; 63 | }); 64 | }); 65 | } 66 | 67 | export const setObserver = () => { 68 | themeObserver.observe(document.body, { 69 | attributes: true, 70 | attributeOldValue: true, 71 | attributeFilter: ["class"], 72 | }); 73 | }; 74 | 75 | export const detachObserver = () => { 76 | themeObserver.disconnect(); 77 | }; 78 | -------------------------------------------------------------------------------- /src/export_button.ts: -------------------------------------------------------------------------------- 1 | const BUTTON_INFO = "Export to clipboard"; 2 | const BUTTON_EXPORTED = "Exported!"; 3 | const BUTTON_FAILED = "Failed to export"; 4 | 5 | import PseudocodePlugin from "main"; 6 | 7 | export function createExportButton( 8 | parentPlugin: PseudocodePlugin, 9 | parentDiv: HTMLDivElement, 10 | inlineMacros: string, 11 | blockContent: string 12 | ) { 13 | const button = parentDiv.createEl("button"); 14 | button.classList.add("hover-button"); 15 | button.textContent = BUTTON_INFO; 16 | button.addEventListener("click", () => { 17 | console.log(BUTTON_INFO); 18 | if (blockContent !== null) { 19 | const exportContent = 20 | "\\documentclass{article}\n" + 21 | macros(parentPlugin, inlineMacros) + 22 | "\n" + 23 | "\\begin{document}\n" + 24 | processBlock(blockContent, parentPlugin) + 25 | "\n\\end{document}"; 26 | 27 | navigator.clipboard 28 | .writeText(exportContent) 29 | .then(() => { 30 | console.log("Copied to clipboard"); 31 | button.textContent = BUTTON_EXPORTED; 32 | 33 | setTimeout(() => { 34 | button.textContent = BUTTON_INFO; 35 | }, 3000); 36 | }) 37 | .catch((error) => { 38 | console.error("Failed to copy to clipboard: ", error); 39 | button.textContent = BUTTON_FAILED; 40 | }); 41 | } 42 | }); 43 | button.addEventListener("mouseover", () => { 44 | button.textContent = BUTTON_INFO; 45 | }); 46 | } 47 | 48 | const macros = (parentPlugin: PseudocodePlugin, inlineMacros: string): string => { 49 | const noEnd = parentPlugin.settings.jsSettings.noEnd; 50 | const scopeLines = parentPlugin.settings.jsSettings.scopeLines; 51 | 52 | // Split inline macros into lines and remove heading or trailing spaces 53 | const inlineMacrosLine = inlineMacros.split("\n").map(line => line.trim()); 54 | 55 | return ` 56 | \\usepackage{algorithm} 57 | \\usepackage[noEnd=${noEnd},indLines=${scopeLines}]{algpseudocodex} 58 | 59 | \\newcommand{\\And}{\\textbf{and~}} 60 | \\newcommand{\\Or}{\\textbf{or~}} 61 | \\newcommand{\\Xor}{\\textbf{xor~}} 62 | \\newcommand{\\Not}{\\textbf{not~}} 63 | \\newcommand{\\To}{\\textbf{to~}} 64 | \\newcommand{\\DownTo}{\\textbf{downto~}} 65 | \\newcommand{\\True}{\\textbf{true~}} 66 | \\newcommand{\\False}{\\textbf{false~}} 67 | \\newcommand{\\Input}{\\item[\\textbf{Input:}]} 68 | \\renewcommand{\\Output}{\\item[\\textbf{Output:}]} 69 | \\newcommand{\\Print}{\\State \\textbf{print~}} 70 | \\renewcommand{\\Return}{\\State \\textbf{return~}} 71 | 72 | \\usepackage{amsmath} 73 | ${inlineMacrosLine} 74 | `; 75 | }; 76 | 77 | const processBlock = ( 78 | block: string, 79 | parentPlugin: PseudocodePlugin 80 | ): string => { 81 | if (parentPlugin.settings.jsSettings.lineNumber) 82 | // Replace "\begin{algorithmic}" with "\begin{algorithmic}[1]" 83 | block = block.replace( 84 | "\\begin{algorithmic}", 85 | "\\begin{algorithmic}[1]" 86 | ); 87 | else; 88 | 89 | return block; 90 | }; 91 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, MarkdownView, Plugin, Notice } from "obsidian"; 2 | 3 | import { PseudocodeSettingTab } from "src/setting_tab"; 4 | import { PseudocodeSuggestor } from "src/auto_complete"; 5 | import { 6 | PseudocodeSettings, 7 | DEFAULT_SETTINGS, 8 | PseudocodeBlockInit, 9 | BLOCK_NAME, 10 | } from "src/setting"; 11 | import { 12 | translateUnsupportedMacrosPerf, 13 | checkTranslatedMacros, 14 | } from "src/latex_translator"; 15 | import { createExportButton } from "src/export_button"; 16 | import { extractInlineMacros } from "src/inline_macro"; 17 | import { setObserver, detachObserver, setPseudocodeTheme } from "src/theme"; 18 | 19 | import * as pseudocode from "pseudocode"; 20 | 21 | export default class PseudocodePlugin extends Plugin { 22 | settings: PseudocodeSettings; 23 | preamble: string = ""; 24 | 25 | async pseudocodeHandler( 26 | source: string, 27 | el: HTMLElement, 28 | ctx: any 29 | ): Promise { 30 | const blockDiv = el.createDiv({ cls: "pseudocode-block" }); 31 | const blockWidth = this.settings.blockSize; 32 | blockDiv.style.width = `${blockWidth}em`; 33 | blockDiv.addEventListener("click", (event) => { 34 | const target = (event.target as HTMLElement | null); 35 | if(target && target.tagName !== 'BUTTON') 36 | (target 37 | .closest('.pseudocode-block') 38 | ?.parentElement?.parentElement 39 | ?.querySelector('.edit-block-button') as HTMLElement | null) 40 | ?.click(); 41 | }); 42 | 43 | // Extract inline macros 44 | const [inlineMacros, nonMacroLines] = extractInlineMacros(source); 45 | const allPreamble = this.preamble + inlineMacros; 46 | 47 | // find all $ enclosements in source, and add the preamble. 48 | // TODO: Might be able to optimize. 49 | const mathRegex = /\$(.*?)\$/g; 50 | const withPreamble = nonMacroLines.replace(mathRegex, (_, group1) => { 51 | return `$${allPreamble}${group1}$`; 52 | }); 53 | 54 | const preEl = blockDiv.createEl("pre", { 55 | cls: "code", 56 | text: withPreamble, 57 | }); 58 | 59 | try { 60 | pseudocode.renderElement(preEl, this.settings.jsSettings); 61 | createExportButton(this, blockDiv, inlineMacros, nonMacroLines); 62 | } catch (error) { 63 | console.log(error); 64 | const errorSpan = blockDiv.createEl("span", { 65 | text: "\u274C " + error.message, 66 | }); 67 | errorSpan.classList.add("error-message"); 68 | blockDiv.empty(); 69 | blockDiv.appendChild(errorSpan); 70 | } 71 | 72 | // Set the pseudocode theme 73 | if (this.settings.followSystemTheme) { 74 | setPseudocodeTheme(blockDiv); 75 | } 76 | } 77 | 78 | async onload() { 79 | await this.loadSettings(); 80 | 81 | setObserver(); 82 | 83 | if (this.settings.preambleEnabled) { 84 | console.log("Preamble is enabled."); 85 | await this.loadPreamble(); 86 | } 87 | 88 | this.registerMarkdownCodeBlockProcessor( 89 | BLOCK_NAME, 90 | this.pseudocodeHandler.bind(this) 91 | ); 92 | 93 | // Register suggest 94 | this.registerEditorSuggest(new PseudocodeSuggestor(this)); 95 | 96 | // This adds a settings tab so the user can configure various aspects of the plugin 97 | this.addSettingTab(new PseudocodeSettingTab(this.app, this)); 98 | 99 | // Auto-gen pseudocode block command. 100 | this.addCommand({ 101 | id: "pseudocode-in-obs", 102 | name: "Insert a new pseudocode block", 103 | editorCallback: (editor: Editor, view: MarkdownView) => { 104 | editor.replaceSelection(PseudocodeBlockInit); 105 | }, 106 | }); 107 | } 108 | 109 | onunload() { 110 | detachObserver(); 111 | } 112 | 113 | async loadPreamble() { 114 | try { 115 | this.preamble = await this.app.vault.adapter.read( 116 | this.settings.preamblePath 117 | ); 118 | } catch (error) { 119 | console.log(error); 120 | // Extract the search path from the error message. 121 | const searchPath = error.message.match(/'(.*?)'/g)[0]; 122 | new Notice( 123 | "Pseudocode Plugin: Preamble file not found at " + 124 | searchPath + 125 | "." 126 | ); 127 | this.preamble = ""; 128 | return; 129 | } 130 | 131 | try { 132 | this.preamble = translateUnsupportedMacrosPerf(this.preamble); 133 | this.preamble = checkTranslatedMacros(this.preamble); 134 | console.log("Loaded preamble:\n" + this.preamble); 135 | console.log( 136 | "Preamble file loaded. You can check the detail in console." 137 | ); 138 | if (this.settings.preambleLoadedNotice) { 139 | new Notice("Pseudocode Plugin: Preamble file loaded."); 140 | } 141 | } catch (error) { 142 | console.log(error); 143 | new Notice( 144 | "Pseudocode Plugin: Preamble file contains invalid LaTeX. " + 145 | "Please refer to console for details." 146 | ); 147 | this.preamble = ""; 148 | } 149 | } 150 | 151 | async loadSettings() { 152 | this.settings = Object.assign( 153 | {}, 154 | DEFAULT_SETTINGS, 155 | await this.loadData() 156 | ); 157 | } 158 | 159 | async saveSettings() { 160 | await this.saveData(this.settings); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/auto_complete.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Editor, 3 | EditorPosition, 4 | EditorSuggest, 5 | EditorSuggestContext, 6 | EditorSuggestTriggerInfo, 7 | TFile, 8 | } from "obsidian"; 9 | 10 | import { BLOCK_NAME } from "src/setting"; 11 | 12 | import PseudocodePlugin from "main"; 13 | 14 | export class PseudocodeSuggestor extends EditorSuggest { 15 | plugin: PseudocodePlugin; 16 | 17 | constructor(plugin: PseudocodePlugin) { 18 | super(plugin.app); 19 | this.plugin = plugin; 20 | } 21 | 22 | onTrigger( 23 | cursor: EditorPosition, 24 | editor: Editor, 25 | file: TFile 26 | ): EditorSuggestTriggerInfo | null { 27 | // perf: Use the "\" to tell whether to return. 28 | const currentLineToCursor = editor 29 | .getLine(cursor.line) 30 | .slice(0, cursor.ch); 31 | const currentLineLastWordStart = currentLineToCursor.lastIndexOf("\\"); 32 | // if there is no word, return null 33 | if (currentLineLastWordStart === -1) return null; 34 | 35 | // If is within a LaTeX $$ wrap, return null 36 | const currentLineLastMoneyMark = currentLineToCursor.lastIndexOf("$"); 37 | if (currentLineLastMoneyMark != -1) return null; 38 | 39 | const currentFileToCursor = editor.getRange({ line: 0, ch: 0 }, cursor); 40 | const indexOfLastCodeBlockStart = 41 | currentFileToCursor.lastIndexOf("```"); 42 | 43 | // check if this is a pseudocode block 44 | const isPseudocode = 45 | currentFileToCursor.slice( 46 | indexOfLastCodeBlockStart + 3, 47 | indexOfLastCodeBlockStart + 3 + BLOCK_NAME.length 48 | ) == BLOCK_NAME; 49 | 50 | if (!isPseudocode) return null; 51 | 52 | return { 53 | start: { line: cursor.line, ch: currentLineLastWordStart }, 54 | end: cursor, 55 | query: currentLineToCursor.slice(currentLineLastWordStart), 56 | }; 57 | } 58 | 59 | getSuggestions( 60 | context: EditorSuggestContext 61 | ): string[] | Promise { 62 | const query = context.query; 63 | 64 | const suggestions = this.pseudocodeKeywords.filter((value) => 65 | value.toLowerCase().startsWith(query.toLowerCase()) 66 | ); 67 | 68 | return suggestions; 69 | } 70 | 71 | renderSuggestion(value: string, el: HTMLElement): void { 72 | el.addClass("suggestion"); 73 | const suggestContent = el.createDiv({ cls: "suggestion-content" }); 74 | suggestContent.setText(value); 75 | } 76 | 77 | selectSuggestion(value: string, evt: MouseEvent | KeyboardEvent): void { 78 | if (this.context) { 79 | const editor = this.context.editor; 80 | const suggestion = value; 81 | const start = this.context.start; 82 | const end = editor.getCursor(); 83 | 84 | const pairSuggestion = this.pairSuggestions[suggestion]; 85 | let insertText = suggestion; 86 | if (pairSuggestion) { 87 | const line = editor.getLine(start.line); 88 | const indentMatch = line.match(/^(\s*)/)?.[0] ?? ""; 89 | const indent = indentMatch.replace(/\t/g, " "); // replace each tab with four spaces 90 | insertText += "\n" + indent + pairSuggestion; 91 | } 92 | 93 | editor.replaceRange(insertText, start, end); 94 | const newCursor = end; 95 | newCursor.ch = start.ch + suggestion.length; 96 | 97 | editor.setCursor(newCursor); 98 | 99 | this.close(); 100 | } 101 | } 102 | 103 | private pairSuggestions: Record = { 104 | "\\begin{algorithmic}": "\\end{algorithmic}", 105 | "\\begin{algorithm}": "\\end{algorithm}", 106 | "\\Procedure{}{}": "\\EndProcedure", 107 | "\\Function{}{}": "\\EndFunction", 108 | "\\For{}": "\\EndFor", 109 | "\\ForAll{}": "\\EndFor", 110 | "\\If{}": "\\EndIf", 111 | "\\While{}": "\\EndWhile", 112 | "\\Repeat": "\\Until{}", 113 | // Add more pairs as needed 114 | }; 115 | 116 | private pseudocodeKeywords: string[] = [ 117 | "\\begin{algorithmic}", 118 | "\\begin{algorithm}", 119 | "\\end{algorithmic}", 120 | "\\end{algorithm}", 121 | "\\caption{}", 122 | "\\Procedure{}{}", 123 | "\\EndProcedure", 124 | "\\Function{}{}", 125 | "\\EndFunction", 126 | "\\Require", 127 | "\\Ensure", 128 | "\\Input", 129 | "\\Output", 130 | "\\State", 131 | "\\Return", 132 | "\\Print", 133 | "\\For{}", 134 | "\\ForAll{}", 135 | "\\EndFor", 136 | "\\If{}", 137 | "\\Else", 138 | "\\Elif{}", 139 | "\\EndIf", 140 | "\\While{}", 141 | "\\EndWhile", 142 | "\\Repeat", 143 | "\\Continue", 144 | "\\Break", 145 | "\\Until{}", 146 | "\\Comment{}", 147 | "\\{", 148 | "\\}", 149 | "\\$", 150 | "\\&", 151 | "\\#", 152 | "\\%", 153 | "\\_", 154 | "\\gets", 155 | "\\Call{}{}", 156 | "\\And", 157 | "\\Or", 158 | "\\Xor", 159 | "\\Not", 160 | "\\To", 161 | "\\DownTo", 162 | "\\True", 163 | "\\False", 164 | "\\tiny", 165 | "\\scriptsize", 166 | "\\footnotesize", 167 | "\\small", 168 | "\\normalsize", 169 | "\\Large", 170 | "\\Huge", 171 | "\\rmfamily", 172 | "\\sffamily", 173 | "\\ttfamily", 174 | "\\upshape", 175 | "\\itshape", 176 | "\\slshape", 177 | "\\scshape", 178 | "\\bfseries", 179 | "\\mdseries", 180 | "\\lfseries", 181 | "\\textnormal{}", 182 | "\\textrm{}", 183 | "\\textsf{}", 184 | "\\texttt{}", 185 | "\\textup{}", 186 | "\\textit{}", 187 | "\\textsl{}", 188 | "\\textsc{}", 189 | "\\uppercase{}", 190 | "\\lowercase{}", 191 | "\\textbf{}", 192 | "\\textmd{}", 193 | "\\textlf{}", 194 | ]; 195 | } 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian-Pseudocode 2 | - [Obsidian-Pseudocode](#obsidian-pseudocode) 3 | - [Features](#features) 4 | - [Future Features](#future-features) 5 | - [Usage](#usage) 6 | - [Basic](#basic) 7 | - [Preamble style customization](#preamble-style-customization) 8 | - [Use a `.sty` file](#use-a-sty-file) 9 | - [Use in-block preamble](#use-in-block-preamble) 10 | - [Supported macros](#supported-macros) 11 | - [Export to a compilable LaTeX file](#export-to-a-compilable-latex-file) 12 | - [Installation](#installation) 13 | - [Credits](#credits) 14 | 15 | This is a plugin for [Obsidian](https://obsidian.md/) that allows you to render LaTeX-style pseudocode inside a code block. The plugin is based on [pseudocode.js](https://github.com/SaswatPadhi/pseudocode.js), a JavaScript library that typesets pseudocode beautifully to HTML. 16 | 17 | ## Features 18 | 19 | - Intuitive grammar: The plugin takes a LaTeX-style input that supports the algorithmic constructs from LaTeX's algorithm packages. With or without LaTeX experience, a user should find the grammar fairly intuitive. 20 | - Print quality: The HTML output produced by the plugin is (almost) identical with the pretty algorithms printed on publications that are typeset by LaTeX. 21 | - Math formula support: Inserting math formulas in the pseudocode is as easy as LaTeX. Just enclose math expression in `$...$` or `\(...\)`. 22 | - Auto-completion inside `pseudo` code block. (Release 1.1.0) 23 | - [Preamble style (macros) customization.](#preamble-style-customization) (Release 1.2.0 & 1.5.0) 24 | - [Export a LaTeX file that can be compiled, including any required additional macros.](#export-to-a-compilable-latex-file) (Release 1.3.0) 25 | - Pseudocode block follows Obsidian theme and supports both light and dark ones. (Release 1.6.0) 26 | 27 | ### Future Features 28 | 29 | - [ ] Syntax highlighting. 30 | 31 | ## Usage 32 | 33 | ### Basic 34 | 35 | To use the plugin, simply create a code block in your Obsidian note and add your pseudocode inside it. Then, add the language specifier `pseudo` (short for "pseudocode") to the code block. The plugin will automatically render the pseudocode as LaTeX. 36 | 37 | **Rocommend: use the command `Pseudocode: Insert a new pseudocode block` to start.** 38 | 39 | Here is an example: 40 | 41 | ``` 42 | ```pseudo 43 | \begin{algorithm} 44 | \caption{Quicksort} 45 | \begin{algorithmic} 46 | \Procedure{Quicksort}{$A, p, r$} 47 | \If{$p < r$} 48 | \State $q \gets $ \Call{Partition}{$A, p, r$} 49 | \State \Call{Quicksort}{$A, p, q - 1$} 50 | \State \Call{Quicksort}{$A, q + 1, r$} 51 | \EndIf 52 | \EndProcedure 53 | \Procedure{Partition}{$A, p, r$} 54 | \State $x \gets A[r]$ 55 | \State $i \gets p - 1$ 56 | \For{$j \gets p$ \To $r - 1$} 57 | \If{$A[j] < x$} 58 | \State $i \gets i + 1$ 59 | \State exchange 60 | $A[i]$ with $A[j]$ 61 | \EndIf 62 | \State exchange $A[i]$ with $A[r]$ 63 | \EndFor 64 | \EndProcedure 65 | \end{algorithmic} 66 | \end{algorithm} 67 | ``` 68 | ``` 69 | 70 | This will be rendered as (to render line number, you need to toggle it in setting tab): 71 | 72 |
73 | example 74 |
75 | 76 | ### Preamble style customization 77 | 78 | #### Use a `.sty` file 79 | 80 | You can use a `.sty` file (actually the suffix does not matter) to customize with some macros. The plugin will locate the file according to the setting. The default path is `preamble.sty`. 81 | 82 | Please reload the plugin after you change the preamble file. 83 | 84 | #### Use in-block preamble 85 | 86 | You can simply write your own macros in the pseudocode block before `\begin{algorithm}`. These macros will only be applicable within this specific block. 87 | 88 | #### Supported macros 89 | 90 | Currently supported macros can be found at [this link](https://katex.org/docs/supported.html#macros) and below(might not be fully supported): 91 | 92 | 1. `\DeclarePairedDelimiter` 93 | 2. `\DeclareMathOperator*` 94 | 3. `\DeclareMathOperator` 95 | 96 | 97 | ### Export to a compilable LaTeX file 98 | 99 | You can easily export a compilable LaTeX file by clicking the `Export to clipboard` button at the bottom right corner for each pseudocode block. The plugin will automatically generate a compilable LaTeX file, including any required additional macros, to your clipboard. 100 | 101 | 102 | ## Installation 103 | 104 | 105 | 106 | :tada: The Pseudocode plugin is now available in the Community Plugins section of Obsidian. To install it, simply search for **Pseudocode** and click on the installation button. 107 | 108 | 122 | 123 | 124 | 125 | ## Credits 126 | 127 | This plugin is based on [pseudocode.js](https://github.com/SaswatPadhi/pseudocode.js), a JavaScript library that typesets pseudocode beautifully to HTML. Many thanks to the pseudocode.js team for their great work! 128 | -------------------------------------------------------------------------------- /src/setting_tab.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from "obsidian"; 2 | import { setObserver, detachObserver } from "./theme"; 3 | 4 | import PseudocodePlugin from "main"; 5 | 6 | export class PseudocodeSettingTab extends PluginSettingTab { 7 | plugin: PseudocodePlugin; 8 | 9 | constructor(app: App, plugin: PseudocodePlugin) { 10 | super(app, plugin); 11 | this.plugin = plugin; 12 | } 13 | 14 | display(): void { 15 | const { containerEl } = this; 16 | 17 | containerEl.empty(); 18 | 19 | containerEl.createEl("h1", { text: "Pseudocode Plugin Settings" }); 20 | 21 | containerEl.createEl("h2", { text: "Render Behevior" }); 22 | 23 | // Instantiate Block Size setting 24 | new Setting(containerEl) 25 | .setName("Block Size") 26 | .setDesc( 27 | "The width of the pseudocode block. The unit is 'em'." + 28 | " The default value is 99, which will work as the max width of the editor." + 29 | " '30' looks good for me." 30 | ) 31 | .addText((text) => 32 | text 33 | .setValue(this.plugin.settings.blockSize.toString()) 34 | .onChange(async (value) => { 35 | this.plugin.settings.blockSize = Number(value); 36 | await this.plugin.saveSettings(); 37 | }) 38 | ); 39 | 40 | // Instantiate Follow System Theme setting 41 | new Setting(containerEl) 42 | .setName("Follow System Theme") 43 | .setDesc("Whether to follow the system theme.") 44 | .addToggle((toggle) => 45 | toggle 46 | .setValue(this.plugin.settings.followSystemTheme) 47 | .onChange(async (value) => { 48 | this.plugin.settings.followSystemTheme = value; 49 | if (value) { 50 | setObserver(); 51 | } else { 52 | detachObserver(); 53 | } 54 | await this.plugin.saveSettings(); 55 | }) 56 | ); 57 | 58 | // Instantiate Indent Size setting 59 | new Setting(containerEl) 60 | .setName("Indent Size") 61 | .setDesc( 62 | "The indent size of inside a control block, e.g. if, for, etc. The unit must be in 'em'." 63 | ) 64 | .addText((text) => 65 | text 66 | .setValue(this.plugin.settings.jsSettings.indentSize) 67 | .onChange(async (value) => { 68 | this.plugin.settings.jsSettings.indentSize = value; 69 | await this.plugin.saveSettings(); 70 | }) 71 | ); 72 | 73 | // Instantiate Comment Delimiter setting 74 | new Setting(containerEl) 75 | .setName("Comment Delimiter") 76 | .setDesc("The string used to indicate a comment in the pseudocode.") 77 | .addText((text) => 78 | text 79 | .setValue(this.plugin.settings.jsSettings.commentDelimiter) 80 | .onChange(async (value) => { 81 | this.plugin.settings.jsSettings.commentDelimiter = 82 | value; 83 | await this.plugin.saveSettings(); 84 | }) 85 | ); 86 | 87 | // Instantiate Show Line Numbers setting 88 | new Setting(containerEl) 89 | .setName("Show Line Numbers") 90 | .setDesc("Whether line numbering is enabled.") 91 | .addToggle((toggle) => 92 | toggle 93 | .setValue(this.plugin.settings.jsSettings.lineNumber) 94 | .onChange(async (value) => { 95 | this.plugin.settings.jsSettings.lineNumber = value; 96 | await this.plugin.saveSettings(); 97 | }) 98 | ); 99 | 100 | // Instantiate Line Number Punctuation setting 101 | new Setting(containerEl) 102 | .setName("Line Number Punctuation") 103 | .setDesc( 104 | "The punctuation used to separate the line number from the pseudocode." 105 | ) 106 | .addText((text) => 107 | text 108 | .setValue(this.plugin.settings.jsSettings.lineNumberPunc) 109 | .onChange(async (value) => { 110 | this.plugin.settings.jsSettings.lineNumberPunc = value; 111 | await this.plugin.saveSettings(); 112 | }) 113 | ); 114 | 115 | // Instantiate No End setting 116 | new Setting(containerEl) 117 | .setName("No End") 118 | .setDesc( 119 | "If enabled, pseudocode blocks will not have an 'end' statement." 120 | ) 121 | .addToggle((toggle) => 122 | toggle 123 | .setValue(this.plugin.settings.jsSettings.noEnd) 124 | .onChange(async (value) => { 125 | this.plugin.settings.jsSettings.noEnd = value; 126 | await this.plugin.saveSettings(); 127 | }) 128 | ); 129 | 130 | // Instantiate scope lines toggle setting 131 | new Setting(containerEl) 132 | .setName("Scope Lines") 133 | .setDesc( 134 | "If enabled, pseudocode blocks will have lines indicating the scope of the block." 135 | ) 136 | .addToggle((toggle) => 137 | toggle 138 | .setValue(this.plugin.settings.jsSettings.scopeLines) 139 | .onChange(async (value) => { 140 | this.plugin.settings.jsSettings.scopeLines = value; 141 | await this.plugin.saveSettings(); 142 | }) 143 | ); 144 | 145 | containerEl.createEl("h2", { text: "Preamble Settings" }); 146 | 147 | // Instantiate Preamble Enabled setting 148 | new Setting(containerEl) 149 | .setName("Preamble Enabled") 150 | .setDesc( 151 | "Whether to load the preamble file. Please reload the plugin for this setting to take effect." 152 | ) 153 | .addToggle((toggle) => 154 | toggle 155 | .setValue(this.plugin.settings.preambleEnabled) 156 | .onChange(async (value) => { 157 | this.plugin.settings.preambleEnabled = value; 158 | await this.plugin.saveSettings(); 159 | }) 160 | ); 161 | 162 | // Instantiate Preamble Path setting 163 | new Setting(containerEl) 164 | .setName("Preamble Path") 165 | .setDesc( 166 | "The path to the preamble file. The path is relative to the vault root." 167 | ) 168 | .addText((text) => 169 | text 170 | // .setValue(this.plugin.settings.preamblePath) 171 | .setValue(this.plugin.settings.preamblePath) 172 | .onChange(async (value) => { 173 | this.plugin.settings.preamblePath = value; 174 | await this.plugin.saveSettings(); 175 | }) 176 | ); 177 | 178 | // Instantiate Preamble Load Sign setting 179 | new Setting(containerEl) 180 | .setName("Preamble Loaded Notice") 181 | .setDesc( 182 | "Whether to show a notice everytime the preamble is loaded." 183 | ) 184 | .addToggle((toggle) => 185 | toggle 186 | .setValue(this.plugin.settings.preambleLoadedNotice) 187 | .onChange(async (value) => { 188 | this.plugin.settings.preambleLoadedNotice = value; 189 | await this.plugin.saveSettings(); 190 | }) 191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /katex.min.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: KaTeX_AMS; 3 | src: url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"), url(fonts/KaTeX_AMS-Regular.woff) format("woff"), url(fonts/KaTeX_AMS-Regular.ttf) format("truetype"); 4 | font-weight: 400; 5 | font-style: normal 6 | } 7 | 8 | @font-face { 9 | font-family: KaTeX_Caligraphic; 10 | src: url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"), url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"), url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype"); 11 | font-weight: 700; 12 | font-style: normal 13 | } 14 | 15 | @font-face { 16 | font-family: KaTeX_Caligraphic; 17 | src: url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"), url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"), url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype"); 18 | font-weight: 400; 19 | font-style: normal 20 | } 21 | 22 | @font-face { 23 | font-family: KaTeX_Fraktur; 24 | src: url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"), url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"), url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype"); 25 | font-weight: 700; 26 | font-style: normal 27 | } 28 | 29 | @font-face { 30 | font-family: KaTeX_Fraktur; 31 | src: url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"), url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"), url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype"); 32 | font-weight: 400; 33 | font-style: normal 34 | } 35 | 36 | @font-face { 37 | font-family: KaTeX_Main; 38 | src: url(fonts/KaTeX_Main-Bold.woff2) format("woff2"), url(fonts/KaTeX_Main-Bold.woff) format("woff"), url(fonts/KaTeX_Main-Bold.ttf) format("truetype"); 39 | font-weight: 700; 40 | font-style: normal 41 | } 42 | 43 | @font-face { 44 | font-family: KaTeX_Main; 45 | src: url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"), url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"), url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype"); 46 | font-weight: 700; 47 | font-style: italic 48 | } 49 | 50 | @font-face { 51 | font-family: KaTeX_Main; 52 | src: url(fonts/KaTeX_Main-Italic.woff2) format("woff2"), url(fonts/KaTeX_Main-Italic.woff) format("woff"), url(fonts/KaTeX_Main-Italic.ttf) format("truetype"); 53 | font-weight: 400; 54 | font-style: italic 55 | } 56 | 57 | @font-face { 58 | font-family: KaTeX_Main; 59 | src: url(fonts/KaTeX_Main-Regular.woff2) format("woff2"), url(fonts/KaTeX_Main-Regular.woff) format("woff"), url(fonts/KaTeX_Main-Regular.ttf) format("truetype"); 60 | font-weight: 400; 61 | font-style: normal 62 | } 63 | 64 | @font-face { 65 | font-family: KaTeX_Math; 66 | src: url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"), url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"), url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype"); 67 | font-weight: 700; 68 | font-style: italic 69 | } 70 | 71 | @font-face { 72 | font-family: KaTeX_Math; 73 | src: url(fonts/KaTeX_Math-Italic.woff2) format("woff2"), url(fonts/KaTeX_Math-Italic.woff) format("woff"), url(fonts/KaTeX_Math-Italic.ttf) format("truetype"); 74 | font-weight: 400; 75 | font-style: italic 76 | } 77 | 78 | @font-face { 79 | font-family: "KaTeX_SansSerif"; 80 | src: url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"), url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"), url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype"); 81 | font-weight: 700; 82 | font-style: normal 83 | } 84 | 85 | @font-face { 86 | font-family: "KaTeX_SansSerif"; 87 | src: url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"), url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"), url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype"); 88 | font-weight: 400; 89 | font-style: italic 90 | } 91 | 92 | @font-face { 93 | font-family: "KaTeX_SansSerif"; 94 | src: url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"), url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"), url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype"); 95 | font-weight: 400; 96 | font-style: normal 97 | } 98 | 99 | @font-face { 100 | font-family: KaTeX_Script; 101 | src: url(fonts/KaTeX_Script-Regular.woff2) format("woff2"), url(fonts/KaTeX_Script-Regular.woff) format("woff"), url(fonts/KaTeX_Script-Regular.ttf) format("truetype"); 102 | font-weight: 400; 103 | font-style: normal 104 | } 105 | 106 | @font-face { 107 | font-family: KaTeX_Size1; 108 | src: url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"), url(fonts/KaTeX_Size1-Regular.woff) format("woff"), url(fonts/KaTeX_Size1-Regular.ttf) format("truetype"); 109 | font-weight: 400; 110 | font-style: normal 111 | } 112 | 113 | @font-face { 114 | font-family: KaTeX_Size2; 115 | src: url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"), url(fonts/KaTeX_Size2-Regular.woff) format("woff"), url(fonts/KaTeX_Size2-Regular.ttf) format("truetype"); 116 | font-weight: 400; 117 | font-style: normal 118 | } 119 | 120 | @font-face { 121 | font-family: KaTeX_Size3; 122 | src: url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"), url(fonts/KaTeX_Size3-Regular.woff) format("woff"), url(fonts/KaTeX_Size3-Regular.ttf) format("truetype"); 123 | font-weight: 400; 124 | font-style: normal 125 | } 126 | 127 | @font-face { 128 | font-family: KaTeX_Size4; 129 | src: url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"), url(fonts/KaTeX_Size4-Regular.woff) format("woff"), url(fonts/KaTeX_Size4-Regular.ttf) format("truetype"); 130 | font-weight: 400; 131 | font-style: normal 132 | } 133 | 134 | @font-face { 135 | font-family: KaTeX_Typewriter; 136 | src: url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"), url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"), url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype"); 137 | font-weight: 400; 138 | font-style: normal 139 | } 140 | 141 | .katex { 142 | font: normal 1.21em KaTeX_Main, Times New Roman, serif; 143 | line-height: 1.2; 144 | text-indent: 0; 145 | text-rendering: auto 146 | } 147 | 148 | .katex * { 149 | -ms-high-contrast-adjust: none !important 150 | } 151 | 152 | .katex .katex-version:after { 153 | content: "0.11.1" 154 | } 155 | 156 | .katex .katex-mathml { 157 | position: absolute; 158 | clip: rect(1px, 1px, 1px, 1px); 159 | padding: 0; 160 | border: 0; 161 | height: 1px; 162 | width: 1px; 163 | overflow: hidden 164 | } 165 | 166 | .katex .katex-html>.newline { 167 | display: block 168 | } 169 | 170 | .katex .base { 171 | position: relative; 172 | white-space: nowrap; 173 | width: min-content 174 | } 175 | 176 | .katex .base, 177 | .katex .strut { 178 | display: inline-block 179 | } 180 | 181 | .katex .textbf { 182 | font-weight: 700 183 | } 184 | 185 | .katex .textit { 186 | font-style: italic 187 | } 188 | 189 | .katex .textrm { 190 | font-family: KaTeX_Main 191 | } 192 | 193 | .katex .textsf { 194 | font-family: KaTeX_SansSerif 195 | } 196 | 197 | .katex .texttt { 198 | font-family: KaTeX_Typewriter 199 | } 200 | 201 | .katex .mathdefault { 202 | font-family: KaTeX_Math; 203 | font-style: italic 204 | } 205 | 206 | .katex .mathit { 207 | font-family: KaTeX_Main; 208 | font-style: italic 209 | } 210 | 211 | .katex .mathrm { 212 | font-style: normal 213 | } 214 | 215 | .katex .mathbf { 216 | font-family: KaTeX_Main; 217 | font-weight: 700 218 | } 219 | 220 | .katex .boldsymbol { 221 | font-family: KaTeX_Math; 222 | font-weight: 700; 223 | font-style: italic 224 | } 225 | 226 | .katex .amsrm, 227 | .katex .mathbb, 228 | .katex .textbb { 229 | font-family: KaTeX_AMS 230 | } 231 | 232 | .katex .mathcal { 233 | font-family: KaTeX_Caligraphic 234 | } 235 | 236 | .katex .mathfrak, 237 | .katex .textfrak { 238 | font-family: KaTeX_Fraktur 239 | } 240 | 241 | .katex .mathtt { 242 | font-family: KaTeX_Typewriter 243 | } 244 | 245 | .katex .mathscr, 246 | .katex .textscr { 247 | font-family: KaTeX_Script 248 | } 249 | 250 | .katex .mathsf, 251 | .katex .textsf { 252 | font-family: KaTeX_SansSerif 253 | } 254 | 255 | .katex .mathboldsf, 256 | .katex .textboldsf { 257 | font-family: KaTeX_SansSerif; 258 | font-weight: 700 259 | } 260 | 261 | .katex .mathitsf, 262 | .katex .textitsf { 263 | font-family: KaTeX_SansSerif; 264 | font-style: italic 265 | } 266 | 267 | .katex .mainrm { 268 | font-family: KaTeX_Main; 269 | font-style: normal 270 | } 271 | 272 | .katex .vlist-t { 273 | display: inline-table; 274 | table-layout: fixed 275 | } 276 | 277 | .katex .vlist-r { 278 | display: table-row 279 | } 280 | 281 | .katex .vlist { 282 | display: table-cell; 283 | vertical-align: bottom; 284 | position: relative 285 | } 286 | 287 | .katex .vlist>span { 288 | display: block; 289 | height: 0; 290 | position: relative 291 | } 292 | 293 | .katex .vlist>span>span { 294 | display: inline-block 295 | } 296 | 297 | .katex .vlist>span>.pstrut { 298 | overflow: hidden; 299 | width: 0 300 | } 301 | 302 | .katex .vlist-t2 { 303 | margin-right: -2px 304 | } 305 | 306 | .katex .vlist-s { 307 | display: table-cell; 308 | vertical-align: bottom; 309 | font-size: 1px; 310 | width: 2px; 311 | min-width: 2px 312 | } 313 | 314 | .katex .msupsub { 315 | text-align: left 316 | } 317 | 318 | .katex .mfrac>span>span { 319 | text-align: center 320 | } 321 | 322 | .katex .mfrac .frac-line { 323 | display: inline-block; 324 | width: 100%; 325 | border-bottom-style: solid 326 | } 327 | 328 | .katex .hdashline, 329 | .katex .hline, 330 | .katex .mfrac .frac-line, 331 | .katex .overline .overline-line, 332 | .katex .rule, 333 | .katex .underline .underline-line { 334 | min-height: 1px 335 | } 336 | 337 | .katex .mspace { 338 | display: inline-block 339 | } 340 | 341 | .katex .clap, 342 | .katex .llap, 343 | .katex .rlap { 344 | width: 0; 345 | position: relative 346 | } 347 | 348 | .katex .clap>.inner, 349 | .katex .llap>.inner, 350 | .katex .rlap>.inner { 351 | position: absolute 352 | } 353 | 354 | .katex .clap>.fix, 355 | .katex .llap>.fix, 356 | .katex .rlap>.fix { 357 | display: inline-block 358 | } 359 | 360 | .katex .llap>.inner { 361 | right: 0 362 | } 363 | 364 | .katex .clap>.inner, 365 | .katex .rlap>.inner { 366 | left: 0 367 | } 368 | 369 | .katex .clap>.inner>span { 370 | margin-left: -50%; 371 | margin-right: 50% 372 | } 373 | 374 | .katex .rule { 375 | display: inline-block; 376 | border: 0 solid; 377 | position: relative 378 | } 379 | 380 | .katex .hline, 381 | .katex .overline .overline-line, 382 | .katex .underline .underline-line { 383 | display: inline-block; 384 | width: 100%; 385 | border-bottom-style: solid 386 | } 387 | 388 | .katex .hdashline { 389 | display: inline-block; 390 | width: 100%; 391 | border-bottom-style: dashed 392 | } 393 | 394 | .katex .sqrt>.root { 395 | margin-left: .27777778em; 396 | margin-right: -.55555556em 397 | } 398 | 399 | .katex .fontsize-ensurer.reset-size1.size1, 400 | .katex .sizing.reset-size1.size1 { 401 | font-size: 1em 402 | } 403 | 404 | .katex .fontsize-ensurer.reset-size1.size2, 405 | .katex .sizing.reset-size1.size2 { 406 | font-size: 1.2em 407 | } 408 | 409 | .katex .fontsize-ensurer.reset-size1.size3, 410 | .katex .sizing.reset-size1.size3 { 411 | font-size: 1.4em 412 | } 413 | 414 | .katex .fontsize-ensurer.reset-size1.size4, 415 | .katex .sizing.reset-size1.size4 { 416 | font-size: 1.6em 417 | } 418 | 419 | .katex .fontsize-ensurer.reset-size1.size5, 420 | .katex .sizing.reset-size1.size5 { 421 | font-size: 1.8em 422 | } 423 | 424 | .katex .fontsize-ensurer.reset-size1.size6, 425 | .katex .sizing.reset-size1.size6 { 426 | font-size: 2em 427 | } 428 | 429 | .katex .fontsize-ensurer.reset-size1.size7, 430 | .katex .sizing.reset-size1.size7 { 431 | font-size: 2.4em 432 | } 433 | 434 | .katex .fontsize-ensurer.reset-size1.size8, 435 | .katex .sizing.reset-size1.size8 { 436 | font-size: 2.88em 437 | } 438 | 439 | .katex .fontsize-ensurer.reset-size1.size9, 440 | .katex .sizing.reset-size1.size9 { 441 | font-size: 3.456em 442 | } 443 | 444 | .katex .fontsize-ensurer.reset-size1.size10, 445 | .katex .sizing.reset-size1.size10 { 446 | font-size: 4.148em 447 | } 448 | 449 | .katex .fontsize-ensurer.reset-size1.size11, 450 | .katex .sizing.reset-size1.size11 { 451 | font-size: 4.976em 452 | } 453 | 454 | .katex .fontsize-ensurer.reset-size2.size1, 455 | .katex .sizing.reset-size2.size1 { 456 | font-size: .83333333em 457 | } 458 | 459 | .katex .fontsize-ensurer.reset-size2.size2, 460 | .katex .sizing.reset-size2.size2 { 461 | font-size: 1em 462 | } 463 | 464 | .katex .fontsize-ensurer.reset-size2.size3, 465 | .katex .sizing.reset-size2.size3 { 466 | font-size: 1.16666667em 467 | } 468 | 469 | .katex .fontsize-ensurer.reset-size2.size4, 470 | .katex .sizing.reset-size2.size4 { 471 | font-size: 1.33333333em 472 | } 473 | 474 | .katex .fontsize-ensurer.reset-size2.size5, 475 | .katex .sizing.reset-size2.size5 { 476 | font-size: 1.5em 477 | } 478 | 479 | .katex .fontsize-ensurer.reset-size2.size6, 480 | .katex .sizing.reset-size2.size6 { 481 | font-size: 1.66666667em 482 | } 483 | 484 | .katex .fontsize-ensurer.reset-size2.size7, 485 | .katex .sizing.reset-size2.size7 { 486 | font-size: 2em 487 | } 488 | 489 | .katex .fontsize-ensurer.reset-size2.size8, 490 | .katex .sizing.reset-size2.size8 { 491 | font-size: 2.4em 492 | } 493 | 494 | .katex .fontsize-ensurer.reset-size2.size9, 495 | .katex .sizing.reset-size2.size9 { 496 | font-size: 2.88em 497 | } 498 | 499 | .katex .fontsize-ensurer.reset-size2.size10, 500 | .katex .sizing.reset-size2.size10 { 501 | font-size: 3.45666667em 502 | } 503 | 504 | .katex .fontsize-ensurer.reset-size2.size11, 505 | .katex .sizing.reset-size2.size11 { 506 | font-size: 4.14666667em 507 | } 508 | 509 | .katex .fontsize-ensurer.reset-size3.size1, 510 | .katex .sizing.reset-size3.size1 { 511 | font-size: .71428571em 512 | } 513 | 514 | .katex .fontsize-ensurer.reset-size3.size2, 515 | .katex .sizing.reset-size3.size2 { 516 | font-size: .85714286em 517 | } 518 | 519 | .katex .fontsize-ensurer.reset-size3.size3, 520 | .katex .sizing.reset-size3.size3 { 521 | font-size: 1em 522 | } 523 | 524 | .katex .fontsize-ensurer.reset-size3.size4, 525 | .katex .sizing.reset-size3.size4 { 526 | font-size: 1.14285714em 527 | } 528 | 529 | .katex .fontsize-ensurer.reset-size3.size5, 530 | .katex .sizing.reset-size3.size5 { 531 | font-size: 1.28571429em 532 | } 533 | 534 | .katex .fontsize-ensurer.reset-size3.size6, 535 | .katex .sizing.reset-size3.size6 { 536 | font-size: 1.42857143em 537 | } 538 | 539 | .katex .fontsize-ensurer.reset-size3.size7, 540 | .katex .sizing.reset-size3.size7 { 541 | font-size: 1.71428571em 542 | } 543 | 544 | .katex .fontsize-ensurer.reset-size3.size8, 545 | .katex .sizing.reset-size3.size8 { 546 | font-size: 2.05714286em 547 | } 548 | 549 | .katex .fontsize-ensurer.reset-size3.size9, 550 | .katex .sizing.reset-size3.size9 { 551 | font-size: 2.46857143em 552 | } 553 | 554 | .katex .fontsize-ensurer.reset-size3.size10, 555 | .katex .sizing.reset-size3.size10 { 556 | font-size: 2.96285714em 557 | } 558 | 559 | .katex .fontsize-ensurer.reset-size3.size11, 560 | .katex .sizing.reset-size3.size11 { 561 | font-size: 3.55428571em 562 | } 563 | 564 | .katex .fontsize-ensurer.reset-size4.size1, 565 | .katex .sizing.reset-size4.size1 { 566 | font-size: .625em 567 | } 568 | 569 | .katex .fontsize-ensurer.reset-size4.size2, 570 | .katex .sizing.reset-size4.size2 { 571 | font-size: .75em 572 | } 573 | 574 | .katex .fontsize-ensurer.reset-size4.size3, 575 | .katex .sizing.reset-size4.size3 { 576 | font-size: .875em 577 | } 578 | 579 | .katex .fontsize-ensurer.reset-size4.size4, 580 | .katex .sizing.reset-size4.size4 { 581 | font-size: 1em 582 | } 583 | 584 | .katex .fontsize-ensurer.reset-size4.size5, 585 | .katex .sizing.reset-size4.size5 { 586 | font-size: 1.125em 587 | } 588 | 589 | .katex .fontsize-ensurer.reset-size4.size6, 590 | .katex .sizing.reset-size4.size6 { 591 | font-size: 1.25em 592 | } 593 | 594 | .katex .fontsize-ensurer.reset-size4.size7, 595 | .katex .sizing.reset-size4.size7 { 596 | font-size: 1.5em 597 | } 598 | 599 | .katex .fontsize-ensurer.reset-size4.size8, 600 | .katex .sizing.reset-size4.size8 { 601 | font-size: 1.8em 602 | } 603 | 604 | .katex .fontsize-ensurer.reset-size4.size9, 605 | .katex .sizing.reset-size4.size9 { 606 | font-size: 2.16em 607 | } 608 | 609 | .katex .fontsize-ensurer.reset-size4.size10, 610 | .katex .sizing.reset-size4.size10 { 611 | font-size: 2.5925em 612 | } 613 | 614 | .katex .fontsize-ensurer.reset-size4.size11, 615 | .katex .sizing.reset-size4.size11 { 616 | font-size: 3.11em 617 | } 618 | 619 | .katex .fontsize-ensurer.reset-size5.size1, 620 | .katex .sizing.reset-size5.size1 { 621 | font-size: .55555556em 622 | } 623 | 624 | .katex .fontsize-ensurer.reset-size5.size2, 625 | .katex .sizing.reset-size5.size2 { 626 | font-size: .66666667em 627 | } 628 | 629 | .katex .fontsize-ensurer.reset-size5.size3, 630 | .katex .sizing.reset-size5.size3 { 631 | font-size: .77777778em 632 | } 633 | 634 | .katex .fontsize-ensurer.reset-size5.size4, 635 | .katex .sizing.reset-size5.size4 { 636 | font-size: .88888889em 637 | } 638 | 639 | .katex .fontsize-ensurer.reset-size5.size5, 640 | .katex .sizing.reset-size5.size5 { 641 | font-size: 1em 642 | } 643 | 644 | .katex .fontsize-ensurer.reset-size5.size6, 645 | .katex .sizing.reset-size5.size6 { 646 | font-size: 1.11111111em 647 | } 648 | 649 | .katex .fontsize-ensurer.reset-size5.size7, 650 | .katex .sizing.reset-size5.size7 { 651 | font-size: 1.33333333em 652 | } 653 | 654 | .katex .fontsize-ensurer.reset-size5.size8, 655 | .katex .sizing.reset-size5.size8 { 656 | font-size: 1.6em 657 | } 658 | 659 | .katex .fontsize-ensurer.reset-size5.size9, 660 | .katex .sizing.reset-size5.size9 { 661 | font-size: 1.92em 662 | } 663 | 664 | .katex .fontsize-ensurer.reset-size5.size10, 665 | .katex .sizing.reset-size5.size10 { 666 | font-size: 2.30444444em 667 | } 668 | 669 | .katex .fontsize-ensurer.reset-size5.size11, 670 | .katex .sizing.reset-size5.size11 { 671 | font-size: 2.76444444em 672 | } 673 | 674 | .katex .fontsize-ensurer.reset-size6.size1, 675 | .katex .sizing.reset-size6.size1 { 676 | font-size: .5em 677 | } 678 | 679 | .katex .fontsize-ensurer.reset-size6.size2, 680 | .katex .sizing.reset-size6.size2 { 681 | font-size: .6em 682 | } 683 | 684 | .katex .fontsize-ensurer.reset-size6.size3, 685 | .katex .sizing.reset-size6.size3 { 686 | font-size: .7em 687 | } 688 | 689 | .katex .fontsize-ensurer.reset-size6.size4, 690 | .katex .sizing.reset-size6.size4 { 691 | font-size: .8em 692 | } 693 | 694 | .katex .fontsize-ensurer.reset-size6.size5, 695 | .katex .sizing.reset-size6.size5 { 696 | font-size: .9em 697 | } 698 | 699 | .katex .fontsize-ensurer.reset-size6.size6, 700 | .katex .sizing.reset-size6.size6 { 701 | font-size: 1em 702 | } 703 | 704 | .katex .fontsize-ensurer.reset-size6.size7, 705 | .katex .sizing.reset-size6.size7 { 706 | font-size: 1.2em 707 | } 708 | 709 | .katex .fontsize-ensurer.reset-size6.size8, 710 | .katex .sizing.reset-size6.size8 { 711 | font-size: 1.44em 712 | } 713 | 714 | .katex .fontsize-ensurer.reset-size6.size9, 715 | .katex .sizing.reset-size6.size9 { 716 | font-size: 1.728em 717 | } 718 | 719 | .katex .fontsize-ensurer.reset-size6.size10, 720 | .katex .sizing.reset-size6.size10 { 721 | font-size: 2.074em 722 | } 723 | 724 | .katex .fontsize-ensurer.reset-size6.size11, 725 | .katex .sizing.reset-size6.size11 { 726 | font-size: 2.488em 727 | } 728 | 729 | .katex .fontsize-ensurer.reset-size7.size1, 730 | .katex .sizing.reset-size7.size1 { 731 | font-size: .41666667em 732 | } 733 | 734 | .katex .fontsize-ensurer.reset-size7.size2, 735 | .katex .sizing.reset-size7.size2 { 736 | font-size: .5em 737 | } 738 | 739 | .katex .fontsize-ensurer.reset-size7.size3, 740 | .katex .sizing.reset-size7.size3 { 741 | font-size: .58333333em 742 | } 743 | 744 | .katex .fontsize-ensurer.reset-size7.size4, 745 | .katex .sizing.reset-size7.size4 { 746 | font-size: .66666667em 747 | } 748 | 749 | .katex .fontsize-ensurer.reset-size7.size5, 750 | .katex .sizing.reset-size7.size5 { 751 | font-size: .75em 752 | } 753 | 754 | .katex .fontsize-ensurer.reset-size7.size6, 755 | .katex .sizing.reset-size7.size6 { 756 | font-size: .83333333em 757 | } 758 | 759 | .katex .fontsize-ensurer.reset-size7.size7, 760 | .katex .sizing.reset-size7.size7 { 761 | font-size: 1em 762 | } 763 | 764 | .katex .fontsize-ensurer.reset-size7.size8, 765 | .katex .sizing.reset-size7.size8 { 766 | font-size: 1.2em 767 | } 768 | 769 | .katex .fontsize-ensurer.reset-size7.size9, 770 | .katex .sizing.reset-size7.size9 { 771 | font-size: 1.44em 772 | } 773 | 774 | .katex .fontsize-ensurer.reset-size7.size10, 775 | .katex .sizing.reset-size7.size10 { 776 | font-size: 1.72833333em 777 | } 778 | 779 | .katex .fontsize-ensurer.reset-size7.size11, 780 | .katex .sizing.reset-size7.size11 { 781 | font-size: 2.07333333em 782 | } 783 | 784 | .katex .fontsize-ensurer.reset-size8.size1, 785 | .katex .sizing.reset-size8.size1 { 786 | font-size: .34722222em 787 | } 788 | 789 | .katex .fontsize-ensurer.reset-size8.size2, 790 | .katex .sizing.reset-size8.size2 { 791 | font-size: .41666667em 792 | } 793 | 794 | .katex .fontsize-ensurer.reset-size8.size3, 795 | .katex .sizing.reset-size8.size3 { 796 | font-size: .48611111em 797 | } 798 | 799 | .katex .fontsize-ensurer.reset-size8.size4, 800 | .katex .sizing.reset-size8.size4 { 801 | font-size: .55555556em 802 | } 803 | 804 | .katex .fontsize-ensurer.reset-size8.size5, 805 | .katex .sizing.reset-size8.size5 { 806 | font-size: .625em 807 | } 808 | 809 | .katex .fontsize-ensurer.reset-size8.size6, 810 | .katex .sizing.reset-size8.size6 { 811 | font-size: .69444444em 812 | } 813 | 814 | .katex .fontsize-ensurer.reset-size8.size7, 815 | .katex .sizing.reset-size8.size7 { 816 | font-size: .83333333em 817 | } 818 | 819 | .katex .fontsize-ensurer.reset-size8.size8, 820 | .katex .sizing.reset-size8.size8 { 821 | font-size: 1em 822 | } 823 | 824 | .katex .fontsize-ensurer.reset-size8.size9, 825 | .katex .sizing.reset-size8.size9 { 826 | font-size: 1.2em 827 | } 828 | 829 | .katex .fontsize-ensurer.reset-size8.size10, 830 | .katex .sizing.reset-size8.size10 { 831 | font-size: 1.44027778em 832 | } 833 | 834 | .katex .fontsize-ensurer.reset-size8.size11, 835 | .katex .sizing.reset-size8.size11 { 836 | font-size: 1.72777778em 837 | } 838 | 839 | .katex .fontsize-ensurer.reset-size9.size1, 840 | .katex .sizing.reset-size9.size1 { 841 | font-size: .28935185em 842 | } 843 | 844 | .katex .fontsize-ensurer.reset-size9.size2, 845 | .katex .sizing.reset-size9.size2 { 846 | font-size: .34722222em 847 | } 848 | 849 | .katex .fontsize-ensurer.reset-size9.size3, 850 | .katex .sizing.reset-size9.size3 { 851 | font-size: .40509259em 852 | } 853 | 854 | .katex .fontsize-ensurer.reset-size9.size4, 855 | .katex .sizing.reset-size9.size4 { 856 | font-size: .46296296em 857 | } 858 | 859 | .katex .fontsize-ensurer.reset-size9.size5, 860 | .katex .sizing.reset-size9.size5 { 861 | font-size: .52083333em 862 | } 863 | 864 | .katex .fontsize-ensurer.reset-size9.size6, 865 | .katex .sizing.reset-size9.size6 { 866 | font-size: .5787037em 867 | } 868 | 869 | .katex .fontsize-ensurer.reset-size9.size7, 870 | .katex .sizing.reset-size9.size7 { 871 | font-size: .69444444em 872 | } 873 | 874 | .katex .fontsize-ensurer.reset-size9.size8, 875 | .katex .sizing.reset-size9.size8 { 876 | font-size: .83333333em 877 | } 878 | 879 | .katex .fontsize-ensurer.reset-size9.size9, 880 | .katex .sizing.reset-size9.size9 { 881 | font-size: 1em 882 | } 883 | 884 | .katex .fontsize-ensurer.reset-size9.size10, 885 | .katex .sizing.reset-size9.size10 { 886 | font-size: 1.20023148em 887 | } 888 | 889 | .katex .fontsize-ensurer.reset-size9.size11, 890 | .katex .sizing.reset-size9.size11 { 891 | font-size: 1.43981481em 892 | } 893 | 894 | .katex .fontsize-ensurer.reset-size10.size1, 895 | .katex .sizing.reset-size10.size1 { 896 | font-size: .24108004em 897 | } 898 | 899 | .katex .fontsize-ensurer.reset-size10.size2, 900 | .katex .sizing.reset-size10.size2 { 901 | font-size: .28929605em 902 | } 903 | 904 | .katex .fontsize-ensurer.reset-size10.size3, 905 | .katex .sizing.reset-size10.size3 { 906 | font-size: .33751205em 907 | } 908 | 909 | .katex .fontsize-ensurer.reset-size10.size4, 910 | .katex .sizing.reset-size10.size4 { 911 | font-size: .38572806em 912 | } 913 | 914 | .katex .fontsize-ensurer.reset-size10.size5, 915 | .katex .sizing.reset-size10.size5 { 916 | font-size: .43394407em 917 | } 918 | 919 | .katex .fontsize-ensurer.reset-size10.size6, 920 | .katex .sizing.reset-size10.size6 { 921 | font-size: .48216008em 922 | } 923 | 924 | .katex .fontsize-ensurer.reset-size10.size7, 925 | .katex .sizing.reset-size10.size7 { 926 | font-size: .57859209em 927 | } 928 | 929 | .katex .fontsize-ensurer.reset-size10.size8, 930 | .katex .sizing.reset-size10.size8 { 931 | font-size: .69431051em 932 | } 933 | 934 | .katex .fontsize-ensurer.reset-size10.size9, 935 | .katex .sizing.reset-size10.size9 { 936 | font-size: .83317261em 937 | } 938 | 939 | .katex .fontsize-ensurer.reset-size10.size10, 940 | .katex .sizing.reset-size10.size10 { 941 | font-size: 1em 942 | } 943 | 944 | .katex .fontsize-ensurer.reset-size10.size11, 945 | .katex .sizing.reset-size10.size11 { 946 | font-size: 1.19961427em 947 | } 948 | 949 | .katex .fontsize-ensurer.reset-size11.size1, 950 | .katex .sizing.reset-size11.size1 { 951 | font-size: .20096463em 952 | } 953 | 954 | .katex .fontsize-ensurer.reset-size11.size2, 955 | .katex .sizing.reset-size11.size2 { 956 | font-size: .24115756em 957 | } 958 | 959 | .katex .fontsize-ensurer.reset-size11.size3, 960 | .katex .sizing.reset-size11.size3 { 961 | font-size: .28135048em 962 | } 963 | 964 | .katex .fontsize-ensurer.reset-size11.size4, 965 | .katex .sizing.reset-size11.size4 { 966 | font-size: .32154341em 967 | } 968 | 969 | .katex .fontsize-ensurer.reset-size11.size5, 970 | .katex .sizing.reset-size11.size5 { 971 | font-size: .36173633em 972 | } 973 | 974 | .katex .fontsize-ensurer.reset-size11.size6, 975 | .katex .sizing.reset-size11.size6 { 976 | font-size: .40192926em 977 | } 978 | 979 | .katex .fontsize-ensurer.reset-size11.size7, 980 | .katex .sizing.reset-size11.size7 { 981 | font-size: .48231511em 982 | } 983 | 984 | .katex .fontsize-ensurer.reset-size11.size8, 985 | .katex .sizing.reset-size11.size8 { 986 | font-size: .57877814em 987 | } 988 | 989 | .katex .fontsize-ensurer.reset-size11.size9, 990 | .katex .sizing.reset-size11.size9 { 991 | font-size: .69453376em 992 | } 993 | 994 | .katex .fontsize-ensurer.reset-size11.size10, 995 | .katex .sizing.reset-size11.size10 { 996 | font-size: .83360129em 997 | } 998 | 999 | .katex .fontsize-ensurer.reset-size11.size11, 1000 | .katex .sizing.reset-size11.size11 { 1001 | font-size: 1em 1002 | } 1003 | 1004 | .katex .delimsizing.size1 { 1005 | font-family: KaTeX_Size1 1006 | } 1007 | 1008 | .katex .delimsizing.size2 { 1009 | font-family: KaTeX_Size2 1010 | } 1011 | 1012 | .katex .delimsizing.size3 { 1013 | font-family: KaTeX_Size3 1014 | } 1015 | 1016 | .katex .delimsizing.size4 { 1017 | font-family: KaTeX_Size4 1018 | } 1019 | 1020 | .katex .delimsizing.mult .delim-size1>span { 1021 | font-family: KaTeX_Size1 1022 | } 1023 | 1024 | .katex .delimsizing.mult .delim-size4>span { 1025 | font-family: KaTeX_Size4 1026 | } 1027 | 1028 | .katex .nulldelimiter { 1029 | display: inline-block; 1030 | width: .12em 1031 | } 1032 | 1033 | .katex .delimcenter, 1034 | .katex .op-symbol { 1035 | position: relative 1036 | } 1037 | 1038 | .katex .op-symbol.small-op { 1039 | font-family: KaTeX_Size1 1040 | } 1041 | 1042 | .katex .op-symbol.large-op { 1043 | font-family: KaTeX_Size2 1044 | } 1045 | 1046 | .katex .op-limits>.vlist-t { 1047 | text-align: center 1048 | } 1049 | 1050 | .katex .accent>.vlist-t { 1051 | text-align: center 1052 | } 1053 | 1054 | .katex .accent .accent-body { 1055 | position: relative 1056 | } 1057 | 1058 | .katex .accent .accent-body:not(.accent-full) { 1059 | width: 0 1060 | } 1061 | 1062 | .katex .overlay { 1063 | display: block 1064 | } 1065 | 1066 | .katex .mtable .vertical-separator { 1067 | display: inline-block; 1068 | min-width: 1px 1069 | } 1070 | 1071 | .katex .mtable .arraycolsep { 1072 | display: inline-block 1073 | } 1074 | 1075 | .katex .mtable .col-align-c>.vlist-t { 1076 | text-align: center 1077 | } 1078 | 1079 | .katex .mtable .col-align-l>.vlist-t { 1080 | text-align: left 1081 | } 1082 | 1083 | .katex .mtable .col-align-r>.vlist-t { 1084 | text-align: right 1085 | } 1086 | 1087 | .katex .svg-align { 1088 | text-align: left 1089 | } 1090 | 1091 | .katex svg { 1092 | display: block; 1093 | position: absolute; 1094 | width: 100%; 1095 | height: inherit; 1096 | fill: currentColor; 1097 | stroke: currentColor; 1098 | fill-rule: nonzero; 1099 | fill-opacity: 1; 1100 | stroke-width: 1; 1101 | stroke-linecap: butt; 1102 | stroke-linejoin: miter; 1103 | stroke-miterlimit: 4; 1104 | stroke-dasharray: none; 1105 | stroke-dashoffset: 0; 1106 | stroke-opacity: 1 1107 | } 1108 | 1109 | .katex svg path { 1110 | stroke: none 1111 | } 1112 | 1113 | .katex img { 1114 | border-style: none; 1115 | min-width: 0; 1116 | min-height: 0; 1117 | max-width: none; 1118 | max-height: none 1119 | } 1120 | 1121 | .katex .stretchy { 1122 | width: 100%; 1123 | display: block; 1124 | position: relative; 1125 | overflow: hidden 1126 | } 1127 | 1128 | .katex .stretchy:after, 1129 | .katex .stretchy:before { 1130 | content: "" 1131 | } 1132 | 1133 | .katex .hide-tail { 1134 | width: 100%; 1135 | position: relative; 1136 | overflow: hidden 1137 | } 1138 | 1139 | .katex .halfarrow-left { 1140 | position: absolute; 1141 | left: 0; 1142 | width: 50.2%; 1143 | overflow: hidden 1144 | } 1145 | 1146 | .katex .halfarrow-right { 1147 | position: absolute; 1148 | right: 0; 1149 | width: 50.2%; 1150 | overflow: hidden 1151 | } 1152 | 1153 | .katex .brace-left { 1154 | position: absolute; 1155 | left: 0; 1156 | width: 25.1%; 1157 | overflow: hidden 1158 | } 1159 | 1160 | .katex .brace-center { 1161 | position: absolute; 1162 | left: 25%; 1163 | width: 50%; 1164 | overflow: hidden 1165 | } 1166 | 1167 | .katex .brace-right { 1168 | position: absolute; 1169 | right: 0; 1170 | width: 25.1%; 1171 | overflow: hidden 1172 | } 1173 | 1174 | .katex .x-arrow-pad { 1175 | padding: 0 .5em 1176 | } 1177 | 1178 | .katex .mover, 1179 | .katex .munder, 1180 | .katex .x-arrow { 1181 | text-align: center 1182 | } 1183 | 1184 | .katex .boxpad { 1185 | padding: 0 .3em 1186 | } 1187 | 1188 | .katex .fbox, 1189 | .katex .fcolorbox { 1190 | box-sizing: border-box; 1191 | border: .04em solid 1192 | } 1193 | 1194 | .katex .cancel-pad { 1195 | padding: 0 .2em 1196 | } 1197 | 1198 | .katex .cancel-lap { 1199 | margin-left: -.2em; 1200 | margin-right: -.2em 1201 | } 1202 | 1203 | .katex .sout { 1204 | border-bottom-style: solid; 1205 | border-bottom-width: .08em 1206 | } 1207 | 1208 | .katex-display { 1209 | display: block; 1210 | margin: 1em 0; 1211 | text-align: center 1212 | } 1213 | 1214 | .katex-display>.katex { 1215 | display: block; 1216 | text-align: center; 1217 | white-space: nowrap 1218 | } 1219 | 1220 | .katex-display>.katex>.katex-html { 1221 | display: block; 1222 | position: relative 1223 | } 1224 | 1225 | .katex-display>.katex>.katex-html>.tag { 1226 | position: absolute; 1227 | right: 0 1228 | } 1229 | 1230 | .katex-display.leqno>.katex>.katex-html>.tag { 1231 | left: 0; 1232 | right: auto 1233 | } 1234 | 1235 | .katex-display.fleqn>.katex { 1236 | text-align: left 1237 | } --------------------------------------------------------------------------------