From 7e75089cb2ad84ebd2df42e318bbf08c17f52a95 Mon Sep 17 00:00:00 2001 From: JK Date: Thu, 30 Oct 2025 20:16:32 +0100 Subject: [PATCH] initializing --- .editorconfig | 10 + .eslintignore | 3 + .eslintrc | 23 + .github/FUNDING.yml | 2 + .github/workflows/release.yml | 35 + .gitignore | 33 + .npmrc | 1 + LICENSE | 21 + README.md | 98 + docs/clever-referencing.html | 1 + docs/commands.html | 1 + docs/credits.html | 1 + docs/equations.html | 79 + ...e-equation-support-inside-blockquotes.html | 7 + .../rendering-equations-inside-callouts.html | 1 + docs/index.html | 2 + docs/lib/scripts/generated.js | 1 + docs/lib/scripts/graph-render-worker.js | 1 + docs/lib/scripts/graph_view.js | 1 + docs/lib/scripts/graph_wasm.js | 1 + docs/lib/scripts/graph_wasm.wasm | Bin 0 -> 23377 bytes docs/lib/scripts/tinycolor.js | 1 + docs/lib/scripts/webpage.js | 1 + docs/lib/styles/generated-styles.css | 1 + docs/lib/styles/obsidian-styles.css | 1 + docs/lib/styles/plugin-styles.css | 2 + docs/lib/styles/snippets.css | 1 + docs/lib/styles/theme.css | 1 + ...migration-from-math-booster-version-1.html | 1 + docs/proof-environment.html | 12 + ...n-link-autocomplete-20231130210116619.webp | Bin 0 -> 8664 bytes .../auto-completion.html | 4 + .../custom-link-autocomplete.html | 1 + .../custom-link-completion.html | 1 + .../editor-auto-completion.html | 1 + ...obsidian's-built-in-link-autocomplete.html | 1 + ...g-obsidian's-built-in-link-completion.html | 1 + .../search-&-link-auto-completion.html | 1 + .../search-&-link-autocomplete.html | 1 + .../search-modal.html | 4 + ...n-link-autocomplete-20231130210116619.webp | Bin 0 -> 8664 bytes .../custom-link-autocomplete.html | 1 + ...obsidian's-built-in-link-autocomplete.html | 1 + .../search-&-link-autocomplete.html | 1 + .../search-modal.html | 4 + docs/settings/local-settings.html | 13 + .../prefix-inference/5.6-sample-note.html | 4 + .../prefix-inference/a-sample-note.html | 4 + .../prefix-inference/prefix-inference.html | 1 + docs/settings/profiles.html | 11 + docs/settings/settings.html | 1 + docs/theorem-callouts/prefix-inference.html | 1 + .../theorem-callouts/style-your-theorems.html | 1 + docs/theorem-callouts/styling.html | 1 + docs/theorem-callouts/theorem-callouts.html | 55 + esbuild.config.mjs | 51 + manifest-beta.json | 12 + manifest.json | 12 + package-lock.json | 2388 +++++++++++++++++ package.json | 36 + src/cleveref.ts | 67 + src/env.ts | 63 + src/equations/common.ts | 68 + src/equations/live-preview.ts | 119 + src/equations/reading-view.ts | 162 ++ src/file-io.ts | 113 + src/index/README.md | 23 + src/index/expression/link.ts | 184 ++ src/index/expression/literal.ts | 405 +++ src/index/import/markdown.ts | 227 ++ src/index/manager.ts | 429 +++ src/index/math-index.ts | 382 +++ src/index/storage/inverted.ts | 41 + src/index/typings/indexable.ts | 64 + src/index/typings/json.ts | 82 + src/index/typings/markdown.ts | 445 +++ src/index/utils/deferred.ts | 22 + src/index/utils/normalizers.ts | 6 + src/index/web-worker/importer.ts | 199 ++ src/index/web-worker/importer.worker.ts | 25 + src/index/web-worker/message.ts | 39 + src/index/web-worker/transferable.ts | 98 + src/main.ts | 404 +++ src/notice.ts | 371 +++ src/patches/link-completion.ts | 69 + src/patches/page-preview.ts | 24 + src/proof/common.ts | 17 + src/proof/live-preview.ts | 211 ++ src/proof/reading-view.ts | 120 + src/search/core.ts | 311 +++ src/search/editor-suggest.ts | 120 + src/search/modal.ts | 149 + src/settings/helper.ts | 545 ++++ src/settings/modals.ts | 292 ++ src/settings/profile.ts | 446 +++ src/settings/settings.ts | 277 ++ src/settings/tab.ts | 146 + src/theorem-callouts/renderer.ts | 479 ++++ src/theorem-callouts/state-field.ts | 96 + src/theorem-callouts/view-plugin.ts | 34 + src/typings/type.d.ts | 43 + src/typings/workers.d.ts | 4 + src/utils/editor.ts | 77 + src/utils/format.ts | 171 ++ src/utils/general.ts | 33 + src/utils/obsidian.ts | 235 ++ src/utils/parse.ts | 115 + src/utils/plugin.ts | 168 ++ src/utils/render.ts | 50 + styles.scss | 21 + styles/framed.scss | 32 + styles/main.css | 156 ++ styles/mathwiki.scss | 49 + styles/plain.scss | 34 + styles/vivid.scss | 44 + tsconfig.json | 30 + version-bump.mjs | 14 + versions.json | 5 + 118 files changed, 11611 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/clever-referencing.html create mode 100644 docs/commands.html create mode 100644 docs/credits.html create mode 100644 docs/equations.html create mode 100644 docs/extending-obsidian's-math-rendering-functionalities/multi-line-equation-support-inside-blockquotes.html create mode 100644 docs/extending-obsidian's-math-rendering-functionalities/rendering-equations-inside-callouts.html create mode 100644 docs/index.html create mode 100644 docs/lib/scripts/generated.js create mode 100644 docs/lib/scripts/graph-render-worker.js create mode 100644 docs/lib/scripts/graph_view.js create mode 100644 docs/lib/scripts/graph_wasm.js create mode 100644 docs/lib/scripts/graph_wasm.wasm create mode 100644 docs/lib/scripts/tinycolor.js create mode 100644 docs/lib/scripts/webpage.js create mode 100644 docs/lib/styles/generated-styles.css create mode 100644 docs/lib/styles/obsidian-styles.css create mode 100644 docs/lib/styles/plugin-styles.css create mode 100644 docs/lib/styles/snippets.css create mode 100644 docs/lib/styles/theme.css create mode 100644 docs/migration-from-math-booster-version-1.html create mode 100644 docs/proof-environment.html create mode 100644 docs/search-&-link-auto-completion/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp create mode 100644 docs/search-&-link-auto-completion/auto-completion.html create mode 100644 docs/search-&-link-auto-completion/custom-link-autocomplete.html create mode 100644 docs/search-&-link-auto-completion/custom-link-completion.html create mode 100644 docs/search-&-link-auto-completion/editor-auto-completion.html create mode 100644 docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-autocomplete.html create mode 100644 docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-completion.html create mode 100644 docs/search-&-link-auto-completion/search-&-link-auto-completion.html create mode 100644 docs/search-&-link-auto-completion/search-&-link-autocomplete.html create mode 100644 docs/search-&-link-auto-completion/search-modal.html create mode 100644 docs/search-&-link-autocomplete/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp create mode 100644 docs/search-&-link-autocomplete/custom-link-autocomplete.html create mode 100644 docs/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html create mode 100644 docs/search-&-link-autocomplete/search-&-link-autocomplete.html create mode 100644 docs/search-&-link-autocomplete/search-modal.html create mode 100644 docs/settings/local-settings.html create mode 100644 docs/settings/prefix-inference/5.6-sample-note.html create mode 100644 docs/settings/prefix-inference/a-sample-note.html create mode 100644 docs/settings/prefix-inference/prefix-inference.html create mode 100644 docs/settings/profiles.html create mode 100644 docs/settings/settings.html create mode 100644 docs/theorem-callouts/prefix-inference.html create mode 100644 docs/theorem-callouts/style-your-theorems.html create mode 100644 docs/theorem-callouts/styling.html create mode 100644 docs/theorem-callouts/theorem-callouts.html create mode 100644 esbuild.config.mjs create mode 100644 manifest-beta.json create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/cleveref.ts create mode 100644 src/env.ts create mode 100644 src/equations/common.ts create mode 100644 src/equations/live-preview.ts create mode 100644 src/equations/reading-view.ts create mode 100644 src/file-io.ts create mode 100644 src/index/README.md create mode 100644 src/index/expression/link.ts create mode 100644 src/index/expression/literal.ts create mode 100644 src/index/import/markdown.ts create mode 100644 src/index/manager.ts create mode 100644 src/index/math-index.ts create mode 100644 src/index/storage/inverted.ts create mode 100644 src/index/typings/indexable.ts create mode 100644 src/index/typings/json.ts create mode 100644 src/index/typings/markdown.ts create mode 100644 src/index/utils/deferred.ts create mode 100644 src/index/utils/normalizers.ts create mode 100644 src/index/web-worker/importer.ts create mode 100644 src/index/web-worker/importer.worker.ts create mode 100644 src/index/web-worker/message.ts create mode 100644 src/index/web-worker/transferable.ts create mode 100644 src/main.ts create mode 100644 src/notice.ts create mode 100644 src/patches/link-completion.ts create mode 100644 src/patches/page-preview.ts create mode 100644 src/proof/common.ts create mode 100644 src/proof/live-preview.ts create mode 100644 src/proof/reading-view.ts create mode 100644 src/search/core.ts create mode 100644 src/search/editor-suggest.ts create mode 100644 src/search/modal.ts create mode 100644 src/settings/helper.ts create mode 100644 src/settings/modals.ts create mode 100644 src/settings/profile.ts create mode 100644 src/settings/settings.ts create mode 100644 src/settings/tab.ts create mode 100644 src/theorem-callouts/renderer.ts create mode 100644 src/theorem-callouts/state-field.ts create mode 100644 src/theorem-callouts/view-plugin.ts create mode 100644 src/typings/type.d.ts create mode 100644 src/typings/workers.d.ts create mode 100644 src/utils/editor.ts create mode 100644 src/utils/format.ts create mode 100644 src/utils/general.ts create mode 100644 src/utils/obsidian.ts create mode 100644 src/utils/parse.ts create mode 100644 src/utils/plugin.ts create mode 100644 src/utils/render.ts create mode 100644 styles.scss create mode 100644 styles/framed.scss create mode 100644 styles/main.css create mode 100644 styles/mathwiki.scss create mode 100644 styles/plain.scss create mode 100644 styles/vivid.scss create mode 100644 tsconfig.json create mode 100644 version-bump.mjs create mode 100644 versions.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..84b8a66 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 +tab_width = 4 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e019f3c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ + +main.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0807290 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,23 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "env": { "node": true }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + "@typescript-eslint/ban-ts-comment": "off", + "no-prototype-builtins": "off", + "@typescript-eslint/no-empty-function": "off" + } + } \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..61497d4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: RyotaUshio +custom: ['https://www.buymeacoffee.com/ryotaushio'] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4eeccdd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: Release Obsidian plugin + +on: + push: + tags: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "18.x" + + - name: Build plugin + run: | + npm install -g sass + npm install + npm run build + + - name: Create release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + tag="${GITHUB_REF#refs/tags/}" + + gh release create "$tag" \ + --title="$tag" \ + --draft \ + main.js manifest.json styles.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7dfbf87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# vscode +.vscode + +# Intellij +*.iml +.idea + +# npm +node_modules + +# Don't include the compiled main.js file in the repo. +# They should be uploaded to GitHub releases instead. +main.js + +# This is auto-generated by the css files in the styles directory. +styles.css + +# Exclude sourcemaps +*.map + +# obsidian +data.json + +# Exclude macOS Finder (System Explorer) View States +.DS_Store + +# Docs +docs/_site/ + +# Unused +src/callout_decoration.ts +exported_latest/* +.export-to-txt.py \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b973752 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +tag-version-prefix="" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2d63f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ryota Ushio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff34634 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# LaTeX-like Theorem & Equation Referencer for Obsidian + +> [!important] +> This plugin had been called **Math Booster** until version 2.1.4, but has been renamed for better clarity and discoverability. A big thank you to those who shared their thoughts [here](https://github.com/RyotaUshio/obsidian-math-booster/issues/210). + +**LaTeX-like Theorem & Equation Referencer** is an [Obsidian.md](https://obsidian.md/) plugin that provides a powerful indexing & referencing system for theorems & equations in your vault, bringing $\LaTeX$-like workflow into Obsidian. + +![Screenshot](https://raw.githubusercontent.com/RyotaUshio/obsidian-math-booster/1c7b106fcfbddccdcda8451de1c21a094994b686/docs/fig/screenshot.png) + +(The theorem in the screenshot is cited from [Tao, Terence, ed. An introduction to measure theory. Vol. 126. American Mathematical Soc., 2011.](https://terrytao.files.wordpress.com/2012/12/gsm-126-tao5-measure-book.pdf)) + +## Docs + +https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/ + +## Features + +- [Theorem environments](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/theorem-callouts.html) +- [Automatic equation numbering](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/equations.html) +- [Clever referencing](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/clever-referencing.html) +- [Search & link autocomplete](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/search-&-link-autocomplete.html) + - [Custom link autocomplete](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/custom-link-autocomplete.html) + - Easily find & insert link to theorems & equations. + - Filter theorems & equations based on their locations (*entire vault/recent notes/active note*) + - [Search modal](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/search-modal.html): more control & flexibility than editor autocomplete, including *Dataview queries* +- [Proof environment (experimental)](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/proof-environment.html) + +> [!note] +> For more modular and focused enhancements, some features are planned to be transitioned from this plugin to dedicated, specialized plugins in the near future. Below are the upcoming changes: +> +> #### Transitioning to [**Better Math in Callouts & Blockquotes**](https://github.com/RyotaUshio/obsidian-math-in-callout) +> +> - Rendering equations inside callouts +> - Multi-line equation support inside blockquotes +> +> #### Transitioning to [**Rendered Block Link Suggestions**](https://github.com/RyotaUshio/obsidian-rendered-block-link-suggestions) +> +> - [Render equations in Obsidian's built-in link suggestions](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html) + +Theorems & equations can be **dynamically/automatically numbered**, while you can statically/manually number them if you want. +The number prefix can be either explicitly specified or automatically inferred from the note title. + +Thanks to the integration with [MathLinks](https://github.com/zhaoshenzhai/obsidian-mathlinks), links to theorems/equations are displayed with their title or number, similarly to the `cleveref` package in LaTeX. (No need for manually typing aliases!) + +You can also customize the appearance of theorem callouts using CSS snippets; see [here](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/styling.html). + +## Companion plugins + +Here's a list of other math-related plugins I've developed: + +- [No More Flickering Inline Math](https://github.com/RyotaUshio/obsidian-inline-math) +- [Better Math in Callouts & Blockquotes](https://github.com/RyotaUshio/obsidian-math-in-callout) +- [MathJax Preamble Manager](https://github.com/RyotaUshio/obsidian-mathjax-preamble-manager) +- [Auto-\\displaystyle Inline Math](https://github.com/RyotaUshio/obsidian-auto-displaystyle-inline-math) + +## Installation + +You can install this plugin via Obsidian's community plugin browser (see [here](https://help.obsidian.md/Extending+Obsidian/Community+plugins#Install+a+community+plugin) for instructions). + +Also, you can test the latest beta release using [BRAT](https://github.com/TfTHacker/obsidian42-brat): + +1. Install BRAT and enable it. +2. Go to **Options**. In the **Beta Plugin List** section, click on the **Add Beta plugin** button. +3. Copy and paste `RyotaUshio/obsidian-latex-theorem-equation-referencer` in the pop-up prompt and click on **Add Plugin**. +4. _(Optional)_ Turn on **Auto-update plugins at startup** at the top of the page. +5. Go to **Community plugins > Installed plugins**. You will find "LaTeX-like Theorem & Equation Referencer" in the list. Click on the toggle button to enable it. +Since version 2 is still beta, it's not on the community plugin browser yet. + +## Dependencies + +### Obsidian plugins + +This plugin requires [MathLinks](https://github.com/zhaoshenzhai/obsidian-mathlinks) version 0.5.3 or higher installed to work properly ([Clever referencing](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/clever-referencing.html)). + +In version 2, [Dataview](https://github.com/blacksmithgu/obsidian-dataview) is no longer required. But I strongly recommend installing it because it enhances this plugin's [search](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-auto-completion/search-modal.html) functionality significantly. + +### Fonts + +You have to install [CMU Serif](https://www.cufonfonts.com/font/cmu-serif) to get some of the [preset styles for theorem callouts](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/styling.html) displayed properly. + +Additionally, [Noto Sans JP](https://fonts.google.com/noto/specimen/Noto+Sans+JP) is required for render the preset styles properly in Japanese. + +## Contributing + +- Feel free to create a new issue if something is not working well. Questions are also welcomed. +- Please send a pull request if you have any ideas to improve this plugin and our experience! +- Contribution to the docs is also highly appreciated: see [here](https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer-docs). + +## Roadmaps + +- Import from LaTeX: ArXiv papers, research/literature notes written in LaTeX, ... +- Export to LaTeX: Write research notes in Obsidian, and then export them into LaTeX. + +## Support development + +If you find this plugin useful, please support my work by buying me a coffee! + +Buy Me A Coffee diff --git a/docs/clever-referencing.html b/docs/clever-referencing.html new file mode 100644 index 0000000..beba3c0 --- /dev/null +++ b/docs/clever-referencing.html @@ -0,0 +1 @@ +Clever referencing

Clever referencing
...

When you insert a internal link to theorem callouts or equation blocks,
the link will be displayed with the corresponding theorem's title or equation number.

See also:

Remark 1.

This feature only works for wikilinks ([[]]), and markdown links ([]()) are not supported. Even if you are turning off Use [[Wikilinks]] in the app settings, you can still insert wikilinks using this plugin's Search & link autocomplete feature.

Remark 2 (MathLinks API).

This functionality is implemented using the MathLinks API. I wrote a sample plugin to demonstrate its usage; it can be found here.

\ No newline at end of file diff --git a/docs/commands.html b/docs/commands.html new file mode 100644 index 0000000..c7980bd --- /dev/null +++ b/docs/commands.html @@ -0,0 +1 @@ +Commands

Commands
...

This plugin provides the following commands (sorted in the order that I find them useful subjectively) that are available from the Command palette, Slash commands, or your custom hotkeys.

\ No newline at end of file diff --git a/docs/credits.html b/docs/credits.html new file mode 100644 index 0000000..6553222 --- /dev/null +++ b/docs/credits.html @@ -0,0 +1 @@ +Credits

Credits
...

I could not have created this plugin without the following works by the community (sorted in alphabetical order). I appreciate their hard work and a lot of inspirations.

Community plugins
...

Datacore by Michael "Tres" Brenan (blacksmithgu)
...

This plugin's indexing mechanism is totally based on Datacore.

Dataview by Michael "Tres" Brenan (blacksmithgu)
...

In version 1, Math Booster had relied on Dataview to efficiently obtain backlinks for a specific equation.

In version 2, the backlink acquisition part was implemented by this plugin itself (but the code was basically taken from his new plugin Datacore). But now Dataview provides a significant enhancement in this plugin's Search modal.

Latex Suite by artisticat
...

The simple & beautiful indicator at the right of the dependency alert modal was taken from this plugin.

MathLinks by Zhaoshen Zhai
...

This plugin's cleveref support (i.e. displaying links with theorem titles or equation numbers) is powered by MathLinks and its API.

Public vault
...

MathWiki by Zhaoshen Zhai
...

The aesthetic preset style "MathWiki" was taken from here.

And of course...
...

A big thank you to the Obsidian team for prividing the foundation for the entire great ecosystem.

\ No newline at end of file diff --git a/docs/equations.html b/docs/equations.html new file mode 100644 index 0000000..3a3349c --- /dev/null +++ b/docs/equations.html @@ -0,0 +1,79 @@ +Equations

Equations
...

With this plugin, you can get your equations automatically numbered and reference them by their equation numbers,[1] just as in .

By default, this plugin only numbers equations with backlinks (i.e. the ones that are referenced elsewhere). Alternatively, you can number all equations by turning off the option Equations - numbering > Number only referenced equations.

Remark 1 (Back-embeds).

An equation is not numbered when it has backlinks but all of them are embeds. This is intentional, but you can send a feature request if you want embeds to be counted as backlinks too.

Examples
...

Suppose you have these three equations:

Source:

$$
+f(x)
+$$
+
+$$
+g(x) \tag{$\ast$}
+$$
+
+$$
+h(x)
+$$
+

Rendered:

Equations-20231118084238497.webp

Now, insert a link to the last equation . Then, you will see the equation number (1) is xautomatically added to the equation and the inserted link is displayed with the corresponding equation number.

Source:

$$
+f(x)
+$$
+
+$$
+g(x) \tag{$\ast$}
+$$
+
+$$
+h(x)
+$$
+
+Link to $h(x)$: [[#^934f5c]]
+

Rendered:

Equation numbers-20231117195856635.webp

But we can do this without any plugins, right? All we have to do is just adding a display text manually, like so: [[#^934f5c|(1)]].

The problem of this naive approach is that the display text (1) will be never updated. What if we add a link to the first equation ?

...Well, this plugin does it right!

Source:

$$
+f(x)
+$$
+
+^05ce41
+
+$$
+g(x) \tag{$\ast$}
+$$
+
+$$
+h(x)
+$$
+
+^934f5c
+
+Link to $h(x)$: [[#^934f5c]] 
+Link to $f(x)$: [[#^05ce41]] 
+

Rendered:

Equation numbers-20231117195938005.webp

Note that linking to the second equation doesn't affect the numbers of other equations, because it has manually specified \tag{...} and this plugin respects it.

Source:

$$
+f(x)
+$$
+
+^05ce41
+
+$$
+g(x) \tag{$\ast$}
+$$
+
+^f16880
+
+$$
+h(x)
+$$
+
+^934f5c
+
+Link to $h(x)$: [[#^934f5c]] 
+Link to $f(x)$: [[#^05ce41]] 
+Link to $g(x)$: [[#^f16880]]
+

Rendered:

Equation numbers-20231117200012405.webp

Also observe that deleting these links remove the equation numbers.

Adding metadata with comments
...

Just like for theorem callouts, you can attach some metadata to each equation via comments (% ...) with a yaml-like key-value pair syntax (key: value).

The currently available keys are label and display. They are also available for theorems, and their functionalities are also the same as the theorem counterparts.

label
...

Has no effect for now. It might be used when exporting to Pandoc markdown or is supported in the future.

display
...

When set, all links to this equation are displayed with this text instead of its equation number (see also: Clever referencing).

Remarks
...

If you are turning off Use [[Wikilinks]] in the app settings, you will have to the live suggestion feature provided by this plugin instead of Obsidian's built-in [[ suggestion. This is because the built-in suggestion generates markdown links (i.e. []()) in this case, but they are not suitable for dynamically updating the displayed text.

Spacing
...

You must include at least one line break between $$ ... $$ if you want the equation to be numbered.
Otherwise, Obsidian will not recognize it as a math block.

In other words:

Good
+$$
+f(x)
+$$
+
+Good
+$$f(x)
+$$
+
+Good
+$$
+f(x)$$
+
+Bad
+$$f(x)$$
+

Also, make sure there is an empty line under the block ID of the equation. Again, this is needed due to how Obsidian works. You can enforce this using the Linter plugin's rule "Empty Line Around Math Blocks."

Equations in callouts/blocks
...

You cannot insert a link to equations in callouts or blockquotes.
This is an inherent limitation of Obsidian rather than this plugin.

PDF export
...

In version 2, equation numbers are expected to be successfully printed in PDF exports. Being a new feature, however, there might be some corner cases where it doesn't work perfectly.

If equation number printing is not successful, this plugin will show you a notification. In that case, you can run the command Convert equation numbers in the current note to static \tag{} to explicitly insert the equation numbers as static LaTeX tags (\tag{...}) as a quick fix.

Make sure you make a backup before running this command and undo the tag insertion after exporting finishes so that your equations can be dynamically numbered again.

The align Environment
...

You can choose whether multi-line equations in an align environment are numbered collectively as a group:

Equations-20231119011206241.webp

or individually.

Equations-20231119011124511.webp

Go to the Number line by line in align section in the plugin preferences to change the current setting.

When Number line by line in align is turned on, you can exclude a line from numbering by inserting \nonumber, just like in .


  1. It was one of Obsidian's long-standing problems.↩︎
\ No newline at end of file diff --git a/docs/extending-obsidian's-math-rendering-functionalities/multi-line-equation-support-inside-blockquotes.html b/docs/extending-obsidian's-math-rendering-functionalities/multi-line-equation-support-inside-blockquotes.html new file mode 100644 index 0000000..3adf54b --- /dev/null +++ b/docs/extending-obsidian's-math-rendering-functionalities/multi-line-equation-support-inside-blockquotes.html @@ -0,0 +1,7 @@ +Multi-line equation support inside blockquotes

Multi-line equation support inside blockquotes
...

Important

This feature is planned to be removed from this plugin, and instead, it will be available as a separate plugin Better Math in Callouts & Blockquotes, featuring a bunch of improvements. Currently awaiting for approval by the Obsidian team.

Obsidian natively renders inside blockquotes, but it breaks when you use line breaks inside a math like this:

> $$
+> \begin{align}
+> a &= b \\
+>   &= c
+> \end{align}
+> $$
+

With this plugin, you will no longer have this nightmare!

\ No newline at end of file diff --git a/docs/extending-obsidian's-math-rendering-functionalities/rendering-equations-inside-callouts.html b/docs/extending-obsidian's-math-rendering-functionalities/rendering-equations-inside-callouts.html new file mode 100644 index 0000000..a98f18f --- /dev/null +++ b/docs/extending-obsidian's-math-rendering-functionalities/rendering-equations-inside-callouts.html @@ -0,0 +1 @@ +Rendering equations inside callouts

Rendering equations inside callouts
...

Important

This feature is planned to be removed from this plugin, and instead, it will be available as a separate plugin Better Math in Callouts & Blockquotes, featuring a bunch of improvements. Currently awaiting for approval by the Obsidian team.

Obsidian doesn't render MathJax equations in callouts while editing them. This plugin gets this job done and makes your math note taking much more seamless.

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..ed11a14 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,2 @@ +LaTeX-like Theorem & Equation Referencer Docs

LaTeX-like Theorem & Equation Referencer
...

LaTeX-like Theorem & Equation Referencer is an Obsidian plugin that provides a powerful indexing & referencing system for theorems & equations in your vault, bringing -like workflow into Obsidian.

Remark 1 (Math Booster).

This plugin had been called Math Booster until before 2.2.0, but has been renamed for better clarity and discoverability.

Remark 2 (Major version update).

Using this plugin since Math Booster version 1? See Migration from Math Booster version 1.

Installation
...

You can install this plugin via Obsidian's community plugin browser (see here for instructions).

Also, you can test the latest beta release using BRAT:

  1. Install BRAT and enable it.
  2. Go to Options. In the Beta Plugin List section, click on the Add Beta plugin button.
  3. Copy and paste RyotaUshio/obsidian-latex-theorem-equation-referencer in the pop-up prompt and click on Add Plugin.
  4. (Optional but highly recommended) Turn on Auto-update plugins at startup at the top of the page.
  5. Go to Community plugins > Installed plugins. You will find “LaTeX-like Theorem & Equation Referencer” in the list. Click on the toggle button to enable it.

Dependencies
...

Obsidian plugins
...

This plugin requires MathLinks version 0.5.3 or higher installed to work properly (Clever referencing).

In version 2, Dataview is no longer required. But I strongly recommend installing it because it enhances this plugin's search functionality significantly.

Fonts
...

You have to install CMU Serif (which this documentation uses) to get some of the preset styles for theorem callouts displayed properly.

Additionally, Noto Sans JP is required for render the preset styles properly in Japanese.

Contributing to this docs
...

This documentation is generated from an Obsidian vault located under the LaTeX-like Theorem & Equation Referencer Docs folder of this GitHub repository. To contribute:

  1. Fork the repository and clone the fork.
     git clone https://github.com/<YOUR NAME>/obsidian-latex-theorem-equation-referencer-docs.git
    +
  2. Open the LaTeX-like Theorem & Equation Referencer Docs folder under the cloned repository as an Obsidian vault (Obsidian Help).
  3. Make changes in Obsidian so that links are properly updated.
  4. Commit & push your changes to the fork and make a pull request to the original repository.
Remark 3 (Webpage HTML Export).

This documentation website is generated using the awesome Webpage HTML Export plugin.

Credits
...

This plugin has been inspired a lot by the great Obsidian community; see Credits.

Say thank you
...

If you find this plugin useful, please consider supporting my work by buying me a coffee!

Buy Me A Coffee

\ No newline at end of file diff --git a/docs/lib/scripts/generated.js b/docs/lib/scripts/generated.js new file mode 100644 index 0000000..5390361 --- /dev/null +++ b/docs/lib/scripts/generated.js @@ -0,0 +1 @@ +let nodes={paths:["extending-obsidian's-math-rendering-functionalities/multi-line-equation-support-inside-blockquotes.html","extending-obsidian's-math-rendering-functionalities/rendering-equations-inside-callouts.html","search-&-link-autocomplete/custom-link-autocomplete.html","search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html","search-&-link-autocomplete/search-&-link-autocomplete.html","search-&-link-autocomplete/search-modal.html","settings/prefix-inference/5.6-sample-note.html","settings/prefix-inference/prefix-inference.html","settings/local-settings.html","settings/profiles.html","settings/settings.html","theorem-callouts/styling.html","theorem-callouts/theorem-callouts.html","clever-referencing.html","commands.html","credits.html","equations.html","index.html","migration-from-math-booster-version-1.html","proof-environment.html"],nodeCount:20,linkSources:[4,4,4,5,7,9,9,9,10,10,11,12,12,12,12,13,13,13,14,14,14,14,14,14,15,15,16,16,17,17,17,17,17,17,17,17,17,17,18,18,18,18,18,19,19],linkTargets:[3,2,5,2,6,8,12,19,8,9,9,13,9,16,11,12,16,4,5,19,8,12,18,16,5,11,12,13,18,12,16,13,4,3,2,5,19,15,12,3,2,5,16,12,9],labels:["Multi-line equation support inside blockquotes","Rendering equations inside callouts","Custom link autocomplete","Enhancing Obsidian's built-in link autocomplete","Search & link autocomplete","Search modal","5.6. Sample note","Prefix inference","Local settings","Profiles","Settings","Styling","Theorem callouts","Clever referencing","Commands","Credits","Equations","index","Migration from Math Booster version 1","Proof environment"],radii:[3,3,5.809917355371901,5.2623966942148765,6.2541322314049586,6.595041322314049,3.857438016528926,3.857438016528926,5.2623966942148765,6.832644628099173,4.6115702479338845,5.2623966942148765,7,6.595041322314049,6.595041322314049,5.2623966942148765,6.832644628099173,7,6.832644628099173,6.2541322314049586],linkCount:45},attractionForce=1,linkLength=10,repulsionForce=150,centralForce=3,edgePruning=100 \ No newline at end of file diff --git a/docs/lib/scripts/graph-render-worker.js b/docs/lib/scripts/graph-render-worker.js new file mode 100644 index 0000000..495e979 --- /dev/null +++ b/docs/lib/scripts/graph-render-worker.js @@ -0,0 +1 @@ +if("function"==typeof importScripts){let e,t,o;importScripts("https://d157l7jdn8e5sf.cloudfront.net/v7.2.0/webworker.js","./tinycolor.js"),addEventListener("message",onMessage),isDrawing=!1;let n=0,a=[],r=[],i=0,l=[],c=[],d=[],s=[],u=[],g={x:0,y:0},p=new Float32Array(0),h=0,f=0,y={background:2302755,link:11184810,node:13421772,outline:11184810,text:16777215,accent:4203434},S=-1,x=-1,v=-1,m=!1,w=[],b=-1,C=1,k=1;function toScreenSpace(e,t,o=!0){return o?{x:Math.floor(e*C+g.x),y:Math.floor(t*C+g.y)}:{x:e*C+g.x,y:t*C+g.y}}function vecToScreenSpace({x:e,y:t},o=!0){return toScreenSpace(e,t,o)}function toWorldspace(e,t){return{x:(e-g.x)/C,y:(t-g.y)/C}}function vecToWorldspace({x:e,y:t}){return toWorldspace(e,t)}function setCameraCenterWorldspace({x:e,y:t}){g.x=canvas.width/2-e*C,g.y=canvas.height/2-t*C}function getCameraCenterWorldspace(){return toWorldspace(canvas.width/2,canvas.height/2)}function getNodeScreenRadius(e){return e*k}function getNodeWorldspaceRadius(e){return e/k}function getPosition(e){return{x:p[2*e],y:p[2*e+1]}}function mixColors(e,t,o){return tinycolor.mix(tinycolor(e.toString(16)),tinycolor(t.toString(16)),o).toHexNumber()}function darkenColor(e,t){return tinycolor(e.toString(16)).darken(t).toHexNumber()}function lightenColor(e,t){return tinycolor(e.toString(16)).lighten(t).toHexNumber()}function invertColor(e,t){if(0===(e=e.toString(16)).indexOf("#")&&(e=e.slice(1)),3===e.length&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),6!==e.length)throw new Error("Invalid HEX color.");var o=parseInt(e.slice(0,2),16),n=parseInt(e.slice(2,4),16),a=parseInt(e.slice(4,6),16);return t?.299*o+.587*n+.114*a>186?"#000000":"#FFFFFF":(o=(255-o).toString(16),n=(255-n).toString(16),a=(255-a).toString(16),"#"+padZero(o)+padZero(n)+padZero(a))}function clamp(e,t,o){return Math.min(Math.max(e,t),o)}function lerp(e,t,o){return e+(t-e)*o}let N=0,T=.2,F=15,M=12,P=F/M;function showLabel(e,t,o=!1){let n=u[e];if(d[e]=t,!(t>.01))return void hideLabel(e);n.visible=!0,n.style.fontSize=o?F:M;let a=vecToScreenSpace(getPosition(e)),r=s[e]*(o?P:1)/2;n.x=a.x-r,n.y=a.y+getNodeScreenRadius(l[e])+9,n.alpha=t}function hideLabel(e){u[e].visible=!1}function draw(){o.clear();let e=[];m&&(w=[]),N=-1!=S||-1!=v?Math.min(1,N+T):Math.max(0,N-T),o.lineStyle(1,mixColors(y.link,y.background,50*N),.7);for(let t=0;t2){showLabel(e,lerp(0,(t-4)/10-1/k/6*.9,Math.max(1-N,.2)))}else hideLabel(e);if(S==e||x==e&&0!=N||-1!=S&&w.includes(e))continue;let n=vecToScreenSpace(getPosition(e));o.drawCircle(n.x,n.y,t)}o.endFill(),t=.7*N,o.lineStyle(1,mixColors(mixColors(y.link,y.accent,100*N),y.background,20),t);for(let t=0;tMath.max(e,r))),GraphAssembly.averageRadius=GraphAssembly.radii.reduce(((e,r)=>e+r))/GraphAssembly.radii.length,GraphAssembly.minRadius=GraphAssembly.radii.reduce(((e,r)=>Math.min(e,r))),r=this.loadState(),Module.HEAP32.set(new Int32Array(r.buffer),GraphAssembly.#e/r.BYTES_PER_ELEMENT),Module.HEAP32.set(new Int32Array(GraphAssembly.radii.buffer),GraphAssembly.#t/GraphAssembly.radii.BYTES_PER_ELEMENT),Module.HEAP32.set(new Int32Array(GraphAssembly.linkSources.buffer),GraphAssembly.#a/GraphAssembly.linkSources.BYTES_PER_ELEMENT),Module.HEAP32.set(new Int32Array(GraphAssembly.linkTargets.buffer),GraphAssembly.#s/GraphAssembly.linkTargets.BYTES_PER_ELEMENT),Module._Init(GraphAssembly.#e,GraphAssembly.#t,GraphAssembly.#a,GraphAssembly.#s,GraphAssembly.nodeCount,GraphAssembly.linkCount,batchFraction,dt,attractionForce,linkLength,repulsionForce,centralForce)}static get positions(){return Module.HEAP32.buffer.slice(GraphAssembly.#e,GraphAssembly.#e+GraphAssembly.#r)}static saveState(e){localStorage.setItem("positions",JSON.stringify(new Float32Array(GraphAssembly.positions).map((e=>Math.round(e)))))}static loadState(){let e=localStorage.getItem("positions"),r=null;if(e&&(r=new Float32Array(Object.values(JSON.parse(e)))),!r||!e||r.length!=2*GraphAssembly.nodeCount){r=new Float32Array(2*GraphAssembly.nodeCount);let e=GraphAssembly.averageRadius*Math.sqrt(GraphAssembly.nodeCount)*2;for(let t=0;t{try{var e=renderWorker.canvasSidebar.classList.contains("is-collapsed")}catch(e){return}running&&e?running=!1:running||e||(running=!0,renderWorker.autoResizeCanvas(),renderWorker.centerCamera())}),1e3))}function updateGraph(){if(running&&!renderWorker.canvasSidebar.classList.contains("is-collapsed")&&(GraphAssembly.update(mouseWorldPos,renderWorker.grabbedNode,renderWorker.cameraScale),GraphAssembly.hoveredNode!=renderWorker.hoveredNode&&(renderWorker.hoveredNode=GraphAssembly.hoveredNode,renderWorker.canvas.style.cursor=-1==GraphAssembly.hoveredNode?"default":"pointer"),renderWorker.draw(GraphAssembly.positions),averageFPS=.95*averageFPS+.05*pixiApp.ticker.FPS,averageFPS<.9*targetFPS&&batchFraction>minBatchFraction&&(batchFraction=Math.max(batchFraction-.5/targetFPS,minBatchFraction),GraphAssembly.batchFraction=batchFraction,GraphAssembly.repulsionForce=repulsionForce/batchFraction),0!=scrollVelocity)){renderWorker.getCameraCenterWorldspace();Math.abs(scrollVelocity)<.001&&(scrollVelocity=0),zoomGraphViewAroundPoint(mouseWorldPos,scrollVelocity),scrollVelocity*=.65}}function zoomGraphViewAroundPoint(e,r,t=.15,a=15){let s=renderWorker.getCameraCenterWorldspace();if(renderWorker.cameraScale=Math.max(Math.min(renderWorker.cameraScale+r*renderWorker.cameraScale,a),t),renderWorker.cameraScale!=t&&renderWorker.cameraScale!=a&&scrollVelocity>0&&null!=mouseWorldPos.x&&null!=mouseWorldPos.y){let t={x:e.x-s.x,y:e.y-s.y},a={x:s.x+t.x*r,y:s.y+t.y*r};renderWorker.setCameraCenterWorldspace(a)}else renderWorker.setCameraCenterWorldspace(s)}function scaleGraphViewAroundPoint(e,r,t=.15,a=15){let s=renderWorker.getCameraCenterWorldspace(),o=renderWorker.cameraScale;renderWorker.cameraScale=Math.max(Math.min(r*renderWorker.cameraScale,a),t);let i=(o-renderWorker.cameraScale)/o;if(renderWorker.cameraScale!=t&&renderWorker.cameraScale!=a&&0!=r){let r={x:e.x-s.x,y:e.y-s.y},t={x:s.x-r.x*i,y:s.y-r.y*i};renderWorker.setCameraCenterWorldspace(t)}else renderWorker.setCameraCenterWorldspace(s)}function initializeGraphEvents(){window.addEventListener("beforeunload",(()=>{running=!1,GraphAssembly.free()}));let e=!1,r=renderWorker.canvas.width;window.addEventListener("resize",(()=>{(e||renderWorker.canvas.width!=r)&&(renderWorker.autoResizeCanvas(),renderWorker.centerCamera())}));let t=document.querySelector(".graph-view-container");function a(e){e.composedPath().includes(t)||s()}function s(){let r=t.clientWidth,s=t.clientHeight;t.classList.add("scale-down"),t.animate({opacity:0},{duration:100,easing:"ease-in",fill:"forwards"}).addEventListener("finish",(function(){t.classList.toggle("expanded"),renderWorker.autoResizeCanvas(),renderWorker.centerCamera();let e=t.clientWidth,a=t.clientHeight;renderWorker.cameraScale*=(e/r+a/s)/2,t.classList.remove("scale-down"),t.classList.add("scale-up"),updateGraph(),t.animate({opacity:1},{duration:200,easing:"ease-out",fill:"forwards"}).addEventListener("finish",(function(){t.classList.remove("scale-up")}))})),e=!e,e?document.addEventListener("pointerdown",a):document.removeEventListener("pointerdown",a)}function o(e){var r=renderWorker.canvas.getBoundingClientRect();let t=getPointerPosition(e);return{x:t.x-r.left,y:t.y-r.top}}let i={x:0,y:0},n={x:0,y:0},d={x:0,y:0},l={x:0,y:0},c={x:0,y:0},h=0,m=!1,p=!1,u=!1,y=document.querySelector(".graph-view-container"),g=-1;y.addEventListener("pointerenter",(function(r){let t=0,a=!1;function b(e){n=o(e),mouseWorldPos=renderWorker.vecToWorldspace(n),l={x:n.x-d.x,y:n.y-d.y},d=n,-1!=renderWorker.grabbedNode&&(c={x:n.x-i.x,y:n.y-i.y}),m&&-1!=renderWorker.hoveredNode&&-1==renderWorker.grabbedNode&&renderWorker.hoveredNode!=renderWorker.grabbedNode&&(renderWorker.grabbedNode=renderWorker.hoveredNode),m&&-1==renderWorker.hoveredNode&&-1==renderWorker.grabbedNode||p?renderWorker.cameraOffset={x:renderWorker.cameraOffset.x+l.x,y:renderWorker.cameraOffset.y+l.y}:-1!=renderWorker.hoveredNode?renderWorker.canvas.style.cursor="pointer":renderWorker.canvas.style.cursor="default"}function v(e){if(1==e.touches?.length)return a&&(d=o(e),a=!1),void b(e);if(2==e.touches?.length){let r=getTouchPosition(e.touches[0]),s=getTouchPosition(e.touches[1]);n=o(e),l={x:n.x-d.x,y:n.y-d.y},d=n;let i=Math.sqrt(Math.pow(r.x-s.x,2)+Math.pow(r.y-s.y,2));a||(a=!0,t=i,l={x:0,y:0},mouseWorldPos={x:void 0,y:void 0},renderWorker.grabbedNode=-1,renderWorker.hoveredNode=-1);let c=(i-t)/t;scaleGraphViewAroundPoint(renderWorker.vecToWorldspace(n),1+c,.15,15),renderWorker.cameraOffset={x:renderWorker.cameraOffset.x+l.x,y:renderWorker.cameraOffset.y+l.y},t=i}}function k(r){document.removeEventListener("pointerup",k);let t=Date.now();setTimeout((()=>{m&&-1!=renderWorker.hoveredNode&&Math.abs(c.x)<=4&&Math.abs(c.y)<=4&&t-h<300&&async function(r){e?s():GraphAssembly.saveState(renderWorker);let t=nodes.paths[r];window.location.pathname.endsWith(nodes.paths[r])||await loadDocument(t)}(renderWorker.hoveredNode),m&&-1!=renderWorker.grabbedNode&&(renderWorker.grabbedNode=-1),0==r.button&&(m=!1),"touch"==r.pointerType&&g==r.pointerId&&(g=-1,m=!1),1==r.button&&(p=!1),u||(document.removeEventListener("mousemove",b),document.removeEventListener("touchmove",v))}),0)}function f(e){document.addEventListener("pointerup",k),mouseWorldPos=renderWorker.vecToWorldspace(n),c={x:0,y:0},0==e.button&&(m=!0),"touch"==e.pointerType&&-1==g&&(g=e.pointerId,m=!0),1==e.button&&(p=!0),i=n,h=Date.now(),m&&-1!=renderWorker.hoveredNode&&(renderWorker.grabbedNode=renderWorker.hoveredNode)}n=o(r),mouseWorldPos=renderWorker.vecToWorldspace(n),d=o(r),u=!0,document.addEventListener("mousemove",b),document.addEventListener("touchmove",v),y.addEventListener("pointerdown",f),y.addEventListener("pointerleave",(function e(r){setTimeout((()=>{u=!1,m||(document.removeEventListener("mousemove",b),document.removeEventListener("touchmove",v),mouseWorldPos={x:void 0,y:void 0}),y.removeEventListener("pointerdown",f),y.removeEventListener("pointerleave",e)}),1)}))})),document.querySelector(".graph-expand.graph-icon")?.addEventListener("click",(e=>{e.stopPropagation(),s()})),y.addEventListener("wheel",(function(e){let r=.09;e.deltaY>0?(scrollVelocity>=-.09&&(scrollVelocity=-.09),scrollVelocity*=1.4):(scrollVelocity<=r&&(scrollVelocity=r),scrollVelocity*=1.4)})),document.querySelector(".theme-toggle-input")?.addEventListener("change",(e=>{setTimeout((()=>renderWorker.resampleColors()),0)}))}Module.onRuntimeInitialized=initializeGraphView,setTimeout((()=>Module.onRuntimeInitialized()),300) \ No newline at end of file diff --git a/docs/lib/scripts/graph_wasm.js b/docs/lib/scripts/graph_wasm.js new file mode 100644 index 0000000..816023d --- /dev/null +++ b/docs/lib/scripts/graph_wasm.js @@ -0,0 +1 @@ +var read_,readAsync,readBinary,setWindowTitle,Module=void 0!==Module?Module:{},moduleOverrides=Object.assign({},Module),arguments_=[],thisProgram="./this.program",quit_=(e,n)=>{throw n},ENVIRONMENT_IS_WEB="object"==typeof window,ENVIRONMENT_IS_WORKER="function"==typeof importScripts,ENVIRONMENT_IS_NODE="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,scriptDirectory="";function locateFile(e){return Module.locateFile?Module.locateFile(e,scriptDirectory):scriptDirectory+e}if(ENVIRONMENT_IS_NODE){var fs=require("fs"),nodePath=require("path");scriptDirectory=ENVIRONMENT_IS_WORKER?nodePath.dirname(scriptDirectory)+"/":__dirname+"/",read_=(e,n)=>(e=isFileURI(e)?new URL(e):nodePath.normalize(e),fs.readFileSync(e,n?void 0:"utf8")),readBinary=e=>{var n=read_(e,!0);return n.buffer||(n=new Uint8Array(n)),n},readAsync=(e,n,t)=>{e=isFileURI(e)?new URL(e):nodePath.normalize(e),fs.readFile(e,(function(e,r){e?t(e):n(r.buffer)}))},!Module.thisProgram&&process.argv.length>1&&(thisProgram=process.argv[1].replace(/\\/g,"/")),arguments_=process.argv.slice(2),"undefined"!=typeof module&&(module.exports=Module),process.on("uncaughtException",(function(e){if(!("unwind"===e||e instanceof ExitStatus||e.context instanceof ExitStatus))throw e}));var nodeMajor=process.versions.node.split(".")[0];nodeMajor<15&&process.on("unhandledRejection",(function(e){throw e})),quit_=(e,n)=>{throw process.exitCode=e,n},Module.inspect=function(){return"[Emscripten Module object]"}}else(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&(ENVIRONMENT_IS_WORKER?scriptDirectory=self.location.href:"undefined"!=typeof document&&document.currentScript&&(scriptDirectory=document.currentScript.src),scriptDirectory=0!==scriptDirectory.indexOf("blob:")?scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1):"",read_=e=>{var n=new XMLHttpRequest;return n.open("GET",e,!1),n.send(null),n.responseText},ENVIRONMENT_IS_WORKER&&(readBinary=e=>{var n=new XMLHttpRequest;return n.open("GET",e,!1),n.responseType="arraybuffer",n.send(null),new Uint8Array(n.response)}),readAsync=(e,n,t)=>{var r=new XMLHttpRequest;r.open("GET",e,!0),r.responseType="arraybuffer",r.onload=()=>{200==r.status||0==r.status&&r.response?n(r.response):t()},r.onerror=t,r.send(null)},setWindowTitle=e=>document.title=e);var wasmBinary,out=Module.print||console.log.bind(console),err=Module.printErr||console.warn.bind(console);Object.assign(Module,moduleOverrides),moduleOverrides=null,Module.arguments&&(arguments_=Module.arguments),Module.thisProgram&&(thisProgram=Module.thisProgram),Module.quit&&(quit_=Module.quit),Module.wasmBinary&&(wasmBinary=Module.wasmBinary);var wasmMemory,noExitRuntime=Module.noExitRuntime||!0;"object"!=typeof WebAssembly&&abort("no native wasm support detected");var EXITSTATUS,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64,wasmTable,ABORT=!1;function updateMemoryViews(){var e=wasmMemory.buffer;Module.HEAP8=HEAP8=new Int8Array(e),Module.HEAP16=HEAP16=new Int16Array(e),Module.HEAP32=HEAP32=new Int32Array(e),Module.HEAPU8=HEAPU8=new Uint8Array(e),Module.HEAPU16=HEAPU16=new Uint16Array(e),Module.HEAPU32=HEAPU32=new Uint32Array(e),Module.HEAPF32=HEAPF32=new Float32Array(e),Module.HEAPF64=HEAPF64=new Float64Array(e)}var __ATPRERUN__=[],__ATINIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1;function preRun(){if(Module.preRun)for("function"==typeof Module.preRun&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module.postRun)for("function"==typeof Module.postRun&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(e){__ATPRERUN__.unshift(e)}function addOnInit(e){__ATINIT__.unshift(e)}function addOnPostRun(e){__ATPOSTRUN__.unshift(e)}var runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function addRunDependency(e){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}function removeRunDependency(e){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),0==runDependencies&&(null!==runDependencyWatcher&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var n=dependenciesFulfilled;dependenciesFulfilled=null,n()}}function abort(e){throw Module.onAbort&&Module.onAbort(e),err(e="Aborted("+e+")"),ABORT=!0,EXITSTATUS=1,e+=". Build with -sASSERTIONS for more info.",new WebAssembly.RuntimeError(e)}var wasmBinaryFile,tempDouble,tempI64,dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(e){return e.startsWith(dataURIPrefix)}function isFileURI(e){return e.startsWith("file://")}function getBinary(e){try{if(e==wasmBinaryFile&&wasmBinary)return new Uint8Array(wasmBinary);if(readBinary)return readBinary(e);throw"both async and sync fetching of the wasm failed"}catch(e){abort(e)}}function getBinaryPromise(e){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if("function"==typeof fetch&&!isFileURI(e))return fetch(e,{credentials:"same-origin"}).then((function(n){if(!n.ok)throw"failed to load wasm binary file at '"+e+"'";return n.arrayBuffer()})).catch((function(){return getBinary(e)}));if(readAsync)return new Promise((function(n,t){readAsync(e,(function(e){n(new Uint8Array(e))}),t)}))}return Promise.resolve().then((function(){return getBinary(e)}))}function instantiateArrayBuffer(e,n,t){return getBinaryPromise(e).then((function(e){return WebAssembly.instantiate(e,n)})).then((function(e){return e})).then(t,(function(e){err("failed to asynchronously prepare wasm: "+e),abort(e)}))}function instantiateAsync(e,n,t,r){return e||"function"!=typeof WebAssembly.instantiateStreaming||isDataURI(n)||isFileURI(n)||ENVIRONMENT_IS_NODE||"function"!=typeof fetch?instantiateArrayBuffer(n,t,r):fetch(n,{credentials:"same-origin"}).then((function(e){return WebAssembly.instantiateStreaming(e,t).then(r,(function(e){return err("wasm streaming compile failed: "+e),err("falling back to ArrayBuffer instantiation"),instantiateArrayBuffer(n,t,r)}))}))}function createWasm(){var e={a:wasmImports};function n(e,n){var t=e.exports;return Module.asm=t,wasmMemory=Module.asm.f,updateMemoryViews(),wasmTable=Module.asm.r,addOnInit(Module.asm.g),removeRunDependency("wasm-instantiate"),t}if(addRunDependency("wasm-instantiate"),Module.instantiateWasm)try{return Module.instantiateWasm(e,n)}catch(e){return err("Module.instantiateWasm callback failed with error: "+e),!1}return instantiateAsync(wasmBinary,wasmBinaryFile,e,(function(e){n(e.instance)})),{}}isDataURI(wasmBinaryFile="graph_wasm.wasm")||(wasmBinaryFile=locateFile(wasmBinaryFile));var ASM_CONSTS={2304:e=>{console.log(UTF8ToString(e))}};function ExitStatus(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function callRuntimeCallbacks(e){for(;e.length>0;)e.shift()(Module)}function getValue(e,n="i8"){switch(n.endsWith("*")&&(n="*"),n){case"i1":case"i8":return HEAP8[e>>0];case"i16":return HEAP16[e>>1];case"i32":case"i64":return HEAP32[e>>2];case"float":return HEAPF32[e>>2];case"double":return HEAPF64[e>>3];case"*":return HEAPU32[e>>2];default:abort("invalid type for getValue: "+n)}}function setValue(e,n,t="i8"){switch(t.endsWith("*")&&(t="*"),t){case"i1":case"i8":HEAP8[e>>0]=n;break;case"i16":HEAP16[e>>1]=n;break;case"i32":HEAP32[e>>2]=n;break;case"i64":tempI64=[n>>>0,(tempDouble=n,+Math.abs(tempDouble)>=1?tempDouble>0?(0|Math.min(+Math.floor(tempDouble/4294967296),4294967295))>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[e>>2]=tempI64[0],HEAP32[e+4>>2]=tempI64[1];break;case"float":HEAPF32[e>>2]=n;break;case"double":HEAPF64[e>>3]=n;break;case"*":HEAPU32[e>>2]=n;break;default:abort("invalid type for setValue: "+t)}}function _abort(){abort("")}var readEmAsmArgsArray=[];function readEmAsmArgs(e,n){var t;for(readEmAsmArgsArray.length=0,n>>=2;t=HEAPU8[e++];)n+=105!=t&n,readEmAsmArgsArray.push(105==t?HEAP32[n]:HEAPF64[n++>>1]),++n;return readEmAsmArgsArray}function runEmAsmFunction(e,n,t){var r=readEmAsmArgs(n,t);return ASM_CONSTS[e].apply(null,r)}function _emscripten_asm_const_int(e,n,t){return runEmAsmFunction(e,n,t)}function _emscripten_date_now(){return Date.now()}function _emscripten_memcpy_big(e,n,t){HEAPU8.copyWithin(e,n,n+t)}function getHeapMax(){return 2147483648}function emscripten_realloc_buffer(e){var n=wasmMemory.buffer;try{return wasmMemory.grow(e-n.byteLength+65535>>>16),updateMemoryViews(),1}catch(e){}}function _emscripten_resize_heap(e){var n=HEAPU8.length;e>>>=0;var t=getHeapMax();if(e>t)return!1;for(var r=1;r<=4;r*=2){var o=n*(1+.2/r);if(o=Math.min(o,e+100663296),emscripten_realloc_buffer(Math.min(t,(a=Math.max(e,o))+((i=65536)-a%i)%i)))return!0}var a,i;return!1}function getCFunc(e){return Module["_"+e]}function writeArrayToMemory(e,n){HEAP8.set(e,n)}function lengthBytesUTF8(e){for(var n=0,t=0;t=55296&&r<=57343?(n+=4,++t):n+=3}return n}function stringToUTF8Array(e,n,t,r){if(!(r>0))return 0;for(var o=t,a=t+r-1,i=0;i=55296&&u<=57343)u=65536+((1023&u)<<10)|1023&e.charCodeAt(++i);if(u<=127){if(t>=a)break;n[t++]=u}else if(u<=2047){if(t+1>=a)break;n[t++]=192|u>>6,n[t++]=128|63&u}else if(u<=65535){if(t+2>=a)break;n[t++]=224|u>>12,n[t++]=128|u>>6&63,n[t++]=128|63&u}else{if(t+3>=a)break;n[t++]=240|u>>18,n[t++]=128|u>>12&63,n[t++]=128|u>>6&63,n[t++]=128|63&u}}return n[t]=0,t-o}function stringToUTF8(e,n,t){return stringToUTF8Array(e,HEAPU8,n,t)}function stringToUTF8OnStack(e){var n=lengthBytesUTF8(e)+1,t=stackAlloc(n);return stringToUTF8(e,t,n),t}var UTF8Decoder="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function UTF8ArrayToString(e,n,t){for(var r=n+t,o=n;e[o]&&!(o>=r);)++o;if(o-n>16&&e.buffer&&UTF8Decoder)return UTF8Decoder.decode(e.subarray(n,o));for(var a="";n>10,56320|1023&s)}}else a+=String.fromCharCode((31&i)<<6|u)}else a+=String.fromCharCode(i)}return a}function UTF8ToString(e,n){return e?UTF8ArrayToString(HEAPU8,e,n):""}function ccall(e,n,t,r,o){var a={string:e=>{var n=0;return null!=e&&0!==e&&(n=stringToUTF8OnStack(e)),n},array:e=>{var n=stackAlloc(e.length);return writeArrayToMemory(e,n),n}};var i=getCFunc(e),u=[],l=0;if(r)for(var s=0;s"number"===e||"boolean"===e));return"string"!==n&&o&&!r?getCFunc(e):function(){return ccall(e,n,t,arguments,r)}}var calledRun,wasmImports={b:_abort,e:_emscripten_asm_const_int,d:_emscripten_date_now,c:_emscripten_memcpy_big,a:_emscripten_resize_heap},asm=createWasm(),___wasm_call_ctors=function(){return(___wasm_call_ctors=Module.asm.g).apply(null,arguments)},_SetBatchFractionSize=Module._SetBatchFractionSize=function(){return(_SetBatchFractionSize=Module._SetBatchFractionSize=Module.asm.h).apply(null,arguments)},_SetAttractionForce=Module._SetAttractionForce=function(){return(_SetAttractionForce=Module._SetAttractionForce=Module.asm.i).apply(null,arguments)},_SetLinkLength=Module._SetLinkLength=function(){return(_SetLinkLength=Module._SetLinkLength=Module.asm.j).apply(null,arguments)},_SetRepulsionForce=Module._SetRepulsionForce=function(){return(_SetRepulsionForce=Module._SetRepulsionForce=Module.asm.k).apply(null,arguments)},_SetCentralForce=Module._SetCentralForce=function(){return(_SetCentralForce=Module._SetCentralForce=Module.asm.l).apply(null,arguments)},_SetDt=Module._SetDt=function(){return(_SetDt=Module._SetDt=Module.asm.m).apply(null,arguments)},_Init=Module._Init=function(){return(_Init=Module._Init=Module.asm.n).apply(null,arguments)},_Update=Module._Update=function(){return(_Update=Module._Update=Module.asm.o).apply(null,arguments)},_SetPosition=Module._SetPosition=function(){return(_SetPosition=Module._SetPosition=Module.asm.p).apply(null,arguments)},_FreeMemory=Module._FreeMemory=function(){return(_FreeMemory=Module._FreeMemory=Module.asm.q).apply(null,arguments)},___errno_location=function(){return(___errno_location=Module.asm.__errno_location).apply(null,arguments)},_malloc=Module._malloc=function(){return(_malloc=Module._malloc=Module.asm.s).apply(null,arguments)},_free=Module._free=function(){return(_free=Module._free=Module.asm.t).apply(null,arguments)},stackSave=function(){return(stackSave=Module.asm.u).apply(null,arguments)},stackRestore=function(){return(stackRestore=Module.asm.v).apply(null,arguments)},stackAlloc=function(){return(stackAlloc=Module.asm.w).apply(null,arguments)},___cxa_is_pointer_type=function(){return(___cxa_is_pointer_type=Module.asm.__cxa_is_pointer_type).apply(null,arguments)};function run(){function e(){calledRun||(calledRun=!0,Module.calledRun=!0,ABORT||(initRuntime(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),postRun()))}runDependencies>0||(preRun(),runDependencies>0||(Module.setStatus?(Module.setStatus("Running..."),setTimeout((function(){setTimeout((function(){Module.setStatus("")}),1),e()}),1)):e()))}if(Module.cwrap=cwrap,Module.setValue=setValue,Module.getValue=getValue,dependenciesFulfilled=function e(){calledRun||run(),calledRun||(dependenciesFulfilled=e)},Module.preInit)for("function"==typeof Module.preInit&&(Module.preInit=[Module.preInit]);Module.preInit.length>0;)Module.preInit.pop()();run() \ No newline at end of file diff --git a/docs/lib/scripts/graph_wasm.wasm b/docs/lib/scripts/graph_wasm.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e94bee22df7e2e277d34b3887496c68fdf3827ae GIT binary patch literal 23377 zcmb`P4Uk^ddEd{y@ArG(clX`ZYPHfz=-eyW5g?oeV}Tu#-6M=7#FrC$Y$qK90)xXY z238U>sgZUGgs6?%8nK#S4>OTvCwPcyDDfn2{2?Vx6FMOs(%MrxAuX99PBL*4#UY8? z605)ebME`@t`KgTi9Yt;d+zyqp6CC3oadaoZr_2ACeFF!<7;kB7LtWqD+>#{Bn$4= zMnWk*7S%P#;V+;4=$)s&7AdnnC_~F4=)_%sS|rR zY=2z3xt8op_A!fn$!)I5&)Z#eaMWb)D{zOVf0? z(Wuqx^;Y7NW^ZJ)Vt-Oc_4|_H;aVd}7SqMWwBCfgaOR0>*0^>wxifXiN8E7o!y-9_x;J;?rq6O-6hFAZhLaCyEwVeU6jnb3zPlye!%sTgRYj`?=DCVxlPH( z+~uvY#Dydqdi%ETTeETFTQ}!h*Z5*f`{*wwQ@@iXjfHf^yKB16yU?5~QZCiGZK*4g z@W6cGX7d{FlaS0)BMFuHYr2U~eQGtR9=VGjkzDtx1}44YDf=;muOZo8li!CG6zWSCgBUC{EOsN2jtX4FO2y*-THLb~X^A9xEzhx4jrEUqFJdZA+`nzcL>xySpwtJL)x8UxVXE z;A0h5A@TjXg9)?tc9ShZY zVY%Nl3VL_9!tll_3hX+Zl^C$DzEXBYrDxxytCdp}=ol@J6I%rzrY-nzc~khn)Yb5b zyo&ItscD2y1nq{{*LhG>TLq2UKMjgR5;Hg#MdH%;MiEr4Kv8WailDDV5j~$5MU)$g zfT-ZrN0Gqr28f_9roFoXbBQTX^F2|O(Myz)LvrE8(b+sn7ou7hm5}hda_C^;3}K%K zr@jI#nT%kJvNf|@GXrf+K~?IUMy+Ok6auErs3n7(J=>5iapA4}Zyx$ck<1;!Dgoc* z;9U42lvgxiG&cHlr@I10>T7G>NN4mnYNySrdawZgApHxYj2@@dFwE0O1Fh8bTv$&Z z$)UO6xNhD(d61bjP=`Fhh$J!sRfFrUE(3>6vT@W`Eie_T^Rq<*=+K}@%%W%j%z)$| z{qB=1;x!)qHH(r7m`SlDl}2GA)I^*SOS16%DF0+=lDWc#1@9(dX_kF8Nh&;C;#}|| z4!k9Kg`c!5%&@sC?~!M3gRBaOF&T^%wYlq}{h=zwTSpSN0n@7vwx#b0ZjWqf?x5Ng zRaqpgvkgA0!P?j$;gG_9YhRT|pkOwK5?`Imt>dx0COlEp>(YSE$1qQzGULy^NwToT zg~fEPsBLvN?aK8GTn&q-tMWbq;#njPH|CJyB3FJ~TO^acY=cjrSQGR$3VU^Xr)p=D z>zE$W)WE$ut}QjthpI8CewyP5V3p&8V~KA5hF!E`Q0Flb-XY@o_IrDwoqMH@=lnB1A!m5FF1 zDcflFUoHHO)W^45ccfe6-L@U+ws^OFN4h=Up-5Bl?(!Y!RZuoF zmrDdQijS)|l_3BBdm1#tAMZjP+eP z-W}-^x_9A|NiQStT-cFb&oeG+D;J*LL$f6OT!Jm%ZnsOieV@92m+R5{i}aq-d#dqX z5HC*vlNw!alkv9D{slDg`6QhQ&a2|{$t*u#bhEzJTNiy0V!b}N8&MtZTwd2@PjJ`6 zO2VLUi`EcX%7P}PU|TvR5i@;ZBOx}Zpkv$8?N~7;!i*&(3VJfL3(-4npyZrc z*pz$g6tGnBqhbf#D!pawo>3Hmz=CoxK#FIwA%m-VCqJb&j@kNoi-c;JOcV%OW6n9N73@)*kGED&JopdwzR zYHvNueI_d{Y8t*fgvbln2p3K*;9$6BK#!T{wUQT2bPi64A!Zg2TlH2M*U392wDlOb2M<=5}2!kOVF?TaQfR;-L{24{4vZCBF-TRISGo3{hQ4 z2O7dw`XCKLu{dA%7&+Lm>5$eFA~e}G$;&z}pJEmpuwIs(vg&>WMjxzud_%w%%=h~y zh!-#8^Gez0u*^hTnOP}Z`I4MLCF3Yw4CN$~L^Bv)wh!hqyUe(bQYh+E9zcezHYb{p z$pl%Js%|=kvO#?WP%v2KfEcjbY0$;8V1>FUQq4Nb*n(N?J#byF$S@HfF@<6Xs7C}? zm>+FHRd&mkwYf{_+29*`G5A4e;om@0t7U#h;xo0D%0tXR$8qe*3Y!D7Q~o1zmdlDG zrG6L)VNTiz66wiOC`F2Y<`%X}e^mGMXw113497gwY~^GImZjenBWYsaPuR;MVsRX9 z)8$cJg}P6S?_7{qcw~I%;tBk~VpUIz<30K-{c0W3ffghdCBe?r1*-K$GkRSBms4k$S%CPmIx4dBDhkUW=g`; z@=w<04tyDUT+tBU8-_Nl;&oji-2fpB2iI#M;DuetJ<01(qT$gutf^1uWsNZ7FsVAr zOfliJxdiR&H<5*ka}`J8T+t~RWa1-+_RVX%`l>9Ml&ctUT*O%Z7ADgdBTveNd?|w13|aWrTBRu+{TZ^Ad?4U+;f2u%LEXO= zaa5Us2y^zhQq)Q=-#lLuUZ%iqk*18XS2!wlsE2JzVmtu3p)oIJ0(b5v$?sTWNRrr5 zJVxSf*{`Ovfq=dmeseT@Qb^%F1StX13C(#eeSGCQE(q!dQ4I#`M5R^rSYgCUIbx+h zBEgC&<{<3}uW85p;?T_9Vh=hz&(Ia|s|v8VNcNGlJ|)l}aFn@#j>Lr1*A-piTq3bJ z<+e-lBxGO))L06jRHcg5ED|BoT49w~kPnmzyW=vM_RhR`EV5 z`J%u}lE5&aWDUagxm=NHy-X3;B>Dlb`YB=oIR<2~AZ`X&11zI8ctaw1(0~coW(~>G z98%`hg8aaIP(DTio6lRRZeqTX_at*wty!d+(Td#!njMPaB)L;?plAaO$=6`eX)=*F z=?drC_fD}Nt5Zf+<%U9C{zmD3$sQgQvxmSC7ee-i1CkzK-L$ekVw(WuBJs0IhvBCd6M=szM2wd$vl)YzBrIL zA48=1c%rpR8Ld@fw1x{pCUIr1*tDPORZ5CfFE&8Y7qyVIQoRB4y6a!W|C(|Xga`%r4SyAJDrrP0@Ql$YK(fXk+ z8*%%k^%Il4wuE6sv}b1huysegWA!bKpgFW?GnFkEHh11&`@rNxxo|Ms2Qx@hl(vuY zY#A$@@?xz2f*Do9ur`*oX+s!}=|Zdu8S$zpk7O3z83vZcL;0FJ#Rk{_tF4GY5|XY( z%P4JpzF(ku0=r8EmqM8(UumEqE57G zuo9hCYA9H-`(?hth+#ohjexf8{#Ha2PWD_hDI|b}7IRi3n%WUfC6-!-rIxT{8}qTI z`B9cO!ct5_7&J;OQK!E#&$=0i^s!_wmazmJR%40q^;|5m4lJ?6ods|umN4cemR2Q< z%99lcPkH~CE@J6p<_Po1?YT%o7&0DBY>6=taf{`|OcW|MGwx{VD&%UdB1@66UhF9a z>9S8J{0txoO*WW^|1ok4rG>}OGEWx;}@FFPk=iQlu1{9-yo9u>RAKTRMBiwpB% z@symO!8x2Yvw{f1G%pf%-86U->W9KR(p{preX-9B5nDekwdXa);;>KqEM zShId7gI+_W>}Qb(;OyCH+a_PpUO`?k*Y!s^~fW4$m5N# zE=$POpa0d{-~82-d(&2zu6|W+zm{>2CUbGcy>rEgg9^PkTXLGog%$j{F3r3_%3Qm0 zwp-n>4OPtY*|Y9p_F9#JSzJ85c-3Jd)AHf$e{hGjLEG3_ebiTXUUl>l(v;Of!{LXE zs_hP{dh^GqSQ|W^b`RziX;8nj#l@M!grC*2ZyNjv=*I5sOdeKMxvTdKYM*9S?_2fs z!&>UEm`Q&Ym52L1zJAs{VjcAhzH?Ry%34|T6x6)G>vqPnsiRNfJ0JTy+uqYcqR)@% zet)OkBSk&ey32)!{*G035>7iMb%;LL41DI%ys5rSd1PGifX@`QM=>L(k<8pPFiRV^ zwMTR1ReVL;IIMWsI7Jm7`VL@HA|ZB(=~I_S^D14|4ZG^HuCCnNGcez~XNn4462|$; zQRR^cU_l#~`V|#?4+55yxRL=gQL&i2P-9+LnRsNU`;li>N$!zzMOWFG^u^HUpG1U- zL_og6JBDO$7faiMtj3_y;=;p^oC1Z}d3EA)BKH*tU8za$Sp8}MUW@95Zb+eRw%?A4 zpm*C*=Bc^RCXpZuhfkL^%PVBhvD4v&PyO;YowBD3z7nezDsJD+fd#KSVZCu+?<5IO zy2sp`mLysmZFh-vZA*eQ``PW4jSK06=@D%OBBu^i=I`5mfgUO zb%W`VwySF@Pr+3dlwr!Xynf3r!WFIF>KsHFOrRLr+a+&Fdtk(lq!N$adY=f^D|80m zhM7syC*G3#^dGj$ZL&3_0n4}fa7gm?866P9v#Du{QG?AzOEx88r=PKyyTHpOBtFqL z+Q0Zcma(>LPD@5DGTVzKc7qDqC;j&RCTP!?)QW~8j0v{FMfgN>XxYlSiWhONR-7JG zqV+WCptfvvrunxtYj3YB2bBg}tmv?)Cb=GVFiq;1&xA6fC{eba|2?f)R$F+zG4r40 zy|S|JvAavK=6r9C!X|FfL6>^(gcsawZ@i$!9^ zqOO*--NIfXARguzE1OzSrj6v6+}u!J9V`C~jSbg_hK9!KdQNMVu`y0FkBwE?=7+xU z>r`$Gjb&rhfd#ejU)`>5tFs1fTv(aOw=l7okgaKE%YR`a)?%pX6rOCT)!FVx4l{lY z-04Uw2M@&i@!<=Y&d$32(+?!BGv0q7DC(^3Kd6VX{)07IlZ7UG9xTK;sMCOR($(-C z*qXsJYzR{=wiH|ODH0k71M(J7U{_c>09&6DgBFJ+Zr`%ATU&PyzdvvLtvjnn^R2#Z z=4#7(*fgPQi~~M0R1ZP=Y~BrNJ}lLnK!&Tv5!N(7O#_g4kEg=6?rglD8ExH(OWf+~ z(GXG)J7!zalU}3|SVUfpJ#p&xklieZS3vu9W1gIgPEiei7ZSWt~fxSkjLtemd zd5_JE9Q9ycn7W1NJ&gJ0tZtet+69o^9id8x5-#+d1U!=kPQ?x`5qv}&D<7jnR7{h7 zF>5Eb>Eud0;M6o0Wh@)%E7qnl2Y$>%bNJR_78VX*7Y41l zoDRcULK!OJ$l8BK?^RDTP^I89sI1TVwx-P8EcOC^Ny}Su0`P>c%3C=&*X`6|=kCrAvWr8|b2Fdb=STu6Sda%G=5C8z%T4wxaJn;_r$jlWiz(sinVtlJN zR2o!Mgkk;+m*Lw z<52ON4bxzlhG$EkI_iH=OgdjfDQw6XMLt}dGB0wV*YG`F)O-)n*0eFZ46~-qqjND9 zBlpdG7#8v}XHzTRNWk(e5*i|484`=OGok#e1vmukzVr62VXT0gwv{)Z7&WXL#P7b1pD4()IO%hr-!>UI={n#j6FTk00EpLr0=BLuI;0(nbr?mhY;zvB z&-Nw?+Mg)$cf0b>ZOmlI4{=;dT0f`5O$@oe7z=;sw2z8y89XZuq?-7!y6GG`Yy%+fN#CG5B`PpzX&zGxZL=8jivW62WsN|T+jchxO=5=sZ$GP`goe8%O?wz|+tB3p- z#bD`#JX?u?+}FPJ8KdEj-~HWiawR>e5acp~PK~tzb{*{+jX?$%6=AtH+59)%h=HRv zy<+sViHzxaCdX3eTbNl5`N^ydKkRD+-+t?(4;EV_)1^Oqxv~gG?B>YfAL1SAZe0)) zW`4_4h2UK(zxiavsAOu2nvM!uWx-zN06`g7ePd3i^0tFcxEtJFvludzv}lF(v!Qj5 zY{gnX7QX3r<&%(sI;xp8qXjKpXn_nR2-rZ1E2ChO^xZ@GCIbELL9A828RX-rBKzTl zX`rW$1^Ao2pZA;Y7VFSAZ4O%e5qq=*FN5g#n&`lG4G8GkvBK@1BT9T$qj02X`bo^L zUmIf++STVmdcR)_M(IPA!7QFhK{JPGv{s$aVg><+VWbq@(2h2jLpQTbt;^(Csc|U++@Ng{iLtoQ?R?D7S}KW+}q@9yExpA zVAf(CSdy2TH*+<%h@k}~tzrPC0(#oaVbE0-XuLZyEmhsDZ1>7^d2^52<95@vlX=1L8)5ARb$y8zE5)bt0;5L1xEZQ3VF@(%#R;G9qe7ci5c1T~ zfc`XDM#2I3rZVO$#J9DIDSe#E)mBr0K4Q(} z;Rr51=DRvB_Awhn5+0M!0vO#uWX?eC`^<+ipSDm*5y$|Q-TSBrE6_DoXmD}`0EvV~ zILR7*%dtj758HsZCRXDoQ7E@pGsKmmAuY3fRs+Bjk)5}a4#RaJ?(el)kLP*mvc139 zIJp-@wfsgDVOH}SgF70#n2e*N`C~DHI@_^%mnIr?x zHa=S*kX2rB3LhsSar_CF7bAY(r$-)-NfCeJ3`1)_tdlKj`qAqqi*DWoI!zh!Hi0NB z23~Z9l$yE9c-ngs|N;2Mp<=i$@43Jx*7)5E5>L9)*rmJdDl;K0q`utOasw;kp9bmI;Cy2pkuw8vS6? za1ukK5nuywhz%_?+JGKxI2bnLWjJJ}g-0V0Q@YT>o#wUDlupg$1)?bik0&vjQBaa{ z)2ykuB~$8)&z!)X3Mpnqi_gfot}8<;nw_Ib11=B4>|t}G<00J-4;1p{qcPGF8&Rk> zg~PL}bc)4M)G4!bBPQDD(2JTPs!x@(ekI(_4D+XDOO$thAmbchx!fGJxi-3l|0H@E2 zOB*q)+>}U+@Gehckvd$@I+Ch20HbYWf**Md2k*6BCl|vMNxV>HjkRSc>`B5boD5SmHbtTmWS8tKD;fI$6ES(dhUI~V(_WG%QE9?faz>wZz|S?= zk(3YKk9hahqO$YyqffDE#!*Us?yf%QZD$cO@pUN*8RlULE^oHo0b=L1t*+*H>W>lM*!>}LbKtjErZ+VQEj zxWfHKY41uqiD5hZRAd)N+}3V0Bf=Cuf+)Eoy(Vt3lUk&UtMiUbrYga8q|_V9vMb)~l{guX zBbBu=A{2NBNRq^@(=Hq=IK^>Zg-Ze-Vs_f`(jdgWnBn8;SYVhpZ) z^oL>8@e~|S0(+5|k6Is3HPVl#JF0F2-F)*GN8}7aIpRc!W5=@u^Z+WiXW3Eo%|_6dc$ z5X?aa9qL@BTen8F6&lsXFHTUrQD$+hiIEz(ytK~j^>Mol&e~T>p z_bIAF7Hx*}QkZ--DKKi=H!FAnQ` z7v@!F2JXtP5IN4~4$hh_vYEq2eas2kus2k20d@oH)_;NP|X1SDdiOmhq+)l(@;h8x& zRmq-AFldo=70wvM?|qqmuc>7wNX4w%HM~09dy5_I)pvq??cu`Z`m7QS5%Fy33K^Jg zu?(vvUZHN{2!p~B0R@IglKM3SzMxD2D7w$2F}b;3zDMtc=Mz-PT_N= z0ICtbq}mo+SHl-|52gGp8WMuBouxu`FM(@guN|KI1DGsknB>}!0;AP zf0P#&*yZtpjRyMSU6p{kqoBT%c%U7QzuwVj2`=PF?sS=jLZw(q!#b^F9`D`&c zSdm|?75U_|@Tb3ESzKJmo+6@s2C=YRb67HvNr3}})L_rfrDFIiMKzijkiG$hh))fj z2|WAL4=Cwhf5)5dVJtoirkHMFcEkO zxWiU)SZ%Ib`id&O?B@6tJmF}YGjv!R_3^_0;?Tc!_Tc>7*S<{Iunr;47xdbkrQXRB zL}yN&PSZm=C+3kgjLy0s!obd|*>tl=I3iR* z`Y-e3llXA#v2s%Na#G;^LxhbR!gogOt3IH~&Rh6AABmQIb(td1{1kY@7sfp$Rjy@; zqH}zSjA^8|-!-@&S=Gzq#h4$HJyHjc7i;{QrDMee)GSW_6PTrf{Mx``zL{aFpq|Ty z$;@jN>wA7H7VTFFiJ0QV@(m$S8Tx3}@;}8;jDPx66j&muiS zH2Y%YaT185Vya2N^yyg96ARtV3{>&G$B-i0Ipn!Oi-#Ra&!@W+y@Ah$o1Y~kM z1#6*2V{pc=JHgO>f$Iush{c$*O*cTr<~pd2N-~=7)5$%PIaPtH}kEFdF0E zp_nhSA ze0`~?FFW1(3a5*dUAXLe;KBgaz4~2sIwYp!+q$G%Nu>kDE6EDuq_sfW9^Z2YN$)o< zznI`x-tdGyaJ}_maUJ&?XSsWlyKO1IwTWMC!0m;VDXno_Z;}dHt!8Zlyp~nE1>5~w z;NHJQ1Nyfph27q$k8Pk<58TCIA$`ufrNS)}>sAn(Cj=qbvdiFhVpMbWsZpIZ^ecOM zlUa{M8^_T3oeTweH|3%KqB-Pq;|I=Sj|LNAB{Dqe; z{hd=k|KX#Z_1(Xo{@}+>w{QBuZ=E{vud|OAH-CHDJ@n*nU;Om1pPIgW!`{7L{;TPm zhd%M258e1{r+)2^{^gkuZuzU}7dGzuv8#Ue-<^8OeJV^JKQnzn=MS%Z?MF_Ya`xxU zbi6wEh25Y2`%m2Z7t@dY=(j)g^v^#%ePTA<{L7#F*7O(Ow&Usd-u#v6Z@xbJfgS$O zrklU-KlVp&{g2a^JpGy5fA2T`bo##CMfcI)_?_uL4-Ztn`RJca|KX7n&;LyA_oi># z^WMLC`6GWk-TI4P`M^~5>(hVv&j0=0op1Z*^yLqKY0qbeUOY9My^{U24}4>KOK5-l zyJ!FBsk_`I7klrgJ~;J&yJYHv7f*eV`%9)CxOnOTPtorE`)<4Kjt~2L?)~r`{;mW5 z-un;wd++qO-FyE%A3or&zw4eke{kRakK94&et+QJ`}f~|o@)2(yT{#i&zApS=2|ZkCMFRX4j}M8)=_HEK2Z$-fQUZ`iQGg>QGW zq5gxtZ@KWC`Q^-2T7=`)<4I(508|xaF1uKYrIoZ#noA_uX*|Lb&%D zHC=2v_sNEHuX0^o{|egPz3;$*)eThtH1)s8|2NdX@3wsh?s#MUGv}@U&RcG|@7}xa zIe5qZl`Yi2=l>Z0_2*UER=8IAFBsJMPm$AW;i1ic{dt}O)e}5c){_*GxFo%LzL&w9 t19zQ|MB~W``qS9&J^0Q6!|1|&kn*oLF*hUG*nOSEDJWGeD9^3y{{;|SKN0`{ literal 0 HcmV?d00001 diff --git a/docs/lib/scripts/tinycolor.js b/docs/lib/scripts/tinycolor.js new file mode 100644 index 0000000..3c4a921 --- /dev/null +++ b/docs/lib/scripts/tinycolor.js @@ -0,0 +1 @@ +function w3color(t,e){return this instanceof w3color?"object"==typeof t?t:(this.attachValues(toColorObject(t)),void(e&&(e.style.backgroundColor=this.toRgbString()))):new w3color(t,e)}function toColorObject(t){var e,r,a,n,i,s,o,h,f,u,l,c=[],b=[],d=[];if(e=(t=w3trim(t.toLowerCase())).substr(0,1).toUpperCase(),r=t.substr(1),h=1,"R"!=e&&"Y"!=e&&"G"!=e&&"C"!=e&&"B"!=e&&"M"!=e&&"W"!=e||isNaN(r)||(6!=t.length||-1!=t.indexOf(","))&&(t="ncol("+t+")"),3==t.length||6==t.length||isNaN(t)||(t="ncol("+t+")"),t.indexOf(",")>0&&-1==t.indexOf("(")&&(t="ncol("+t+")"),"rgb"==t.substr(0,3)||"hsl"==t.substr(0,3)||"hwb"==t.substr(0,3)||"ncol"==t.substr(0,4)||"cmyk"==t.substr(0,4)){if("ncol"==t.substr(0,4)?(4==t.split(",").length&&-1==t.indexOf("ncola")&&(t=t.replace("ncol","ncola")),a="ncol",t=t.substr(4)):"cmyk"==t.substr(0,4)?(a="cmyk",t=t.substr(4)):(a=t.substr(0,3),t=t.substr(3)),n=3,s=!1,"a"==t.substr(0,1).toLowerCase()?(n=4,s=!0,t=t.substr(1)):"cmyk"==a&&(n=4,5==t.split(",").length&&(n=5,s=!0)),c=(t=(t=t.replace("(","")).replace(")","")).split(","),"rgb"==a){if(c.length!=n)return emptyObject();for(i=0;i-1&&(c[i]=c[i].replace("%",""),c[i]=Number(c[i]/100),i<3&&(c[i]=Math.round(255*c[i]))),isNaN(c[i]))return emptyObject();parseInt(c[i])>255&&(c[i]=255),i<3&&(c[i]=parseInt(c[i])),3==i&&Number(c[i])>1&&(c[i]=1)}l={r:c[0],g:c[1],b:c[2]},1==s&&(h=Number(c[3]))}if("hsl"==a||"hwb"==a||"ncol"==a){for(;c.length=360&&(c[0]=0),i=1;i-1){if(c[i]=c[i].replace("%",""),c[i]=Number(c[i]),isNaN(c[i]))return emptyObject();c[i]=c[i]/100}else c[i]=Number(c[i]);Number(c[i])>1&&(c[i]=1),0>Number(c[i])&&(c[i]=0)}"hsl"==a&&(l=hslToRgb(c[0],c[1],c[2]),f=Number(c[0]),u=Number(c[1])),"hwb"==a&&(l=hwbToRgb(c[0],c[1],c[2])),"ncol"==a&&(l=ncolToRgb(c[0],c[1],c[2])),1==s&&(h=Number(c[3]))}if("cmyk"==a){for(;c.length-1){if(c[i]=c[i].replace("%",""),c[i]=Number(c[i]),isNaN(c[i]))return emptyObject();c[i]=c[i]/100}else c[i]=Number(c[i]);Number(c[i])>1&&(c[i]=1),0>Number(c[i])&&(c[i]=0)}l=cmykToRgb(c[0],c[1],c[2],c[3]),1==s&&(h=Number(c[4]))}}else if("ncs"==t.substr(0,3))l=ncsToRgb(t);else{for(i=0,o=!1,b=getColorArr("names");i=6&&(r-=6),r<1?(e-t)*r+t:r<3?e:r<4?(e-t)*(4-r)+t:t}function hwbToRgb(t,e,r){var a,n,i,s=[];for(n=hslToRgb(t,1,.5),s[0]=n.r/255,s[1]=n.g/255,s[2]=n.b/255,(i=e+r)>1&&(e=Number((e/i).toFixed(2)),r=Number((r/i).toFixed(2))),a=0;a<3;a++)s[a]*=1-e-r,s[a]+=e,s[a]=Number(255*s[a]);return{r:s[0],g:s[1],b:s[2]}}function cmykToRgb(t,e,r,a){return{r:255-255*Math.min(1,t*(1-a)+a),g:255-255*Math.min(1,e*(1-a)+a),b:255-255*Math.min(1,r*(1-a)+a)}}function ncolToRgb(t,e,r){var a,n,i;if(i=t,isNaN(t.substr(0,1))){if(a=t.substr(0,1).toUpperCase(),""==(n=t.substr(1))&&(n=0),isNaN(n=Number(n)))return!1;"R"==a&&(i=0+.6*n),"Y"==a&&(i=60+.6*n),"G"==a&&(i=120+.6*n),"C"==a&&(i=180+.6*n),"B"==a&&(i=240+.6*n),"M"==a&&(i=300+.6*n),"W"==a&&(i=0,e=1-n/100,r=n/100)}return hwbToRgb(i,e,r)}function hueToNcol(t){for(;t>=360;)t-=360;return t<60?"R"+t/.6:t<120?"Y"+(t-60)/.6:t<180?"G"+(t-120)/.6:t<240?"C"+(t-180)/.6:t<300?"B"+(t-240)/.6:t<360?"M"+(t-300)/.6:void 0}function ncsToRgb(t){var e,r,a,n,i,s,o,h,f,u,l,c,b,d,g,m,p;return-1==(t=(t=(t=(t=(t=w3trim(t).toUpperCase()).replace("(","")).replace(")","")).replace("NCS","NCS ")).replace(/ /g," ")).indexOf("NCS")&&(t="NCS "+t),null!==(t=t.match(/^(?:NCS|NCS\sS)\s(\d{2})(\d{2})-(N|[A-Z])(\d{2})?([A-Z])?$/))&&(e=parseInt(t[1],10),r=parseInt(t[2],10),("N"==(a=t[3])||"Y"==a||"R"==a||"B"==a||"G"==a)&&(n=parseInt(t[4],10)||0,"N"!==a?(i=1.05*e-5.25,s=r,"Y"===a&&n<=60?o=1:"Y"===a&&n>60||"R"===a&&n<=80?o=(Math.sqrt(14884-Math.pow(h="Y"===a?n-60:n+40,2))-22)/100:"R"===a&&n>80||"B"===a?o=0:"G"===a&&(o=(Math.sqrt(33800-Math.pow(h=n-170,2))-70)/100),"Y"===a&&n<=80?f=0:"Y"===a&&n>80||"R"===a&&n<=60?f=(104-Math.sqrt(11236-Math.pow(h="Y"===a?n-80+20.5:n+20+20.5,2)))/100:"R"===a&&n>60||"B"===a&&n<=80?f=(Math.sqrt(1e4-Math.pow(h="R"===a?n-60-60:n+40-60,2))-10)/100:"B"===a&&n>80||"G"===a&&n<=40?f=(122-Math.sqrt(19881-Math.pow(h="B"===a?n-80-131:n+20-131,2)))/100:"G"===a&&n>40&&(f=0),"Y"===a?green1=(85-.85*n)/100:"R"===a&&n<=60?green1=0:"R"===a&&n>60?green1=(67.5-Math.sqrt(5776-Math.pow(h=n-60+35,2)))/100:"B"===a&&n<=60?green1=(6.5+Math.sqrt(7044.5-Math.pow(h=1*n-68.5,2)))/100:"B"===a&&n>60||"G"===a&&n<=60?green1=.9:"G"===a&&n>60&&(green1=(90-1/8*(h=n-60))/100),u=((h=(o+green1+f)/3)-o)*(100-s)/100+o,c=(h-f)*(100-s)/100+f,b=1/(u>(l=(h-green1)*(100-s)/100+green1)&&u>c?u:l>u&&l>c?l:c>u&&c>l?c:(u+l+c)/3),(g=parseInt(u*b*(100-i)/100*255,10))>255&&(g=255),(m=parseInt(l*b*(100-i)/100*255,10))>255&&(m=255),(p=parseInt(c*b*(100-i)/100*255,10))>255&&(p=255),g<0&&(g=0),m<0&&(m=0),p<0&&(p=0)):((d=parseInt(255*(1-e/100),10))>255&&(d=255),d<0&&(d=0),g=d,m=d,p=d),{r:g,g:m,b:p}))}function rgbToHsl(t,e,r){var a,n,i,s,o,h,f=[];for(i=0,f[0]=t/255,f[1]=e/255,f[2]=r/255,a=f[0],n=f[0],o=0;i=n&&(n=f[i+1],o=i+1);return 0==o&&(h=(f[1]-f[2])/(n-a)),1==o&&(h=2+(f[2]-f[0])/(n-a)),2==o&&(h=4+(f[0]-f[1])/(n-a)),isNaN(h)&&(h=0),(h*=60)<0&&(h+=360),s=(a+n)/2,{h:h,s:a==n?0:s<.5?(n-a)/(n+a):(n-a)/(2-n-a),l:s}}function rgbToHwb(t,e,r){return t/=255,e/=255,r/=255,{h:0==(chroma=(max=Math.max(t,e,r))-(min=Math.min(t,e,r)))?0:t==max?(e-r)/chroma%6*360:e==max?((r-t)/chroma+2)%6*360:((t-e)/chroma+4)%6*360,w:min,b:1-max}}function rgbToCmyk(t,e,r){var a,n,i,s;return t/=255,e/=255,r/=255,1==(s=1-(max=Math.max(t,e,r)))?(a=0,n=0,i=0):(a=(1-t-s)/(1-s),n=(1-e-s)/(1-s),i=(1-r-s)/(1-s)),{c:a,m:n,y:i,k:s}}function toHex(t){for(var e=t.toString(16);e.length<2;)e="0"+e;return e}function cl(t){console.log(t)}function w3trim(t){return t.replace(/^\s+|\s+$/g,"")}function isHex(t){return"0123456789ABCDEFabcdef".indexOf(t)>-1}function w3SetColorsByAttribute(){var t,e,r;for(e=0,t=document.getElementsByTagName("*");e1&&(r-=1),r<1/6?t+6*(e-t)*r:r<.5?e:r<2/3?t+(e-t)*(2/3-r)*6:t}if(t=w(t,360),e=w(e,100),r=w(r,100),0===e)a=n=i=r;else{var o=r<.5?r*(1+e):r+e-r*e,h=2*r-o;a=s(h,o,t+1/3),n=s(h,o,t),i=s(h,o,t-1/3)}return{r:255*a,g:255*n,b:255*i}}(s.h,f,u),l=!0,c="hsl"),s.hasOwnProperty("a")&&(h=s.a)),h=_(h),{ok:l,format:s.format||c,r:Math.min(255,Math.max(o.r,0)),g:Math.min(255,Math.max(o.g,0)),b:Math.min(255,Math.max(o.b,0)),a:h});this._originalInput=n,this._r=G.r,this._g=G.g,this._b=G.b,this._a=G.a,this._roundA=Math.round(100*this._a)/100,this._format=i.format||G.format,this._gradientType=i.gradientType,this._r<1&&(this._r=Math.round(this._r)),this._g<1&&(this._g=Math.round(this._g)),this._b<1&&(this._b=Math.round(this._b)),this._ok=G.ok}function n(t,e,r){t=w(t,255),e=w(e,255),r=w(r,255);var a,n,i=Math.max(t,e,r),s=Math.min(t,e,r),o=(i+s)/2;if(i==s)a=n=0;else{var h=i-s;switch(n=o>.5?h/(2-i-s):h/(i+s),i){case t:a=(e-r)/h+(e>1)+720)%360;--e;)n.h=(n.h+i)%360,s.push(a(n));return s}function M(t,e){e=e||6;for(var r=a(t).toHsv(),n=r.h,i=r.s,s=r.v,o=[],h=1/e;e--;)o.push(a({h:n,s:i,v:s})),s=(s+h)%1;return o}a.prototype={isDark:function(){return 128>this.getBrightness()},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var t=this.toRgb();return(299*t.r+587*t.g+114*t.b)/1e3},getLuminance:function(){var t,e,r,a=this.toRgb();return t=a.r/255,e=a.g/255,r=a.b/255,.2126*(t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4))+.7152*(e<=.03928?e/12.92:Math.pow((e+.055)/1.055,2.4))+.0722*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))},setAlpha:function(t){return this._a=_(t),this._roundA=Math.round(100*this._a)/100,this},toHsv:function(){var t=i(this._r,this._g,this._b);return{h:360*t.h,s:t.s,v:t.v,a:this._a}},toHsvString:function(){var t=i(this._r,this._g,this._b),e=Math.round(360*t.h),r=Math.round(100*t.s),a=Math.round(100*t.v);return 1==this._a?"hsv("+e+", "+r+"%, "+a+"%)":"hsva("+e+", "+r+"%, "+a+"%, "+this._roundA+")"},toHsl:function(){var t=n(this._r,this._g,this._b);return{h:360*t.h,s:t.s,l:t.l,a:this._a}},toHslString:function(){var t=n(this._r,this._g,this._b),e=Math.round(360*t.h),r=Math.round(100*t.s),a=Math.round(100*t.l);return 1==this._a?"hsl("+e+", "+r+"%, "+a+"%)":"hsla("+e+", "+r+"%, "+a+"%, "+this._roundA+")"},toHex:function(t){return s(this._r,this._g,this._b,t)},toHexString:function(t){return"#"+this.toHex(t)},toHexNumber:function(){return Number("0x"+this.toHex())},toHex8:function(t){var e,r,a,n,i,s;return e=this._r,r=this._g,a=this._b,n=this._a,i=t,s=[A(Math.round(e).toString(16)),A(Math.round(r).toString(16)),A(Math.round(a).toString(16)),A(R(n))],i&&s[0].charAt(0)==s[0].charAt(1)&&s[1].charAt(0)==s[1].charAt(1)&&s[2].charAt(0)==s[2].charAt(1)&&s[3].charAt(0)==s[3].charAt(1)?s[0].charAt(0)+s[1].charAt(0)+s[2].charAt(0)+s[3].charAt(0):s.join("")},toHex8String:function(t){return"#"+this.toHex8(t)},toRgb:function(){return{r:Math.round(this._r),g:Math.round(this._g),b:Math.round(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+Math.round(this._r)+", "+Math.round(this._g)+", "+Math.round(this._b)+")":"rgba("+Math.round(this._r)+", "+Math.round(this._g)+", "+Math.round(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:Math.round(100*w(this._r,255))+"%",g:Math.round(100*w(this._g,255))+"%",b:Math.round(100*w(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+Math.round(100*w(this._r,255))+"%, "+Math.round(100*w(this._g,255))+"%, "+Math.round(100*w(this._b,255))+"%)":"rgba("+Math.round(100*w(this._r,255))+"%, "+Math.round(100*w(this._g,255))+"%, "+Math.round(100*w(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1||!v[s(this._r,this._g,this._b,!0)])},toFilter:function(t){var e="#"+o(this._r,this._g,this._b,this._a),r=e,n=this._gradientType?"GradientType = 1, ":"";if(t){var i=a(t);r="#"+o(i._r,i._g,i._b,i._a)}return"progid:DXImageTransform.Microsoft.gradient("+n+"startColorstr="+e+",endColorstr="+r+")"},toString:function(t){var e=!!t;t=t||this._format;var r=!1,a=this._a<1&&this._a>=0;return e||!a||"hex"!==t&&"hex6"!==t&&"hex3"!==t&&"hex4"!==t&&"hex8"!==t&&"name"!==t?("rgb"===t&&(r=this.toRgbString()),"prgb"===t&&(r=this.toPercentageRgbString()),("hex"===t||"hex6"===t)&&(r=this.toHexString()),"hex3"===t&&(r=this.toHexString(!0)),"hex4"===t&&(r=this.toHex8String(!0)),"hex8"===t&&(r=this.toHex8String()),"name"===t&&(r=this.toName()),"hsl"===t&&(r=this.toHslString()),"hsv"===t&&(r=this.toHsvString()),r||this.toHexString()):"name"===t&&0===this._a?this.toName():this.toRgbString()},clone:function(){return a(this.toString())},_applyModification:function(t,e){var r=t.apply(null,[this].concat([].slice.call(e)));return this._r=r._r,this._g=r._g,this._b=r._b,this.setAlpha(r._a),this},lighten:function(){return this._applyModification(l,arguments)},brighten:function(){return this._applyModification(c,arguments)},darken:function(){return this._applyModification(b,arguments)},desaturate:function(){return this._applyModification(h,arguments)},saturate:function(){return this._applyModification(f,arguments)},greyscale:function(){return this._applyModification(u,arguments)},spin:function(){return this._applyModification(d,arguments)},_applyCombination:function(t,e){return t.apply(null,[this].concat([].slice.call(e)))},analogous:function(){return this._applyCombination(y,arguments)},complement:function(){return this._applyCombination(g,arguments)},monochromatic:function(){return this._applyCombination(M,arguments)},splitcomplement:function(){return this._applyCombination(p,arguments)},triad:function(){return this._applyCombination(m,[3])},tetrad:function(){return this._applyCombination(m,[4])}},a.fromRatio=function(e,r){if("object"==t(e)){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[i]="a"===i?e[i]:N(e[i]));e=n}return a(e,r)},a.equals=function(t,e){return!!t&&!!e&&a(t).toRgbString()==a(e).toRgbString()},a.random=function(){return a.fromRatio({r:Math.random(),g:Math.random(),b:Math.random()})},a.mix=function(t,e,r){r=0===r?0:r||50;var n=a(t).toRgb(),i=a(e).toRgb(),s=r/100;return a({r:(i.r-n.r)*s+n.r,g:(i.g-n.g)*s+n.g,b:(i.b-n.b)*s+n.b,a:(i.a-n.a)*s+n.a})},a.readability=function(t,e){var r=a(t),n=a(e);return(Math.max(r.getLuminance(),n.getLuminance())+.05)/(Math.min(r.getLuminance(),n.getLuminance())+.05)},a.isReadable=function(t,e,r){var n,i,s,o,h,f=a.readability(t,e);switch(i=!1,(s=r,o=((s=s||{level:"AA",size:"small"}).level||"AA").toUpperCase(),h=(s.size||"small").toLowerCase(),"AA"!==o&&"AAA"!==o&&(o="AA"),"small"!==h&&"large"!==h&&(h="small"),n={level:o,size:h}).level+n.size){case"AAsmall":case"AAAlarge":i=f>=4.5;break;case"AAlarge":i=f>=3;break;case"AAAsmall":i=f>=7}return i},a.mostReadable=function(t,e,r){var n,i,s,o,h=null,f=0;i=(r=r||{}).includeFallbackColors,s=r.level,o=r.size;for(var u=0;uf&&(f=n,h=a(e[u]));return a.isReadable(t,h,{level:s,size:o})||!i?h:(r.includeFallbackColors=!1,a.mostReadable(t,["#fff","#000"],r))};var k=a.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},v=a.hexNames=function(t){var e={};for(var r in t)t.hasOwnProperty(r)&&(e[t[r]]=r);return e}(k);function _(t){return(isNaN(t=parseFloat(t))||t<0||t>1)&&(t=1),t}function w(t,e){"string"==typeof(r=t)&&-1!=r.indexOf(".")&&1===parseFloat(r)&&(t="100%");var r,a,n="string"==typeof(a=t)&&-1!=a.indexOf("%");return t=Math.min(e,Math.max(0,parseFloat(t))),n&&(t=parseInt(t*e,10)/100),1e-6>Math.abs(t-e)?1:t%e/parseFloat(e)}function x(t){return Math.min(1,Math.max(0,t))}function S(t){return parseInt(t,16)}function A(t){return 1==t.length?"0"+t:""+t}function N(t){return t<=1&&(t=100*t+"%"),t}function R(t){return Math.round(255*parseFloat(t)).toString(16)}function C(t){return S(t)/255}var H,G,B,T=(G="[\\s|\\(]+("+(H="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+H+")[,|\\s]+("+H+")\\s*\\)?",B="[\\s|\\(]+("+H+")[,|\\s]+("+H+")[,|\\s]+("+H+")[,|\\s]+("+H+")\\s*\\)?",{CSS_UNIT:RegExp(H),rgb:RegExp("rgb"+G),rgba:RegExp("rgba"+B),hsl:RegExp("hsl"+G),hsla:RegExp("hsla"+B),hsv:RegExp("hsv"+G),hsva:RegExp("hsva"+B),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function F(t){return!!T.CSS_UNIT.exec(t)}return a})),w3color.prototype={toRgbString:function(){return"rgb("+this.red+", "+this.green+", "+this.blue+")"},toRgbaString:function(){return"rgba("+this.red+", "+this.green+", "+this.blue+", "+this.opacity+")"},toHwbString:function(){return"hwb("+this.hue+", "+Math.round(100*this.whiteness)+"%, "+Math.round(100*this.blackness)+"%)"},toHwbStringDecimal:function(){return"hwb("+this.hue+", "+this.whiteness+", "+this.blackness+")"},toHwbaString:function(){return"hwba("+this.hue+", "+Math.round(100*this.whiteness)+"%, "+Math.round(100*this.blackness)+"%, "+this.opacity+")"},toHslString:function(){return"hsl("+this.hue+", "+Math.round(100*this.sat)+"%, "+Math.round(100*this.lightness)+"%)"},toHslStringDecimal:function(){return"hsl("+this.hue+", "+this.sat+", "+this.lightness+")"},toHslaString:function(){return"hsla("+this.hue+", "+Math.round(100*this.sat)+"%, "+Math.round(100*this.lightness)+"%, "+this.opacity+")"},toCmykString:function(){return"cmyk("+Math.round(100*this.cyan)+"%, "+Math.round(100*this.magenta)+"%, "+Math.round(100*this.yellow)+"%, "+Math.round(100*this.black)+"%)"},toCmykStringDecimal:function(){return"cmyk("+this.cyan+", "+this.magenta+", "+this.yellow+", "+this.black+")"},toNcolString:function(){return this.ncol+", "+Math.round(100*this.whiteness)+"%, "+Math.round(100*this.blackness)+"%"},toNcolStringDecimal:function(){return this.ncol+", "+this.whiteness+", "+this.blackness},toNcolaString:function(){return this.ncol+", "+Math.round(100*this.whiteness)+"%, "+Math.round(100*this.blackness)+"%, "+this.opacity},toName:function(){var t,e,r,a=getColorArr("hexs");for(i=0;i1&&(this.sat=1),r=colorObject(hslToRgb(this.hue,this.sat,this.lightness),this.opacity,this.hue,this.sat),this.attachValues(r)},desaturate:function(t){var e,r;e=t/100||.1,this.sat-=e,this.sat<0&&(this.sat=0),r=colorObject(hslToRgb(this.hue,this.sat,this.lightness),this.opacity,this.hue,this.sat),this.attachValues(r)},lighter:function(t){var e,r;e=t/100||.1,this.lightness+=e,this.lightness>1&&(this.lightness=1),r=colorObject(hslToRgb(this.hue,this.sat,this.lightness),this.opacity,this.hue,this.sat),this.attachValues(r)},darker:function(t){var e,r;e=t/100||.1,this.lightness-=e,this.lightness<0&&(this.lightness=0),r=colorObject(hslToRgb(this.hue,this.sat,this.lightness),this.opacity,this.hue,this.sat),this.attachValues(r)},attachValues:function(t){this.red=t.red,this.green=t.green,this.blue=t.blue,this.hue=t.hue,this.sat=t.sat,this.lightness=t.lightness,this.whiteness=t.whiteness,this.blackness=t.blackness,this.cyan=t.cyan,this.magenta=t.magenta,this.yellow=t.yellow,this.black=t.black,this.ncol=t.ncol,this.opacity=t.opacity,this.valid=t.valid}} \ No newline at end of file diff --git a/docs/lib/scripts/webpage.js b/docs/lib/scripts/webpage.js new file mode 100644 index 0000000..52366e3 --- /dev/null +++ b/docs/lib/scripts/webpage.js @@ -0,0 +1 @@ +let webpageContainer,documentContainer,viewContent,leftSidebar,rightSidebar,sidebarCollapseIcons,sidebarGutters,sidebars,sidebarTargetWidth,contentTargetWidth,canvasWrapper,canvas,canvasNodes,canvasBackground,canvasBackgroundPattern,focusedCanvasNode,loadingIcon,documentType,embedType,customType,deviceSize,lastScreenWidth,isOffline=!1,collapseIconUp=["m7 15 5 5 5-5","m7 9 5-5 5 5"],collapseIconDown=["m7 20 5-5 5 5","m7 4 5 5 5-5"],isTouchDevice=isTouchCapable(),fullyInitialized=!1;function initGlobalObjects(){loadingIcon=document.createElement("div"),loadingIcon.classList.add("loading-icon"),document.body.appendChild(loadingIcon),loadingIcon.innerHTML="
",webpageContainer=document.querySelector(".webpage-container"),documentContainer=document.querySelector(".document-container"),leftSidebar=document.querySelector(".sidebar-left"),rightSidebar=document.querySelector(".sidebar-right"),sidebarCollapseIcons=Array.from(document.querySelectorAll(".sidebar-collapse-icon")),sidebarGutters=[sidebarCollapseIcons[0].parentElement,sidebarCollapseIcons[1].parentElement],sidebars=[sidebarGutters[0].parentElement,sidebarGutters[1].parentElement]}async function initializePage(){focusedCanvasNode=null,canvasWrapper=document.querySelector(".canvas-wrapper")??canvasWrapper,canvas=document.querySelector(".canvas")??canvas;let e=document.querySelectorAll(".canvas-node");canvasNodes=e.length>0?e:canvasNodes,canvasBackground=document.querySelector(".canvas-background")??canvasBackground,canvasBackgroundPattern=document.querySelector(".canvas-background pattern")??canvasBackgroundPattern,viewContent=document.querySelector(".document-container > .view-content")??document.querySelector(".document-container > .markdown-preview-view")??viewContent,fullyInitialized||(initGlobalObjects(),initializeDocumentTypes(),setupSidebars(),setupThemeToggle(),sidebarTargetWidth=await getComputedPixelValue("--sidebar-width"),contentTargetWidth=.9*await getComputedPixelValue("--line-width"),window.addEventListener("resize",(()=>onResize())),onResize(),document.body.classList.toggle("post-load",!0),document.body.classList.toggle("loading",!1),setTimeout((function(){document.body.classList.toggle("loaded",!0),document.body.classList.toggle("post-load",!1)}),2e3),fullyInitialized=!0),"video"==embedType||"embed"==embedType||"excalidraw"==customType||"kanban"==customType||"canvas"==documentType?rightSidebar.collapsed||rightSidebar.temporaryCollapse():rightSidebar.temporarilyCollapsed&&rightSidebar.collapsed&&(rightSidebar.collapse(!1),rightSidebar.temporarilyCollapsed=!1)}function initializePageEvents(e){setupHeaders(e),setupTrees(e),setupCallouts(e),setupCheckboxes(e),setupCanvas(e),setupCodeblocks(e),setupLinks(e),setupScroll(e)}function initializeDocumentTypes(){document.querySelector(".document-container > .markdown-preview-view")?documentType="markdown":document.querySelector(".canvas-wrapper")?documentType="canvas":(documentType="custom",document.querySelector(".kanban-plugin")?customType="kanban":document.querySelector(".excalidraw-plugin")&&(customType="excalidraw"))}function initializeForFileProtocol(){let e=document.querySelector(".graph-view-placeholder");e&&(console.log("Running locally, skipping graph view initialization and hiding graph."),e.style.display="none",e.previousElementSibling.style.display="none")}function onOffline(e){e.preventDefault(),e.stopPropagation(),console.log("Offline"),isOffline=!0}function onEndResize(){document.body.classList.toggle("resizing",!1)}function onStartResize(){document.body.classList.toggle("resizing",!0)}window.onload=async function(){"file:"==window.location.protocol&&initializeForFileProtocol(),await initializePage(),initializePageEvents(document)},window.addEventListener("offline",onOffline),window.onpopstate=function(e){if(e.preventDefault(),e.stopPropagation(),document.body.classList.contains("floating-sidebars")&&(!leftSidebar.collapsed||!rightSidebar.collapsed))return leftSidebar.collapse(!0),void rightSidebar.collapse(!0);loadDocument(getURLPath(),!1)};let checkStillResizingTimeout,isResizing=!1;function onResize(e=!1){function t(e,t){let o=window.innerWidth;return o>e&&oe&&o=t)}isResizing||(onStartResize(),isResizing=!0),!function(e){let t=window.innerWidth;return t>e&&null==lastScreenWidth||t>e&&lastScreenWidthe}(1.5*sidebarTargetWidth)&&(deviceSize="phone",document.body.classList.toggle("floating-sidebars",!0),document.body.classList.toggle("is-large-screen",!1),document.body.classList.toggle("is-small-screen",!1),document.body.classList.toggle("is-tablet",!1),document.body.classList.toggle("is-phone",!0),sidebars.forEach((function(e){e.collapse(!0)})),sidebarGutters.forEach((function(e){e.collapse(!1)}))):(deviceSize="large-screen",document.body.classList.toggle("floating-sidebars",!1),document.body.classList.toggle("is-large-screen",!0),document.body.classList.toggle("is-small-screen",!1),document.body.classList.toggle("is-tablet",!1),document.body.classList.toggle("is-phone",!1),sidebars.forEach((function(e){e.collapse(!1)})),document.body.classList.contains("sidebars-always-collapsible")?sidebarGutters.forEach((function(e){e.collapse(!1)})):sidebarGutters.forEach((function(e){e.collapse(!0)}))),lastScreenWidth=window.innerWidth,null!=checkStillResizingTimeout&&clearTimeout(checkStillResizingTimeout);let o=window.innerWidth;checkStillResizingTimeout=setTimeout((function(){window.innerWidth==o&&(checkStillResizingTimeout=void 0,isResizing=!1,onEndResize())}),200)}function clamp(e,t,o){return Math.min(Math.max(e,t),o)}function getElBounds(e){let t=e.getBoundingClientRect(),o=t.x,n=t.y,i=t.width,s=t.height;return{x:o,y:n,width:i,height:s,minX:o,minY:n,maxX:o+i,maxY:n+s,centerX:t.x+t.width/2,centerY:t.y+t.height/2}}async function getComputedPixelValue(e){const t=document.createElement("div");document.body.appendChild(t),t.style.position="absolute",t.style.width=`var(${e})`,await new Promise((e=>setTimeout(e,10)));const o=window.getComputedStyle(t).width;return t.remove(),parseFloat(o)}function getPointerPosition(e){let t=e.touches?Array.from(e.touches):[];return{x:t.length>0?t.reduce(((e,t)=>e+t.clientX),0)/e.touches.length:e.clientX,y:t.length>0?t.reduce(((e,t)=>e+t.clientY),0)/e.touches.length:e.clientY}}function getTouchPosition(e){return{x:e.clientX,y:e.clientY}}function getAllChildrenRecursive(e){let t=[];for(let o=0;o0||navigator.msMaxTouchPoints>0}function downloadBlob(e,t="file.txt"){if(window.navigator&&window.navigator.msSaveOrOpenBlob)return window.navigator.msSaveOrOpenBlob(e);const o=window.URL.createObjectURL(e),n=document.createElement("a");n.href=o,n.download=t,n.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window})),setTimeout((()=>{window.URL.revokeObjectURL(o),n.remove()}),100)}function extentionToTag(e){return["png","jpg","jpeg","svg","gif","bmp","ico"].includes(e)?"img":["mp4","mov","avi","webm","mpeg"].includes(e)?"video":["mp3","wav","ogg","aac"].includes(e)?"audio":["pdf"].includes(e)?"embed":void 0}let slideUp=(e,t=500)=>{e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.boxSizing="border-box",e.style.height=e.offsetHeight+"px",e.offsetHeight,e.style.overflow="hidden",e.style.height=0,e.style.paddingTop=0,e.style.paddingBottom=0,e.style.marginTop=0,e.style.marginBottom=0,window.setTimeout((async()=>{e.style.display="none",e.style.removeProperty("height"),e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}),t)},slideUpAll=(e,t=500)=>{e.forEach((async e=>{e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.boxSizing="border-box",e.style.height=e.offsetHeight+"px",e.offsetHeight,e.style.overflow="hidden",e.style.height=0,e.style.paddingTop=0,e.style.paddingBottom=0,e.style.marginTop=0,e.style.marginBottom=0})),window.setTimeout((async()=>{e.forEach((async e=>{e.style.display="none",e.style.removeProperty("height"),e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}))}),t)},slideDown=(e,t=500)=>{e.style.removeProperty("display");let o=window.getComputedStyle(e).display;"none"===o&&(o="block"),e.style.display=o;let n=e.offsetHeight;e.style.overflow="hidden",e.style.height=0,e.style.paddingTop=0,e.style.paddingBottom=0,e.style.marginTop=0,e.style.marginBottom=0,e.offsetHeight,e.style.boxSizing="border-box",e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.height=n+"px",e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),window.setTimeout((async()=>{e.style.removeProperty("height"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}),t)},slideDownAll=(e,t=500)=>{e.forEach((async e=>{e.style.removeProperty("display");let o=window.getComputedStyle(e).display;"none"===o&&(o="block"),e.style.display=o;let n=e.offsetHeight;e.style.overflow="hidden",e.style.height=0,e.style.paddingTop=0,e.style.paddingBottom=0,e.style.marginTop=0,e.style.marginBottom=0,e.offsetHeight,e.style.boxSizing="border-box",e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.height=n+"px",e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom")})),window.setTimeout((async()=>{e.forEach((async e=>{e.style.removeProperty("height"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}))}),t)};var slideToggle=(e,t=500)=>"none"===window.getComputedStyle(e).display?slideDown(e,t):slideUp(e,t),slideToggleAll=(e,t=500)=>"none"===window.getComputedStyle(e[0]).display?slideDownAll(e,t):slideUpAll(e,t);let transferDocument=document.implementation.createHTMLDocument();async function loadDocument(e,t=!0,o=!0){let n=e.split("#"),i=n[0]??e;console.log("Loading document: "+i),loadingIcon.classList.toggle("shown",!0);let s,a=getViewBounds();if(loadingIcon.style.left=a.centerX-loadingIcon.offsetWidth/2+"px",loadingIcon.style.top=a.centerY-loadingIcon.offsetHeight/2+"px",documentContainer.classList.toggle("hide",!0),documentContainer.classList.toggle("show",!1),"phone"==deviceSize&&leftSidebar.collapse(!0),!isOffline){try{s=await fetch(i)}catch(e){return console.log("Cannot use fetch API (likely due to CORS), just loading the page normally."),void window.location.assign(i)}if(s.ok){setActiveDocument(i,o,t);let a=e.split(".").pop().split("?")[0].split("#")[0].toLowerCase().trim();if(documentType="none",embedType="none",customType="none","html"==a){let e=(await s.text()).replaceAll("","").replaceAll("","").replaceAll("","");transferDocument.write(e);let t=document.importNode(transferDocument.querySelector(".document-container"),!0);documentContainer.remove(),documentContainer=t,webpageContainer.insertBefore(documentContainer,webpageContainer.children[1]),document.querySelector(".outline-tree").innerHTML=transferDocument.querySelector(".outline-tree").innerHTML;let o=n.length>1?n[1]:null;o&&document.getElementById(o).scrollIntoView(),setupRootPath(transferDocument),initializeDocumentTypes(),setTimeout((function(){initializePageEvents(documentContainer),initializePageEvents(document.querySelector(".outline-tree"))}),0),document.title=transferDocument.title,transferDocument.close()}else if(documentType="embed",embedType=extentionToTag(a),null!=embedType){let t=document.createElement(embedType);t.controls=!0,t.src=e,t.style.maxWidth="100%","embed"==embedType&&(t.style.width="100%",t.style.height="100%"),t.style.objectFit="contain",viewContent.innerHTML="",viewContent.setAttribute("class","view-content embed"),viewContent.appendChild(t),document.querySelector(".outline-tree").innerHTML="",document.title=e.split("/").pop()}else{downloadBlob(await s.blob(),e.split("/").pop())}await initializePage()}else setTimeout((function(){viewContent.innerHTML="\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t

Page Not Found

\n\t\t\t\t
\n\t\t\t
\n\t\t\t",document.querySelector(".outline-tree").innerHTML="",console.log("Page not found: "+getAbsoluteRootPath()+e);let t=getURLRootPath(getAbsoluteRootPath()+e);rootPath=t,document.querySelector("base").href=t,document.title="Page Not Found"}),1e3);return loadingIcon.classList.toggle("shown",!1),documentContainer.style.transitionDuration="",documentContainer.classList.toggle("hide",!1),documentContainer.classList.toggle("show",!0),transferDocument}setTimeout((function(){viewContent.innerHTML="\n
\n\t

You appear to be offline. Check your internet connection and then try reloading the page.

\n
",document.querySelector(".outline-tree").innerHTML="",console.log("Page offline: "+getAbsoluteRootPath()+e);let t=getURLRootPath(getAbsoluteRootPath()+e);rootPath=t,document.querySelector("base").href=t,document.title="Page Offline",documentContainer.classList.toggle("hide",!1),documentContainer.classList.toggle("show",!0),loadingIcon.classList.toggle("shown",!1)}),1e3)}function setActiveDocument(e,t=!0,o=!0){let n=e.split("#")[0]??e;document.querySelector(".tree-item.mod-active")?.classList.remove("mod-active");let i,s=Array.from(document.querySelectorAll(".tree-item > .tree-item-contents > .tree-item-link"));for(let t of s)if(t.getAttribute("href")==decodeURI(e)){let e=t.parentElement.parentElement;for(e.classList.add("mod-active"),i=e;e.hasAttribute("data-depth");)setTreeCollapsed(e,!1,!1),e=e.parentElement.parentElement;break}if(t&&i?.scrollIntoView({block:"center",inline:"nearest"}),"undefined"!=typeof nodes&&window.renderWorker){let e=nodes?.paths.findIndex((function(e){return e.endsWith(n)}))??-1;e>=0&&(window.renderWorker.activeNode=e)}o&&"file:"!=window.location.protocol&&window.history.pushState({path:n},"",n)}function setupRootPath(e){let t=e.querySelector("#root-path").getAttribute("root-path");document.querySelector("base").href=t,document.querySelector("#root-path").setAttribute("root-path",t),rootPath=t}function getAbsoluteRootPath(){return"undefined"==typeof rootPath&&setupRootPath(document),new URL(window.location.href+"/../"+rootPath).pathname}function getURLPath(e=window.location.pathname){let t=getAbsoluteRootPath();return e.substring(t.length)}function getURLRootPath(e=window.location.pathname){let t=getURLPath(e).split("/"),o="";for(let e=0;ee+t.offsetHeight),0);return void(e.markdownPreviewSizer.style.minHeight=o+"px")}let a=getComputedStyle(i).transitionDuration;a=a.endsWith("s")?parseFloat(a):a.endsWith("ms")?parseFloat(a)/1e3:0;let l=Math.min(a*Math.sqrt(s)/16,.5);i.style.transitionDuration=`${l}s`,i.style.height=t?"0px":s+"px",e.classList.toggle("is-animating",!0),e.classList.toggle("is-collapsed",t),setTimeout((function(){i.style.transitionDuration="",t||(i.style.height=""),e.classList.toggle("is-animating",!1);let o=Array.from(e.markdownPreviewSizer.children).reduce(((e,t)=>e+t.offsetHeight),0);e.markdownPreviewSizer.style.minHeight=o+"px"}),1e3*l)}function toggleTreeHeaderOpen(e,t=!0){e.collapse(!e.collapsed,t)}function hideHeader(e){if(e.forceShown)return;if(e.classList.contains("is-hidden")||e.classList.contains("is-collapsed"))return;if("none"==getComputedStyle(e).display)return;let t=e.offsetHeight;e.classList.toggle("is-hidden",!0),0!=t&&(e.style.height=t+"px"),e.style.visibility="hidden"}function showHeader(e,t=!0,o=!1,n=!1){if(n&&(e.forceShown=!0),t){let t=e.parentHeader;isHeadingWrapper(t)&&t.show(!0,!1,n)}if(o){e.querySelectorAll(".heading-wrapper").forEach((function(e){e.show(!1,!0,n)}))}e.classList.contains("is-hidden")&&!e.classList.contains("is-collapsed")&&(e.classList.toggle("is-hidden",!1),e.style.height="",e.style.visibility="")}function setupTrees(e){const t=Array.from(e.querySelectorAll(".tree-container.file-tree .tree-item")),o=Array.from(e.querySelectorAll(".tree-container.outline-tree .tree-item"));e.querySelectorAll(".tree-item-link > .collapse-icon").forEach((function(e){e.addEventListener("click",(function(t){return t.preventDefault(),t.stopPropagation(),toggleTreeCollapsed(e.parentElement.parentElement.parentElement),!1}))})),e.querySelectorAll(".collapse-tree-button").forEach((function(e){e.treeRoot=e.parentElement.parentElement,e.icon=e.firstChild,e.icon.innerHTML="";let n=e.treeRoot.classList.contains("file-tree")?t:o;e.setIcon=function(t){e.icon.children[0].setAttribute("d",t?collapseIconUp[0]:collapseIconDown[0]),e.icon.children[1].setAttribute("d",t?collapseIconUp[1]:collapseIconDown[1])},e.collapse=function(t){setTreeCollapsedAll(n,t),e.setIcon(t),e.collapsed=t},e.toggleCollapse=function(){e.collapse(!e.collapsed)},e.collapsed=0!=e.treeRoot.querySelectorAll(".tree-scroll-area + .tree-item.mod-collapsible.is-collapsed"),e.setIcon(e.collapsed),e.addEventListener("click",(function(t){return t.preventDefault(),t.stopPropagation(),e.toggleCollapse(),!1}))})),t.forEach((function(e){let t=e.querySelector(".tree-item-link");if(e.querySelector(".collapse-icon"))t?.addEventListener("click",(function(e){e.preventDefault(),e.stopPropagation();let t=this.parentElement?.parentElement;t&&toggleTreeCollapsed(t)}));else{let o=t.getAttribute("href").split(".").pop();if(!o.includes(" ")&&"html"!=o){let t=document.createElement("div");t.classList.add("nav-file-tag"),t.textContent=o.toUpperCase(),e.querySelector(".tree-item-contents").appendChild(t)}}}))}async function setTreeCollapsed(e,t,o=!0){if(!e||!e.classList.contains("mod-collapsible"))return;let n=e.querySelector(".tree-item-children");t?(e.classList.add("is-collapsed"),o?slideUp(n,100):n.style.display="none"):(e.classList.remove("is-collapsed"),o?slideDown(n,100):n.style.removeProperty("display"))}async function setTreeCollapsedAll(e,t,o=!0){let n=[];e.forEach((async e=>{if(!e||!e.classList.contains("mod-collapsible"))return;let o=e.querySelector(".tree-item-children");t?e.classList.add("is-collapsed"):e.classList.remove("is-collapsed"),n.push(o)})),t?o?slideUpAll(n,100):n.forEach((async e=>e.style.display="none")):o?slideDownAll(n,100):n.forEach((async e=>e.style.removeProperty("display")))}function toggleTreeCollapsed(e){e&&setTreeCollapsed(e,!e.classList.contains("is-collapsed"))}function toggleTreeCollapsedAll(e){e&&setTreeCollapsedAll(e,!e[0].classList.contains("is-collapsed"))}function setupCanvas(e){if("canvas"!=documentType||!e.querySelector(".canvas-wrapper"))return;e.querySelector(".canvas")?.setAttribute("style","translate: 0px 1px; scale: 1;");let t=getNodesBounds();function o(e){let t=e.touches??[];if(!(t.length>1)&&(1==e.button||0==e.button||t.length>0)){let o=getPointerPosition(e),n=!1,i=0,s=t.length,a=function(t){let a=t.touches??[],l=getPointerPosition(t);s!=a.length&&(o=l,s=a.length);let r=l.x-o.x,c=l.y-o.y,d=!1;if((1==e.button||1==a.length)&&focusedCanvasNode){let e=Math.abs(r)>Math.abs(1.5*c),t=Math.abs(c)>Math.abs(1.5*r),o=focusedCanvasNode.querySelector(".markdown-preview-sizer");if(o){let n=o.scrollHeight>o.parentElement.clientHeight+1,i=o.scrollWidth>o.parentElement.clientWidth+1;(e&&i||t&&n)&&(d=!0)}}if(d||(translateCanvas(r,c),o=l),2==a.length){let e=getPointerPosition(t,!1),o=getTouchPosition(t.touches[0]),s=getTouchPosition(t.touches[1]),a=Math.sqrt(Math.pow(o.x-s.x,2)+Math.pow(o.y-s.y,2));n||(n=!0,i=a),scaleCanvasAroundPoint(1+(a-i)/i,e.x,e.y),i=a}},l=function(e){document.body.removeEventListener("mousemove",a),document.body.removeEventListener("mouseup",l),document.body.removeEventListener("mouseenter",r),document.body.removeEventListener("touchmove",a),document.body.removeEventListener("touchend",l),document.body.removeEventListener("touchcancel",l)},r=function(e){1!=e.buttons&&4!=e.buttons&&l(e)};document.body.addEventListener("mousemove",a),document.body.addEventListener("mouseup",l),document.body.addEventListener("mouseenter",r),document.body.addEventListener("touchmove",a),document.body.addEventListener("touchend",l),document.body.addEventListener("touchcancel",l)}}setViewCenter(t.centerX,t.centerY),e.querySelectorAll(".canvas-node-container").forEach((function(e){var t=e.parentElement;function o(e){t.classList.toggle("is-focused"),null!=focusedCanvasNode&&focusedCanvasNode!=t&&(focusedCanvasNode.classList.remove("is-focused"),focusedCanvasNode.querySelector(".canvas-node-container").style.display=""),focusedCanvasNode=t,t.addEventListener("mouseleave",n),t.addEventListener("touchend",n)}function n(e){focusedCanvasNode&&(focusedCanvasNode.classList.remove("is-focused"),focusedCanvasNode=null),t.removeEventListener("mouseleave",n),t.removeEventListener("touchend",n)}e.addEventListener("mouseenter",o),e.addEventListener("touchstart",o)})),e.querySelectorAll(".canvas-node").forEach((function(e){e.addEventListener("dblclick",(function(t){fitViewToNode(e)}))})),e.querySelectorAll(".canvas-background").forEach((function(e){e.addEventListener("dblclick",(function(e){fitViewToCanvas()}))})),canvasWrapper.addEventListener("mousedown",o),canvasWrapper.addEventListener("touchstart",o);let n=0,i=0;canvasWrapper.addEventListener("mousemove",(function(e){let t=getPointerPosition(e);n=t.x,i=t.y}));let s=1,a=0,l=!1;canvasWrapper.addEventListener("wheel",(function(e){if(focusedCanvasNode){let e=focusedCanvasNode.querySelector(".markdown-preview-sizer");if(e&&e.scrollHeight>e.parentElement.clientHeight)return}if(e.preventDefault(),e.stopPropagation(),l){let t=1;t-=e.deltaY/700*t,t=clamp(t,.1,10);let o=getViewBounds();scaleCanvasAroundPoint(t,o.centerX,o.centerY)}else{let t=0==a;a-=e.deltaY/200;const o=.14*s;a=clamp(a,-o,o),t&&requestAnimationFrame(u)}}));let r=0,c=0,d=0;function u(e){if(r=e-c,0==c&&(r=30),c=e,d=.05*r+.95*d,d>50)return console.log("Scrolling too slow, turning on instant scroll"),void(l=!0);let t=s;s+=a*(r/1e3)*30,s=clamp(s,.1,10);getViewBounds();scaleCanvasAroundPoint(s/t,n,i),a*=.4,Math.abs(a)<.01?(a=0,c=0):requestAnimationFrame(u)}fitViewToCanvas()}function getViewBounds(){let e=viewContent.getBoundingClientRect(),t=e.x,o=e.y,n=e.x+e.width,i=e.y+e.height;return{x:t,y:o,width:n-t,height:i-o,minX:t,minY:o,maxX:n,maxY:i,centerX:e.x+e.width/2,centerY:e.y+e.height/2}}function getNodesBounds(){let e=1/0,t=1/0,o=-1/0,n=-1/0;canvasNodes.forEach((function(i){let s=i.getBoundingClientRect();s.xo&&(o=s.x+s.width),s.y+s.height>n&&(n=s.y+s.height)}));let i=o-e,s=n-t;return{x:e,y:t,width:i,height:s,minX:e,minY:t,maxX:o,maxY:n,centerX:e+i/2,centerY:t+s/2}}function getCanvasBounds(){let e=canvas.getBoundingClientRect(),t=e.x,o=e.y,n=e.width,i=e.height;return{x:t,y:o,width:n,height:i,minX:t,minY:o,maxX:t+n,maxY:o+i,centerX:e.x+e.width/2,centerY:e.y+e.height/2}}function scaleCanvasAroundPoint(e,t,o){let n=getCanvasBounds(),i=t-n.x,s=o-n.y,a=t-(n.x+i*e),l=o-(n.y+s*e);return scaleCanvas(e),translateCanvas(a,l),{x:a,y:l}}function scaleCanvas(e){let t=Math.max(e*canvas.style.scale,.001);canvas.style.scale=t,canvasWrapper.style.setProperty("--zoom-multiplier",1/Math.sqrt(t))}function translateCanvas(e,t){let o=canvas.style.translate,n=o.split(" "),i=n.length>0?parseFloat(o.split(" ")[0].trim()):0,s=n.length>1?parseFloat(o.split(" ")[1].trim()):i;canvas.style.translate=`${i+e}px ${s+t}px`}function setViewCenter(e,t){let o=getViewBounds();translateCanvas(o.centerX-e,o.centerY-t)}function getCanvasTranslation(){let e=canvas.style.translate,t=e.split(" "),o=t.length>0?parseFloat(e.split(" ")[0].trim()):0;return{x:o,y:t.length>1?parseFloat(e.split(" ")[1].trim()):o}}function scaleCanvasBackground(e){let t=e*canvasBackgroundPattern.getAttribute("width"),o=e*canvasBackgroundPattern.getAttribute("height");canvasBackgroundPattern.setAttribute("width",t),canvasBackgroundPattern.setAttribute("height",o)}function translateCanvasBackground(e,t){canvasBackgroundPattern.setAttribute("x",e+canvasBackgroundPattern.getAttribute("x")),canvasBackgroundPattern.setAttribute("y",t+canvasBackgroundPattern.getAttribute("y"))}function fitViewToNode(e){let t=getElBounds(e),o=getViewBounds(),n=getCanvasBounds(),i=.8*Math.min(o.width/t.width,o.height/t.height),s=n.x,a=n.y,l=s+(t.centerX-s)*i,r=a+(t.centerY-a)*i,c=o.centerX-l,d=o.centerY-r;t=getElBounds(e),canvas.style.transition="scale 0.5s cubic-bezier(0.5, -0.1, 0.5, 1.1), translate 0.5s cubic-bezier(0.5, -0.1, 0.5, 1.1)",scaleCanvas(i),translateCanvas(c,d),setTimeout((function(){canvas.style.transition=""}),550)}function fitViewToCanvas(){let e=getNodesBounds(),t=getViewBounds(),o=getCanvasBounds(),n=.8*Math.min(t.width/e.width,t.height/e.height),i=o.x,s=o.y,a=i+(e.centerX-i)*n,l=s+(e.centerY-s)*n,r=t.centerX-a,c=t.centerY-l;canvas.style.transition="scale 0.5s cubic-bezier(0.5, -0.1, 0.5, 1.1), translate 0.5s cubic-bezier(0.5, -0.1, 0.5, 1.1)",scaleCanvas(n),translateCanvas(r,c),setTimeout((function(){canvas.style.transition=""}),550)}function setupCallouts(e){e.querySelectorAll(".callout.is-collapsible .callout-title").forEach((function(e){e.addEventListener("click",(function(){var t=this.parentElement;t.classList.toggle("is-collapsed"),e.querySelector(".callout-fold").classList.toggle("is-collapsed"),slideToggle(t.querySelector(".callout-content"),100)}))}))}function setupCheckboxes(e){e.querySelectorAll(".task-list-item-checkbox").forEach((function(e){e.addEventListener("click",(function(){var e=this.parentElement;e.classList.toggle("is-checked"),e.setAttribute("data-task",e.classList.contains("is-checked")?"x":" ")}))})),e.querySelectorAll('.plugin-tasks-list-item input[type="checkbox"]').forEach((function(e){e.checked=e.parentElement.classList.contains("is-checked")})),e.querySelectorAll(".kanban-plugin__item.is-complete").forEach((function(e){e.querySelector('input[type="checkbox"]').checked=!0}))}function setupCodeblocks(e){e.querySelectorAll(".copy-code-button").forEach((function(t){t.addEventListener("click",(function(){var t=this.parentElement.querySelector("code").textContent;navigator.clipboard.writeText(t),this.textContent="Copied!",setTimeout((function(){e.querySelectorAll(".copy-code-button").forEach((function(e){e.textContent="Copy"}))}),2e3)}))}))}function setupLinks(e){e.querySelectorAll(".internal-link, .footnote-link, .tree-item:not(.mod-tree-folder) > .tree-item-contents > .tree-item-link").forEach((function(e){e.addEventListener("click",(function(t){let o=e.getAttribute("href");if(t.preventDefault(),o)if(o.startsWith("#")){let e=document.getElementById(o.substring(1));e?(e.headingWrapper?.collapse(!1,!0,!0),setTimeout((function(){e.classList.contains(".heading")?e.headingWrapper?.scrollIntoView({behavior:"smooth",block:"start"}):e.scrollIntoView({behavior:"smooth",block:"start"}),"phone"==deviceSize&&rightSidebar.collapse(!0)}),0)):console.log("No element found with id: "+o.substring(1))}else loadDocument(o,!0,!e.classList.contains("tree-item-link"))}))}))}function setupSidebars(){sidebarCollapseIcons[0].otherIcon=sidebarCollapseIcons[1],sidebarCollapseIcons[1].otherIcon=sidebarCollapseIcons[0],sidebarCollapseIcons[0].gutter=sidebarGutters[0],sidebarCollapseIcons[1].gutter=sidebarGutters[1],sidebarCollapseIcons[0].sidebar=sidebars[0],sidebarCollapseIcons[1].sidebar=sidebars[1],sidebarGutters[0].otherGutter=sidebarGutters[1],sidebarGutters[1].otherGutter=sidebarGutters[0],sidebarGutters[0].collapseIcon=sidebarCollapseIcons[0],sidebarGutters[1].collapseIcon=sidebarCollapseIcons[1],sidebars[0].otherSidebar=sidebars[1],sidebars[1].otherSidebar=sidebars[0],sidebars[0].gutter=sidebarGutters[0],sidebars[1].gutter=sidebarGutters[1],sidebars.forEach((function(e){e.collapsed=e.classList.contains("is-collapsed"),e.collapse=function(t=!0){if(!t&&this.temporarilyCollapsed&&"large-screen"==deviceSize&&this.gutter.collapse(!0),!t&&document.body.classList.contains("floating-sidebars")){document.body.addEventListener("click",(function t(o){e.collapse(!0),document.body.removeEventListener("click",t)}))}"phone"==deviceSize&&(t||e.otherSidebar.fullCollapse(!0,!0),t&&e.gutter.otherGutter.collapse(!1,!0)),"tablet"==deviceSize&&(t||e.otherSidebar.collapse(!0)),this.classList.toggle("is-collapsed",t),this.collapsed=t},e.temporaryCollapse=function(e=!0){this.temporarilyCollapsed=!0,this.collapse(!0),this.gutter.collapse(!1),this.collapsed=e},e.fullCollapse=function(e=!0,t=!1){this.collapse(e),this.gutter.collapse(!0,t),this.collapsed=e},e.toggleCollapse=function(){this.collapse(!this.collapsed)},e.toggleFullCollapse=function(){this.fullCollapse(!this.collapsed)}})),sidebarGutters.forEach((function(e){e.collapsed=e.classList.contains("is-collapsed"),e.collapse=function(e,t=!1){!t&&document.body.classList.contains("sidebars-always-collapsible")||(this.classList.toggle("is-collapsed",e),this.collapsed=e)},e.toggleCollapse=function(){this.collapse(!this.collapsed)}})),sidebarCollapseIcons.forEach((function(e){e.addEventListener("click",(function(t){t.stopPropagation(),e.sidebar.toggleCollapse()}))})),document.querySelectorAll(".sidebar-container").forEach((function(e){e.addEventListener("click",(function(e){e.stopPropagation()}))}))}function getSidebarWidthProp(){return getComputedPixelValue("--sidebar-width")}function setupThemeToggle(){function e(e,t=!1){let o=document.querySelector(".theme-toggle-input");if(o.checked=e,t){var n=document.body.style.transition;document.body.style.transition="none"}!o.classList.contains("is-checked")&&e?o.classList.add("is-checked"):o.classList.contains("is-checked")&&!e&&o.classList.remove("is-checked"),e?(document.body.classList.contains("theme-dark")&&document.body.classList.remove("theme-dark"),document.body.classList.contains("theme-light")||document.body.classList.add("theme-light")):(document.body.classList.contains("theme-light")&&document.body.classList.remove("theme-light"),document.body.classList.contains("theme-dark")||document.body.classList.add("theme-dark")),t&&setTimeout((function(){document.body.style.transition=n}),100),localStorage.setItem("theme_toggle",e?"true":"false")}null!=localStorage.getItem("theme_toggle")&&e("true"==localStorage.getItem("theme_toggle")),document.body.classList.contains("theme-light")?e(!0):e(!1),document.querySelector(".theme-toggle-input")?.addEventListener("change",(t=>{console.log("Theme toggle changed to: "+!("true"==localStorage.getItem("theme_toggle"))),e(!("true"==localStorage.getItem("theme_toggle")))}))}function setupScroll(e){if("canvas"!=documentType)return;let t=Array.from(e.querySelectorAll(".markdown-preview-view")),o=0,n=0;t.forEach((async function(e){console.log("Setting up markdown view");let t=Array.from(e.querySelectorAll(".heading-wrapper"));e.updateVisibleWindowMarkdown=function(o=!0,i=!0){let s=e.getBoundingClientRect();n=Math.min(.1*s.height,150);let a=s.top-n,l=s.bottom+n;async function r(e){let t=e?.getBoundingClientRect();if(!t)return;let n=t.topl&&t.bottom>l;n&&o?e.hide():!n&&i&&e.show()}for(let e=0;en/3&&e.updateVisibleWindowMarkdown(!1,!0),o=e.scrollTop}))})),setInterval((async function(){t.length>0&&(t[o].updateVisibleWindowMarkdown(),o=(o+1)%t.length)}),200)}function setupExcalidraw(e){e.querySelectorAll(".excalidraw-svg svg").forEach((function(e){let t=e.querySelector("rect").getAttribute("fill")>"#7F7F7F";e.classList.add(t?"light":"dark")}))} \ No newline at end of file diff --git a/docs/lib/styles/generated-styles.css b/docs/lib/styles/generated-styles.css new file mode 100644 index 0000000..a3ae82a --- /dev/null +++ b/docs/lib/styles/generated-styles.css @@ -0,0 +1 @@ +body{--line-width:50em;--line-width-adaptive:50em;--file-line-width:50em;--content-width:500em;--sidebar-width:calc(min(22em, 80vw));--collapse-arrow-size:0.35em;--tree-horizontal-spacing:0.6em;--tree-vertical-spacing:0.6em;--sidebar-margin:24px}body{--zoom-factor:1!important;--font-text-override:'CMU Serif','Noto Serif JP'!important;--font-print-override:'CMU Serif','Noto Serif JP'!important;--font-monospace-override:'CMU Typewriter Text'!important;--font-text-size:16px} \ No newline at end of file diff --git a/docs/lib/styles/obsidian-styles.css b/docs/lib/styles/obsidian-styles.css new file mode 100644 index 0000000..d0df5b5 --- /dev/null +++ b/docs/lib/styles/obsidian-styles.css @@ -0,0 +1 @@ +.markdown-preview-view .heading-collapse-indicator{margin-left:calc(0px - var(--collapse-arrow-size) - 10px)!important;padding:0 0!important}.node-insert-event{animation-duration:unset!important;animation-name:none!important}h1,h2,h3,h4,h5,h6{margin-top:0!important}hr{border:none;border-top:var(--hr-thickness) solid;border-color:var(--hr-color)}@media print{html body>:not(.print){display:unset!important}.collapse-indicator{display:none!important}.is-collapsed>element>.collapse-indicator{display:unset!important}}.list-collapse-indicator{display:none!important}.collapse-icon::before{content:"​"!important;visibility:hidden!important}.mod-header{display:none!important}.markdown-embed .markdown-embed-content .markdown-preview-sizer{padding-left:1em!important}.markdown-embed .embed-title.markdown-embed-title{display:none}.markdown-embed-link{display:none!important}.canvas-card-menu{display:none;cursor:default!important}.canvas-controls{display:none;cursor:default!important}.canvas-background{pointer-events:visible!important;cursor:grab!important}.canvas-background:active{cursor:grabbing!important}.canvas-node-connection-point{display:none;cursor:default!important}.canvas-node-content{backface-visibility:visible!important}.canvas-menu-container{display:none}.canvas-node-content-blocker{cursor:pointer!important}.canvas-wrapper{position:relative;cursor:default!important}.canvas-node-resizer{cursor:default!important}.canvas-node-container{cursor:default!important}.markdown-rendered pre:not(:hover)>button.copy-code-button{display:unset;opacity:0}.markdown-rendered pre:hover>button.copy-code-button{opacity:1}.markdown-rendered pre button.copy-code-button{transition:opacity .2s ease-in-out,width .3s ease-in-out,background-color .2s ease-in-out;text-overflow:clip}.markdown-rendered pre>button.copy-code-button:hover{background-color:var(--interactive-normal)}.markdown-rendered pre>button.copy-code-button:active{background-color:var(--interactive-hover);box-shadow:var(--input-shadow);transition:none}@font-face{font-family:MJXZERO;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Zero.woff") format("woff")}@font-face{font-family:MJXTEX;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-B;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff") format("woff")}@font-face{font-family:MJXTEX-I;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff") format("woff")}@font-face{font-family:MJXTEX-MI;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff") format("woff")}@font-face{font-family:MJXTEX-BI;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff") format("woff")}@font-face{font-family:MJXTEX-S1;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-S2;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-S3;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-S4;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-A;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-C;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-CB;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff") format("woff")}@font-face{font-family:MJXTEX-FR;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-FRB;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff") format("woff")}@font-face{font-family:MJXTEX-SS;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-SSB;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff") format("woff")}@font-face{font-family:MJXTEX-SSI;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff") format("woff")}@font-face{font-family:MJXTEX-SC;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-T;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-V;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff") format("woff")}@font-face{font-family:MJXTEX-VB;src:url("https://publish.obsidian.md/lib/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff") format("woff")}@font-face{font-family:'Avenir Next';font-weight:400;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/94f2f163d4b698242fef.otf')}@font-face{font-family:Inter;font-style:normal;font-weight:200;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/72505e6a122c6acd5471.woff2') format('woff2')}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/2d5198822ab091ce4305.woff2') format('woff2')}@font-face{font-family:Inter;font-weight:400;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/c8ba52b05a9ef10f4758.woff2')}@font-face{font-family:Inter;font-weight:400;font-style:italic;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/cb10ffd7684cd9836a05.woff2')}@font-face{font-family:Inter;font-weight:600;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/b5f0f109bc88052d4000.woff2')}@font-face{font-family:Inter;font-weight:800;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/cbe0ae49c52c920fd563.woff2')}@font-face{font-family:Inter;font-weight:800;font-style:italic;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/535a6cf662596b3bd6a6.woff2')}@font-face{font-family:'Source Code Pro';font-weight:400;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/70cc7ff27245e82ad414.ttf')}@font-face{font-family:'Source Code Pro';font-weight:400;font-style:italic;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/454577c22304619db035.ttf')}@font-face{font-family:'Source Code Pro';font-weight:700;font-style:normal;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/52ac8f3034507f1d9e53.ttf')}@font-face{font-family:'Source Code Pro';font-weight:700;font-style:italic;font-display:swap;src:url('https://publish.obsidian.md/public/fonts/05b618077343fbbd92b7.ttf')}@font-face{font-family:'Flow Circular';font-display:swap;src:url('https://publish.obsidian.md/public/fonts/853ff76f08786ae44ca0.woff')}:root{--highlight-bg-color:rgba(180, 0, 170, 1);--highlight-selected-bg-color:rgba(0, 100, 0, 1)}:root{--annotation-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,");--input-focus-border-color:Highlight;--input-focus-outline:1px solid Canvas;--input-unfocused-border-color:transparent;--input-disabled-border-color:transparent;--input-hover-border-color:black;--link-outline:none}:root{--xfa-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,");--xfa-focus-outline:auto}:root{--viewer-container-height:0;--pdfViewer-padding-bottom:0;--page-margin:1px auto -8px;--page-border:9px solid transparent;--spreadHorizontalWrapped-margin-LR:-3.5px;--loading-icon-delay:400ms}[data-main-rotation="90"]{transform:rotate(90deg) translateY(-100%)}[data-main-rotation="180"]{transform:rotate(180deg) translate(-100%,-100%)}[data-main-rotation="270"]{transform:rotate(270deg) translateX(-100%)}.hiddenCopyElement{position:absolute;top:0;left:0;width:0;height:0;display:none}body{--anim-duration-none:0;--anim-duration-superfast:70ms;--anim-duration-fast:140ms;--anim-duration-moderate:300ms;--anim-duration-slow:560ms;--anim-motion-smooth:cubic-bezier(0.45, 0.05, 0.55, 0.95);--anim-motion-delay:cubic-bezier(0.65, 0.05, 0.36, 1);--anim-motion-jumpy:cubic-bezier(0.68, -0.55, 0.27, 1.55);--anim-motion-swing:cubic-bezier(0, 0.55, 0.45, 1);--blockquote-border-thickness:2px;--blockquote-border-color:var(--interactive-accent);--blockquote-font-style:normal;--blockquote-color:inherit;--blockquote-background-color:transparent;--bold-weight:var(--font-semibold);--bold-color:inherit;--border-width:1px;--button-radius:var(--input-radius);--callout-border-width:0px;--callout-border-opacity:0.25;--callout-padding:var(--size-4-3) var(--size-4-3) var(--size-4-3) var(--size-4-6);--callout-radius:var(--radius-s);--callout-blend-mode:var(--highlight-mix-blend-mode);--callout-title-color:inherit;--callout-title-padding:0;--callout-title-size:inherit;--callout-content-padding:0;--callout-content-background:transparent;--callout-bug:var(--color-red-rgb);--callout-default:var(--color-blue-rgb);--callout-error:var(--color-red-rgb);--callout-example:var(--color-purple-rgb);--callout-fail:var(--color-red-rgb);--callout-important:var(--color-cyan-rgb);--callout-info:var(--color-blue-rgb);--callout-question:var(--color-orange-rgb);--callout-success:var(--color-green-rgb);--callout-summary:var(--color-cyan-rgb);--callout-tip:var(--color-cyan-rgb);--callout-todo:var(--color-blue-rgb);--callout-warning:var(--color-orange-rgb);--callout-quote:158,158,158;--canvas-background:var(--background-primary);--canvas-card-label-color:var(--text-faint);--canvas-color-1:var(--color-red-rgb);--canvas-color-2:var(--color-orange-rgb);--canvas-color-3:var(--color-yellow-rgb);--canvas-color-4:var(--color-green-rgb);--canvas-color-5:var(--color-cyan-rgb);--canvas-color-6:var(--color-purple-rgb);--canvas-dot-pattern:var(--color-base-30);--checkbox-radius:var(--radius-s);--checkbox-size:var(--font-text-size);--checkbox-marker-color:var(--background-primary);--checkbox-color:var(--interactive-accent);--checkbox-color-hover:var(--interactive-accent-hover);--checkbox-border-color:var(--text-faint);--checkbox-border-color-hover:var(--text-muted);--checklist-done-decoration:line-through;--checklist-done-color:var(--text-muted);--code-white-space:pre-wrap;--code-radius:var(--radius-s);--code-size:var(--font-smaller);--code-background:var(--background-primary-alt);--code-normal:var(--text-muted);--code-comment:var(--text-faint);--code-function:var(--color-yellow);--code-important:var(--color-orange);--code-keyword:var(--color-pink);--code-operator:var(--color-red);--code-property:var(--color-cyan);--code-punctuation:var(--text-muted);--code-string:var(--color-green);--code-tag:var(--color-red);--code-value:var(--color-purple);--collapse-icon-color:var(--text-faint);--collapse-icon-color-collapsed:var(--text-accent);--cursor:default;--cursor-link:pointer;--dialog-width:560px;--dialog-max-width:80vw;--dialog-max-height:85vh;--divider-color:var(--background-modifier-border);--divider-color-hover:var(--interactive-accent);--divider-width:1px;--divider-width-hover:3px;--divider-vertical-height:calc(100% - var(--header-height));--drag-ghost-background:rgba(0, 0, 0, 0.85);--drag-ghost-text-color:#fff;--embed-max-height:4000px;--embed-canvas-max-height:400px;--embed-background:inherit;--embed-border-left:2px solid var(--interactive-accent);--embed-border-right:none;--embed-border-top:none;--embed-border-bottom:none;--embed-padding:0 0 0 var(--size-4-6);--embed-font-style:inherit;--embed-block-shadow-hover:0 0 0 1px var(--background-modifier-border),inset 0 0 0 1px var(--background-modifier-border);--file-line-width:700px;--file-folding-offset:24px;--file-margins:var(--size-4-8);--file-header-font-size:var(--font-ui-small);--file-header-font-weight:400;--file-header-border:var(--border-width) solid transparent;--file-header-justify:center;--font-smallest:0.8em;--font-smaller:0.875em;--font-small:0.933em;--font-ui-smaller:12px;--font-ui-small:13px;--font-ui-medium:15px;--font-ui-large:20px;--font-thin:100;--font-extralight:200;--font-light:300;--font-normal:400;--font-medium:500;--font-semibold:600;--font-bold:700;--font-extrabold:800;--font-black:900;--footnote-size:var(--font-smaller);--graph-controls-width:240px;--graph-text:var(--text-normal);--graph-line:var(--color-base-35, var(--background-modifier-border-focus));--graph-node:var(--text-muted);--graph-node-unresolved:var(--text-faint);--graph-node-focused:var(--text-accent);--graph-node-tag:var(--color-green);--graph-node-attachment:var(--color-yellow);--heading-formatting:var(--text-faint);--heading-spacing:calc(var(--p-spacing) * 2.5);--h1-color:inherit;--h2-color:inherit;--h3-color:inherit;--h4-color:inherit;--h5-color:inherit;--h6-color:inherit;--h1-font:inherit;--h2-font:inherit;--h3-font:inherit;--h4-font:inherit;--h5-font:inherit;--h6-font:inherit;--h1-line-height:1.2;--h2-line-height:1.2;--h3-line-height:1.3;--h4-line-height:1.4;--h5-line-height:var(--line-height-normal);--h6-line-height:var(--line-height-normal);--h1-size:1.802em;--h2-size:1.602em;--h3-size:1.424em;--h4-size:1.266em;--h5-size:1.125em;--h6-size:1em;--h1-style:normal;--h2-style:normal;--h3-style:normal;--h4-style:normal;--h5-style:normal;--h6-style:normal;--h1-variant:normal;--h2-variant:normal;--h3-variant:normal;--h4-variant:normal;--h5-variant:normal;--h6-variant:normal;--h1-weight:700;--h2-weight:600;--h3-weight:600;--h4-weight:600;--h5-weight:600;--h6-weight:600;--header-height:40px;--hr-color:var(--background-modifier-border);--hr-thickness:2px;--icon-size:var(--icon-m);--icon-stroke:var(--icon-m-stroke-width);--icon-xs:14px;--icon-s:16px;--icon-m:18px;--icon-l:18px;--icon-xl:32px;--icon-xs-stroke-width:2px;--icon-s-stroke-width:2px;--icon-m-stroke-width:1.75px;--icon-l-stroke-width:1.75px;--icon-xl-stroke-width:1.25px;--icon-color:var(--text-muted);--icon-color-hover:var(--text-muted);--icon-color-active:var(--text-accent);--icon-color-focused:var(--text-normal);--icon-opacity:0.85;--icon-opacity-hover:1;--icon-opacity-active:1;--clickable-icon-radius:var(--radius-s);--indentation-guide-width:1px;--indentation-guide-width-active:1px;--indentation-guide-color:rgba(var(--mono-rgb-100), 0.12);--indentation-guide-color-active:rgba(var(--mono-rgb-100), 0.3);--inline-title-color:var(--h1-color);--inline-title-font:var(--h1-font);--inline-title-line-height:var(--h1-line-height);--inline-title-size:var(--h1-size);--inline-title-style:var(--h1-style);--inline-title-variant:var(--h1-variant);--inline-title-weight:var(--h1-weight);--inline-title-margin-bottom:0.5em;--input-height:30px;--input-radius:5px;--input-font-weight:var(--font-normal);--input-border-width:1px;--italic-color:inherit;--italic-weight:inherit;--layer-cover:5;--layer-sidedock:10;--layer-status-bar:15;--layer-popover:30;--layer-slides:45;--layer-modal:50;--layer-notice:60;--layer-menu:65;--layer-tooltip:70;--layer-dragged-item:80;--line-height-normal:1.5;--line-height-tight:1.3;--link-color:var(--text-accent);--link-color-hover:var(--text-accent-hover);--link-decoration:underline;--link-decoration-hover:underline;--link-decoration-thickness:auto;--link-external-color:var(--text-accent);--link-external-color-hover:var(--text-accent-hover);--link-external-decoration:underline;--link-external-decoration-hover:underline;--link-external-filter:none;--link-unresolved-color:var(--text-accent);--link-unresolved-opacity:0.7;--link-unresolved-filter:none;--link-unresolved-decoration-style:solid;--link-unresolved-decoration-color:hsla(var(--interactive-accent-hsl), 0.3);--list-indent:2em;--list-spacing:0.075em;--list-marker-color:var(--text-faint);--list-marker-color-hover:var(--text-muted);--list-marker-color-collapsed:var(--text-accent);--list-bullet-border:none;--list-bullet-radius:50%;--list-bullet-size:0.3em;--list-bullet-transform:none;--list-numbered-style:decimal;--nav-item-size:var(--font-ui-small);--nav-item-color:var(--text-muted);--nav-item-color-hover:var(--text-normal);--nav-item-color-active:var(--text-normal);--nav-item-color-selected:var(--text-normal);--nav-item-color-highlighted:var(--text-accent-hover);--nav-item-background-hover:var(--background-modifier-hover);--nav-item-background-active:var(--background-modifier-hover);--nav-item-background-selected:hsla(var(--color-accent-hsl), 0.15);--nav-item-padding:var(--size-4-1) var(--size-4-2) var(--size-4-1) var(--size-4-6);--nav-item-parent-padding:var(--nav-item-padding);--nav-item-children-padding-left:var(--size-2-2);--nav-item-children-margin-left:var(--size-4-3);--nav-item-weight:inherit;--nav-item-weight-hover:inherit;--nav-item-weight-active:inherit;--nav-item-white-space:nowrap;--nav-indentation-guide-width:var(--indentation-guide-width);--nav-indentation-guide-color:var(--indentation-guide-color);--nav-collapse-icon-color:var(--collapse-icon-color);--nav-collapse-icon-color-collapsed:var(--text-faint);--metadata-background:transparent;--metadata-display-reading:block;--metadata-display-editing:block;--metadata-max-width:none;--metadata-padding:var(--size-4-2) 0;--metadata-border-color:var(--background-modifier-border);--metadata-border-radius:0;--metadata-border-width:0;--metadata-divider-color:var(--background-modifier-border);--metadata-divider-color-hover:transparent;--metadata-divider-color-focus:transparent;--metadata-divider-width:0;--metadata-gap:3px;--metadata-property-padding:0;--metadata-property-radius:6px;--metadata-property-background:transparent;--metadata-property-background-hover:transparent;--metadata-property-background-active:var(--background-modifier-hover);--metadata-label-background-hover:transparent;--metadata-label-background-active:var(--background-modifier-hover);--metadata-label-font-size:var(--font-smaller);--metadata-label-font-weight:inherit;--metadata-label-text-color:var(--text-muted);--metadata-label-text-color-hover:var(--text-muted);--metadata-label-width:9em;--metadata-input-height:calc(var(--font-text-size) * 1.75);--metadata-input-text-color:var(--text-normal);--metadata-input-font-size:var(--font-smaller);--metadata-input-background:transparent;--metadata-input-background-hover:transparent;--metadata-input-background-active:var(--background-modifier-hover);--metadata-sidebar-label-font-size:var(--font-ui-small);--metadata-sidebar-input-font-size:var(--font-ui-small);--modal-background:var(--background-primary);--modal-width:90vw;--modal-height:85vh;--modal-max-width:1100px;--modal-max-height:1000px;--modal-max-width-narrow:800px;--modal-border-width:var(--border-width);--modal-border-color:var(--color-base-40, var(--background-modifier-border-focus));--modal-radius:var(--radius-l);--modal-community-sidebar-width:280px;--pill-color:var(--text-muted);--pill-color-hover:var(--text-normal);--pill-color-remove:var(--text-faint);--pill-color-remove-hover:var(--text-accent);--pill-decoration:none;--pill-decoration-hover:none;--pill-background:transparent;--pill-background-hover:transparent;--pill-border-color:var(--background-modifier-border);--pill-border-color-hover:var(--background-modifier-border-hover);--pill-border-width:var(--border-width);--pill-padding-x:0.65em;--pill-padding-y:0.25em;--pill-radius:2em;--pill-weight:inherit;--p-spacing:1rem;--p-spacing-empty:0rem;--pdf-background:var(--background-primary);--pdf-page-background:var(--background-primary);--pdf-shadow:0 0 0 1px rgba(0, 0, 0, 0.05),0 2px 8px rgba(0, 0, 0, 0.1);--pdf-spread-shadow:0 0 0 1px rgba(0, 0, 0, 0.05);--pdf-sidebar-background:var(--background-primary);--pdf-thumbnail-shadow:0 0 0 1px rgba(0, 0, 0, 0.15),0 2px 8px rgba(0, 0, 0, 0.2);--popover-width:450px;--popover-height:400px;--popover-max-height:70vh;--popover-pdf-width:600px;--popover-pdf-height:800px;--popover-font-size:var(--font-text-size);--prompt-width:700px;--prompt-max-width:80vw;--prompt-max-height:70vh;--prompt-border-width:var(--border-width);--prompt-border-color:var(--color-base-40, var(--background-modifier-border-focus));--radius-s:4px;--radius-m:8px;--radius-l:12px;--radius-xl:16px;--ribbon-background:var(--background-secondary);--ribbon-background-collapsed:var(--background-primary);--ribbon-width:44px;--ribbon-padding:var(--size-4-2) var(--size-4-1) var(--size-4-3);--scrollbar-active-thumb-bg:rgba(var(--mono-rgb-100), 0.2);--scrollbar-bg:rgba(var(--mono-rgb-100), 0.05);--scrollbar-thumb-bg:rgba(var(--mono-rgb-100), 0.1);--search-clear-button-color:var(--text-muted);--search-clear-button-size:13px;--search-icon-color:var(--text-muted);--search-icon-size:18px;--search-result-background:var(--background-primary);--size-2-1:2px;--size-2-2:4px;--size-2-3:6px;--size-4-1:4px;--size-4-2:8px;--size-4-3:12px;--size-4-4:16px;--size-4-5:20px;--size-4-6:24px;--size-4-8:32px;--size-4-9:36px;--size-4-12:48px;--size-4-16:64px;--size-4-18:72px;--sidebar-markdown-font-size:calc(var(--font-text-size) * 0.9);--sidebar-tab-text-display:none;--slider-thumb-border-width:1px;--slider-thumb-border-color:var(--background-modifier-border-hover);--slider-thumb-height:18px;--slider-thumb-width:18px;--slider-thumb-y:-6px;--slider-thumb-radius:50%;--slider-s-thumb-size:15px;--slider-s-thumb-position:-5px;--slider-track-background:var(--background-modifier-border);--slider-track-height:3px;--status-bar-background:var(--background-secondary);--status-bar-border-color:var(--divider-color);--status-bar-border-width:1px 0 0 1px;--status-bar-font-size:var(--font-ui-smaller);--status-bar-text-color:var(--text-muted);--status-bar-position:fixed;--status-bar-radius:var(--radius-m) 0 0 0;--status-bar-scroll-padding:calc(var(--status-bar-font-size) + 18px);--swatch-radius:14px;--swatch-height:24px;--swatch-width:24px;--swatch-shadow:inset 0 0 0 1px rgba(var(--mono-rgb-100), 0.15);--tab-background-active:var(--background-primary);--tab-text-color:var(--text-faint);--tab-text-color-active:var(--text-muted);--tab-text-color-focused:var(--text-muted);--tab-text-color-focused-active:var(--text-muted);--tab-text-color-focused-highlighted:var(--text-accent);--tab-text-color-focused-active-current:var(--text-normal);--tab-font-size:var(--font-ui-small);--tab-font-weight:inherit;--tab-container-background:var(--background-secondary);--tab-divider-color:var(--background-modifier-border-hover);--tab-outline-color:var(--divider-color);--tab-outline-width:1px;--tab-curve:6px;--tab-radius:var(--radius-s);--tab-radius-active:6px 6px 0 0;--tab-width:200px;--tab-max-width:320px;--tab-stacked-pane-width:700px;--tab-stacked-header-width:var(--header-height);--tab-stacked-font-size:var(--font-ui-small);--tab-stacked-font-weight:400;--tab-stacked-text-align:left;--tab-stacked-text-transform:rotate(0deg);--tab-stacked-text-writing-mode:vertical-lr;--tab-stacked-shadow:-8px 0 8px 0 rgba(0, 0, 0, 0.05);--table-background:transparent;--table-border-width:1px;--table-border-color:var(--background-modifier-border);--table-white-space:normal;--table-header-background:var(--table-background);--table-header-background-hover:inherit;--table-header-border-width:var(--table-border-width);--table-header-border-color:var(--table-border-color);--table-header-font:inherit;--table-header-size:var(--font-text-size);--table-header-weight:var(--bold-weight);--table-header-color:var(--text-normal);--table-line-height:var(--line-height-tight);--table-text-size:inherit;--table-text-color:inherit;--table-column-max-width:none;--table-column-alt-background:var(--table-background);--table-column-first-border-width:var(--table-border-width);--table-column-last-border-width:var(--table-border-width);--table-row-background-hover:var(--table-background);--table-row-alt-background:var(--table-background);--table-row-last-border-width:var(--table-border-width);--tag-size:var(--font-smaller);--tag-color:var(--text-accent);--tag-color-hover:var(--text-accent);--tag-decoration:none;--tag-decoration-hover:none;--tag-background:hsla(var(--interactive-accent-hsl), 0.1);--tag-background-hover:hsla(var(--interactive-accent-hsl), 0.2);--tag-border-color:hsla(var(--interactive-accent-hsl), 0.15);--tag-border-color-hover:hsla(var(--interactive-accent-hsl), 0.15);--tag-border-width:0px;--tag-padding-x:0.65em;--tag-padding-y:0.25em;--tag-radius:2em;--tag-weight:inherit;--titlebar-background:var(--background-secondary);--titlebar-background-focused:var(--background-secondary-alt);--titlebar-border-width:0px;--titlebar-border-color:var(--background-modifier-border);--titlebar-text-color:var(--text-muted);--titlebar-text-color-focused:var(--text-normal);--titlebar-text-weight:var(--font-bold);--toggle-border-width:2px;--toggle-width:40px;--toggle-radius:18px;--toggle-thumb-color:white;--toggle-thumb-radius:18px;--toggle-thumb-height:18px;--toggle-thumb-width:18px;--toggle-s-border-width:2px;--toggle-s-width:34px;--toggle-s-thumb-height:15px;--toggle-s-thumb-width:15px;--vault-name-font-size:var(--font-ui-small);--vault-name-font-weight:var(--font-medium);--vault-name-color:var(--text-normal);--workspace-background-translucent:rgba(var(--mono-rgb-0), 0.6);--accent-h:258;--accent-s:88%;--accent-l:66%;--background-primary:var(--color-base-00);--background-primary-alt:var(--color-base-10);--background-secondary:var(--color-base-20);--background-modifier-hover:rgba(var(--mono-rgb-100), 0.075);--background-modifier-active-hover:hsla(var(--interactive-accent-hsl), 0.15);--background-modifier-border:var(--color-base-30);--background-modifier-border-hover:var(--color-base-35);--background-modifier-border-focus:var(--color-base-40);--background-modifier-error-rgb:var(--color-red-rgb);--background-modifier-error:var(--color-red);--background-modifier-error-hover:var(--color-red);--background-modifier-success-rgb:var(--color-green-rgb);--background-modifier-success:var(--color-green);--background-modifier-message:rgba(0, 0, 0, 0.9);--background-modifier-form-field:var(--color-base-00);--text-normal:var(--color-base-100);--text-muted:var(--color-base-70);--text-faint:var(--color-base-50);--text-on-accent:white;--text-on-accent-inverted:black;--text-error:var(--color-red);--text-warning:var(--color-orange);--text-success:var(--color-green);--text-selection:hsla(var(--color-accent-hsl), 0.2);--text-highlight-bg-rgb:255,208,0;--text-highlight-bg:rgba(var(--text-highlight-bg-rgb), 0.4);--text-accent:var(--color-accent);--text-accent-hover:var(--color-accent-2);--interactive-normal:var(--color-base-00);--interactive-hover:var(--color-base-10);--interactive-accent-hsl:var(--color-accent-hsl);--interactive-accent:var(--color-accent-1);--interactive-accent-hover:var(--color-accent-2)}.theme-light{color-scheme:light;--highlight-mix-blend-mode:darken;--mono-rgb-0:255,255,255;--mono-rgb-100:0,0,0;--color-red-rgb:233,49,71;--color-red:#e93147;--color-orange-rgb:236,117,0;--color-orange:#ec7500;--color-yellow-rgb:224,172,0;--color-yellow:#e0ac00;--color-green-rgb:8,185,78;--color-green:#08b94e;--color-cyan-rgb:0,191,188;--color-cyan:#00bfbc;--color-blue-rgb:8,109,221;--color-blue:#086ddd;--color-purple-rgb:120,82,238;--color-purple:#7852ee;--color-pink-rgb:213,57,132;--color-pink:#d53984;--color-base-00:#ffffff;--color-base-05:#fcfcfc;--color-base-10:#fafafa;--color-base-20:#f6f6f6;--color-base-25:#e3e3e3;--color-base-30:#e0e0e0;--color-base-35:#d4d4d4;--color-base-40:#bdbdbd;--color-base-50:#ababab;--color-base-60:#707070;--color-base-70:#5c5c5c;--color-base-100:#222222;--color-accent-hsl:var(--accent-h),var(--accent-s),var(--accent-l);--color-accent:hsl(var(--accent-h), var(--accent-s), var(--accent-l));--color-accent-1:hsl(calc(var(--accent-h) - 1), calc(var(--accent-s) * 1.01), calc(var(--accent-l) * 1.075));--color-accent-2:hsl(calc(var(--accent-h) - 3), calc(var(--accent-s) * 1.02), calc(var(--accent-l) * 1.15));--background-secondary-alt:var(--color-base-05);--background-modifier-box-shadow:rgba(0, 0, 0, 0.1);--background-modifier-cover:rgba(220, 220, 220, 0.4);--input-shadow:inset 0 0 0 1px rgba(0, 0, 0, 0.12),0 2px 3px 0 rgba(0,0,0,.05),0 1px 1.5px 0 rgba(0,0,0,.03),0 1px 2px 0 rgba(0,0,0,.04),0 0 0 0 transparent;--input-shadow-hover:inset 0 0 0 1px rgba(0, 0, 0, 0.17),0 2px 3px 0 rgba(0,0,0,.1),0 1px 1.5px 0 rgba(0,0,0,.03),0 1px 2px 0 rgba(0,0,0,.04),0 0 0 0 transparent;--shadow-s:0px 1px 2px rgba(0, 0, 0, 0.028),0px 3.4px 6.7px rgba(0, 0, 0, .042),0px 15px 30px rgba(0, 0, 0, .07);--shadow-l:0px 1.8px 7.3px rgba(0, 0, 0, 0.071),0px 6.3px 24.7px rgba(0, 0, 0, 0.112),0px 30px 90px rgba(0, 0, 0, 0.2)}.theme-dark{color-scheme:dark;--highlight-mix-blend-mode:lighten;--mono-rgb-0:0,0,0;--mono-rgb-100:255,255,255;--color-red-rgb:251,70,76;--color-red:#fb464c;--color-orange-rgb:233,151,63;--color-orange:#e9973f;--color-yellow-rgb:224,222,113;--color-yellow:#e0de71;--color-green-rgb:68,207,110;--color-green:#44cf6e;--color-cyan-rgb:83,223,221;--color-cyan:#53dfdd;--color-blue-rgb:2,122,255;--color-blue:#027aff;--color-purple-rgb:168,130,255;--color-purple:#a882ff;--color-pink-rgb:250,153,205;--color-pink:#fa99cd;--color-base-00:#1e1e1e;--color-base-05:#212121;--color-base-10:#242424;--color-base-20:#262626;--color-base-25:#2a2a2a;--color-base-30:#363636;--color-base-35:#3f3f3f;--color-base-40:#555555;--color-base-50:#666666;--color-base-60:#999999;--color-base-70:#b3b3b3;--color-base-100:#dadada;--color-accent-hsl:var(--accent-h),var(--accent-s),var(--accent-l);--color-accent:hsl(var(--accent-h), var(--accent-s), var(--accent-l));--color-accent-1:hsl(calc(var(--accent-h) - 3), calc(var(--accent-s) * 1.02), calc(var(--accent-l) * 1.15));--color-accent-2:hsl(calc(var(--accent-h) - 5), calc(var(--accent-s) * 1.05), calc(var(--accent-l) * 1.29));--background-modifier-form-field:var(--color-base-25);--background-secondary-alt:var(--color-base-30);--interactive-normal:var(--color-base-30);--interactive-hover:var(--color-base-35);--text-accent:var(--color-accent-1);--interactive-accent:var(--color-accent);--interactive-accent-hover:var(--color-accent-1);--background-modifier-box-shadow:rgba(0, 0, 0, 0.3);--background-modifier-cover:rgba(10, 10, 10, 0.4);--text-selection:hsla(var(--interactive-accent-hsl), 0.25);--input-shadow:inset 0 0.5px 0.5px 0.5px rgba(255, 255, 255, 0.09),0 2px 4px 0 rgba(0,0,0,.15),0 1px 1.5px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.2),0 0 0 0 transparent;--input-shadow-hover:inset 0 0.5px 1px 0.5px rgba(255, 255, 255, 0.16),0 2px 3px 0 rgba(0,0,0,.3),0 1px 1.5px 0 rgba(0,0,0,.2),0 1px 2px 0 rgba(0,0,0,.4),0 0 0 0 transparent;--shadow-s:0px 1px 2px rgba(0, 0, 0, 0.121),0px 3.4px 6.7px rgba(0, 0, 0, 0.179),0px 15px 30px rgba(0, 0, 0, 0.3);--shadow-l:0px 1.8px 7.3px rgba(0, 0, 0, 0.071),0px 6.3px 24.7px rgba(0, 0, 0, 0.112),0px 30px 90px rgba(0, 0, 0, 0.2);--pdf-shadow:0 0 0 1px var(--background-modifier-border);--pdf-thumbnail-shadow:0 0 0 1px var(--background-modifier-border)}iframe{color-scheme:normal}@font-face{font-family:"Avenir Next";font-weight:400;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/94f2f163d4b698242fef.otf")}@font-face{font-family:Inter;font-style:normal;font-weight:200;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/72505e6a122c6acd5471.woff2") format("woff2")}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/2d5198822ab091ce4305.woff2") format("woff2")}@font-face{font-family:Inter;font-weight:400;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/c8ba52b05a9ef10f4758.woff2")}@font-face{font-family:Inter;font-weight:400;font-style:italic;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/cb10ffd7684cd9836a05.woff2")}@font-face{font-family:Inter;font-weight:600;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/b5f0f109bc88052d4000.woff2")}@font-face{font-family:Inter;font-weight:800;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/cbe0ae49c52c920fd563.woff2")}@font-face{font-family:Inter;font-weight:800;font-style:italic;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/535a6cf662596b3bd6a6.woff2")}@font-face{font-family:"Source Code Pro";font-weight:400;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/70cc7ff27245e82ad414.ttf")}@font-face{font-family:"Source Code Pro";font-weight:400;font-style:italic;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/454577c22304619db035.ttf")}@font-face{font-family:"Source Code Pro";font-weight:700;font-style:normal;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/52ac8f3034507f1d9e53.ttf")}@font-face{font-family:"Source Code Pro";font-weight:700;font-style:italic;font-display:swap;src:url("https://publish.obsidian.md/public/fonts/05b618077343fbbd92b7.ttf")}@font-face{font-family:"Flow Circular";font-display:swap;src:url("https://publish.obsidian.md/public/fonts/4bb6ac751d1c5478ff3a.woff2")}@font-face{font-family:"??";unicode-range:U+0}body{--font-default:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Inter","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Microsoft YaHei Light",sans-serif;--font-monospace-default:Menlo,SFMono-Regular,Consolas,"Roboto Mono",'Source Code Pro',monospace;--font-interface-override:'??';--font-interface-theme:'??';--font-interface:var(--font-interface-override),var(--font-interface-theme),var(--default-font, '??'),var(--font-default);--font-text-override:'??';--font-text-theme:'??';--font-text:var(--font-text-override),var(--font-text-theme),var(--font-interface);--font-print-override:'??';--font-print:var(--font-print-override),var(--font-text-override),var(--font-text-theme),'Arial';--font-monospace-override:'??';--font-monospace-theme:'??';--font-monospace:var(--font-monospace-override),var(--font-monospace-theme),var(--font-monospace-default);--font-text-size:16px;--font-mermaid:var(--font-text)}*{box-sizing:border-box}body,html{margin:0;padding:0;height:100%;width:100%;overflow:hidden}body{text-rendering:optimizelegibility;font-family:var(--font-interface);line-height:var(--line-height-tight);font-size:var(--font-ui-medium);background-color:var(--background-primary);color:var(--text-normal);-webkit-tap-highlight-color:rgba(255,255,255,0)}body.is-translucent{background-color:transparent}.node-insert-event{animation-duration:10ms;animation-name:node-inserted}.is-flashing{transition:all .25s ease 0s;color:var(--text-normal);mix-blend-mode:var(--highlight-mix-blend-mode);border-radius:var(--radius-s);background-color:var(--text-highlight-bg)!important}body{user-select:none;overflow:hidden}body.is-grabbing iframe:not(.is-controlled),body.is-grabbing webview{pointer-events:none}.app-container{display:flex;height:100%;width:100%;position:relative;flex-direction:column}.app-container.no-transition *{transition:none 0s ease 0s!important}.horizontal-main-container{width:100%;display:flex;overflow:hidden;flex:1 0 0px}:focus{outline:0}.is-text-garbled *{font-family:"Flow Circular",sans-serif!important;line-height:1.45em!important}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none;outline:0}.CodeMirror-vscrollbar{right:0;top:0;overflow:hidden scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow:scroll hidden}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{z-index:unset;outline:unset}.CodeMirror-vscrollbar{outline:0}.CodeMirror-hscrollbar{outline:0;z-index:3}.empty-state{position:absolute;height:100%;width:100%;top:0;left:0;display:flex;align-items:center;justify-content:center;flex-direction:column}.empty-state-container{max-width:480px;max-height:280px;margin:20px;text-align:center}.empty-state-title{margin:20px 0;font-weight:var(--h2-weight);font-size:var(--h2-size);line-height:var(--line-height-tight);position:relative}.empty-state-action-list{font-size:var(--font-text-size);line-height:var(--line-height-tight);color:var(--text-muted);margin-top:20px}.empty-state-action{cursor:var(--cursor);line-height:36px;color:var(--text-accent)}.empty-state-close-button{display:none}body{--zoom-factor:1;--titlebar-height:30px}body.is-frameless:not(.is-hidden-frameless){padding-top:calc(var(--titlebar-height)/ var(--zoom-factor))}.is-hidden-frameless{--divider-vertical-height:100%}body.is-frameless>.app-container~*{app-region:no-drag}body.is-frameless .modal{app-region:no-drag}.pane-empty{color:var(--text-faint);font-size:var(--font-ui-small);margin:var(--size-4-2) auto;text-align:center}.view-header-title::-webkit-scrollbar{display:none}.view-content{width:100%;height:calc(100% - var(--header-height))}.inline-title{font-weight:var(--inline-title-weight);font-size:var(--inline-title-size);line-height:var(--inline-title-line-height);font-style:var(--inline-title-style);font-variant:var(--inline-title-variant);font-family:var(--inline-title-font);margin-bottom:var(--inline-title-margin-bottom);letter-spacing:-.015em;color:var(--inline-title-color)}body:not(.show-inline-title) .inline-title:not([data-level]){display:none}::selection{background-color:var(--text-selection)}.markdown-reading-view{display:flex;flex-direction:column}.markdown-preview-view{font-size:var(--font-text-size);font-family:var(--font-text);line-height:var(--line-height-normal);width:100%;height:100%;padding:var(--file-margins);position:relative;overflow-y:auto;overflow-wrap:break-word;color:var(--text-normal);user-select:text;scrollbar-gutter:stable}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer{max-width:var(--file-line-width);margin-left:auto;margin-right:auto}.markdown-rendered.rtl{direction:rtl}.release-notes-view{padding:var(--file-margins)}.release-notes-view .markdown-preview-view{overflow:visible}.release-notes-view .is-readable-line-width{max-width:var(--file-line-width);margin-left:auto;margin-right:auto}.modal.mod-trust-folder{max-width:700px}.modal.mod-plugin-options .modal-content{margin:var(--size-4-6) 0}.hotkey-list-container{overflow:auto}.search-input-container.mod-hotkey .clickable-icon{padding:var(--size-2-1)}.modal.mod-image-lightbox{max-width:90vw;max-height:90vh;padding:0}.modal.mod-image-lightbox .modal-content{padding:var(--size-4-12) var(--size-4-3) var(--size-4-2) var(--size-4-3);text-align:center}.login-field{max-width:500px;margin:1em auto}.search-input-container input:placeholder-shown~.search-input-clear-button{display:none}.community-modal-details-empty-state{padding:0;text-align:center}.community-modal-search-summary{font-size:var(--font-ui-small);padding:var(--size-4-1) var(--size-4-3) var(--size-4-3) var(--size-4-4)}.community-modal-search-results-wrapper{flex:1 0 auto;overflow:auto;border-top:var(--border-width) solid var(--divider-color);scroll-padding:var(--size-4-3);contain:strict}.community-modal-search-results{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--size-4-3);padding:var(--size-4-3)}.community-item{position:relative;background-color:var(--background-primary);padding:var(--size-4-3);cursor:var(--cursor);border-radius:var(--radius-m);border:1px solid var(--background-modifier-border);display:flex;flex-direction:column;gap:var(--size-2-1)}.community-item:last-child{margin-bottom:0}.community-item.is-selected,.community-item.is-selected:hover{border-color:var(--interactive-accent);background-color:var(--interactive-accent);color:var(--text-on-accent)}.community-item.is-selected .flair,.community-item.is-selected:hover .flair{color:var(--text-on-accent);background-color:transparent}.community-item .flair{margin-left:var(--size-4-1);background-color:var(--tag-background);color:var(--tag-color);vertical-align:middle;top:-1px}.community-item-name{font-size:var(--font-ui-medium);line-height:var(--line-height-tight);font-weight:var(--font-medium)}.community-item-author{font-size:var(--font-ui-smaller);line-height:var(--line-height-tight);color:var(--text-muted)}.community-item-updated{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-bottom:var(--size-4-2)}.community-item-desc{font-size:var(--font-ui-small);line-height:var(--line-height-tight);margin-top:4px}.community-item-badge.mod-update{--icon-size:var(--icon-xs);--icon-stroke:var(--icon-xs-stroke-width);color:var(--interactive-accent);position:absolute;top:var(--size-4-3);right:var(--size-4-3)}.community-item-screenshot{max-width:100%;object-fit:cover;border-radius:var(--radius-s);aspect-ratio:16/9;image-rendering:-webkit-optimize-contrast;margin-top:var(--size-4-1)}.community-item-screenshot.mod-unavailable{text-align:center;color:var(--text-muted)}.community-item-screenshot .placeholder-icon{display:flex;align-items:center;justify-content:center;height:100%}.community-item-screenshot .placeholder-icon .svg-icon{color:var(--text-faint);width:var(--size-4-8);height:var(--size-4-8)}.community-modal-info-name{font-size:var(--h2-size);font-weight:var(--font-semibold);line-height:var(--line-height-tight);margin-bottom:var(--size-4-6)}.community-modal-info-author,.community-modal-info-repo,.community-modal-info-version{font-size:var(--font-ui-small);line-height:var(--line-height-tight);color:var(--text-muted)}.community-modal-info-desc{font-size:var(--font-ui-small);line-height:var(--line-height-tight);margin-top:4px}.community-modal-details{flex:1 1 calc(var(--modal-max-width) - var(--modal-community-sidebar-width));overflow:auto;display:flex;flex-direction:column;border-left:1px solid var(--divider-color)}.community-modal-info{flex:1 1 0px;overflow-y:auto;padding:var(--size-4-8) var(--size-4-16);scroll-padding:var(--size-4-4)}.community-readme{overflow-y:visible;height:auto;padding:var(--size-4-4) 0}.community-readme img,.community-readme video{max-width:100%}.community-modal-info-desc{font-size:var(--font-ui-medium);line-height:var(--line-height-tight);margin-top:var(--size-4-2)}.community-modal-button-container{display:flex;flex-wrap:wrap;gap:var(--size-4-2);margin:1.5em 0}.community-modal-readme{font-size:var(--font-text-size);font-family:var(--font-text);line-height:var(--line-height-normal);overflow-wrap:break-word;color:var(--text-normal);user-select:text}.installed-plugins-container{padding-top:var(--size-4-4);border-top:1px solid var(--background-modifier-border)}.community-modal-grid-button-container{position:absolute;top:var(--size-4-4);right:var(--size-4-12);display:flex;gap:var(--size-4-2)}body.is-frameless.is-hidden-frameless{padding-top:0!important}.is-translucent:not(.is-fullscreen){--nav-collapse-icon-color:rgba(var(--mono-rgb-100), 0.3);--nav-collapse-icon-color-collapsed:rgba(var(--mono-rgb-100), 0.3);--divider-color:rgba(0, 0, 0, 0.15)}.workspace-tab-header-container-inner::-webkit-scrollbar,.workspace-tab-header-container-inner::-webkit-scrollbar-thumb{display:none}.button-container{margin-top:20px}button{app-region:no-drag;display:inline-flex;align-items:center;justify-content:center;color:var(--text-normal);font-size:var(--font-ui-small);border-radius:var(--button-radius);border:0;padding:var(--size-4-1) var(--size-4-3);height:var(--input-height);font-weight:var(--input-font-weight);cursor:var(--cursor);font-family:inherit;outline:0;user-select:none;white-space:nowrap}button:not(.clickable-icon){background-color:var(--interactive-normal);box-shadow:var(--input-shadow)}button:focus-visible{box-shadow:0 0 0 3px var(--background-modifier-border-focus)}button[disabled=true]{cursor:not-allowed}button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent)}button.mod-cta:focus-visible{box-shadow:0 0 0 3px var(--background-modifier-border-focus)}button.mod-muted{background-color:var(--background-secondary);color:var(--text-muted)}button.mod-warning{background-color:var(--background-modifier-error);color:var(--text-on-accent)}button.mod-destructive{color:var(--text-error)}.card-container{display:flex}.card-container.mod-horizontal{flex-direction:column}.card{background-color:var(--background-secondary-alt);border-radius:4px;border:1px solid var(--background-modifier-border);margin:0 10px;padding:15px 30px;display:flex;flex-direction:column;flex-grow:1}.card ul{padding:0}.card .button-container{margin:10px 0}.card-container.mod-horizontal .card{margin:10px 0}.card-container.mod-horizontal .card ul{padding-left:24px}.card li{margin:5px 0}.card.u-clickable{cursor:var(--cursor)}.card.is-selected{border:1px solid var(--interactive-accent);background-color:hsla(var(--interactive-accent-hsl),.2)}.card-title{text-align:center;font-size:20px;line-height:30px;color:var(--text-muted);margin-bottom:8px}.card-description{color:var(--text-muted);font-size:var(--font-ui-small);line-height:20px;flex-grow:1}.changelog-item{margin:var(--size-4-2) 0;font-size:var(--font-ui-medium);line-height:var(--line-height)}.changelog-item::before{content:attr(data-label);width:50px;border-radius:var(--radius-m);font-size:var(--font-ui-small);display:inline-block;text-align:center;margin-right:14px;text-transform:uppercase;letter-spacing:1px;line-height:22px}.changelog-item.mod-success::before{background-color:var(--background-modifier-success)}.changelog-item.mod-highlighted::before{background-color:var(--interactive-accent)}.list-item{display:flex;padding:0;margin:8px 0;gap:var(--size-4-2);align-items:center}.list-item-part.mod-extended{flex-grow:1;overflow-wrap:anywhere}.list-item-part.clickable-icon{display:flex;align-items:center;justify-content:center;padding:var(--size-2-2);cursor:var(--cursor);border-radius:var(--radius-s);color:var(--icon-color)}.list-item-part.clickable-icon:active,.list-item-part.clickable-icon:hover{color:var(--icon-color-hover);background-color:var(--background-modifier-hover)}.u-center-text{text-align:center}.u-faded-text{color:var(--text-muted)}.u-pop{color:var(--text-accent);font-weight:var(--font-semibold)}.u-muted{color:var(--text-muted)}.u-small{font-size:.8em}.u-clickable{cursor:var(--cursor)}.diff-view{height:100%;overflow:auto;user-select:text}.diff-line{padding:0 var(--size-4-2)}.diff-line.mod-left{background-color:rgba(var(--background-modifier-error-rgb),.2)}.diff-line.mod-left .diff-changed{background-color:rgba(var(--background-modifier-error-rgb),.4)}.diff-line.mod-right{background-color:rgba(var(--background-modifier-success-rgb),.2)}.diff-line.mod-right .diff-changed{background-color:rgba(var(--background-modifier-success-rgb),.4)}.diff-collapsed{text-align:center;color:var(--text-muted);cursor:pointer;font-size:var(--font-ui-small);margin:var(--size-4-2) 0}.diff-collapsed:hover{color:var(--text-accent)}.mod-active .document-search-container{background-color:var(--background-primary)}.document-search-container{display:flex;flex-direction:column;padding:var(--size-4-2) 0;margin:0 var(--size-4-4);gap:var(--size-4-2);z-index:var(--layer-popover)}.document-replace,.document-search{width:100%;max-width:var(--file-line-width);margin:0 auto;display:flex;padding:0 var(--size-4-2);gap:var(--size-4-2)}.document-replace{display:none}.document-search-container.mod-replace-mode .document-replace{display:flex}input.document-replace-input,input.document-search-input{flex-grow:1}input.document-replace-input.mod-no-match,input.document-search-input.mod-no-match{background-color:rgba(var(--background-modifier-error-rgb),.2)}.document-replace-buttons,.document-search-buttons{display:flex;gap:var(--size-4-2);align-items:center}.document-search-button{font-size:var(--font-ui-small);padding:0 var(--size-4-2);color:var(--text-muted)}.document-search-close-button{cursor:var(--cursor);position:relative;top:2px;font-size:24px;line-height:20px;height:24px;width:24px;padding:0 var(--size-2-2);border-radius:var(--radius-s);color:var(--text-muted)}.document-search-close-button::before{font-family:Inter,sans-serif;content:"×";font-weight:300}.markdown-rendered .search-highlight>div{position:absolute;pointer-events:none;box-shadow:0 0 0 2px var(--text-normal);opacity:.3;mix-blend-mode:var(--highlight-mix-blend-mode);border-radius:2px}.markdown-rendered .search-highlight>div.is-active{box-shadow:0 0 0 3px var(--text-accent);opacity:1}.flair{background-color:var(--interactive-normal);border-radius:var(--radius-s);color:var(--text-normal);font-size:10px;letter-spacing:.05em;margin-left:var(--size-4-2);padding:var(--size-2-1) var(--size-2-2);position:relative;text-transform:uppercase;white-space:nowrap;vertical-align:middle}.flair.mod-flat{vertical-align:top}.flair.mod-pop{background-color:var(--interactive-accent);color:var(--text-on-accent)}.markdown-preview-view:not(.allow-fold-headings) .heading-collapse-indicator,.markdown-preview-view:not(.allow-fold-lists) .list-collapse-indicator{display:none}.collapse-icon{display:flex;align-items:center}.collapse-icon::before{content:"​"}.collapse-icon svg.svg-icon{color:var(--nav-collapse-icon-color);stroke-width:4px;width:10px;height:10px;transition:transform .1s ease-in-out 0s}.collapse-icon.is-collapsed svg.svg-icon{transform:rotate(-90deg)}.rtl .collapse-icon.is-collapsed svg.svg-icon{transform:rotate(90deg)}.view-content .collapse-indicator svg.svg-icon,.view-content .list-collapse-indicator svg.svg-icon{color:var(--collapse-icon-color)}.view-content .is-collapsed .collapse-indicator svg.svg-icon,.view-content .is-collapsed .list-collapse-indicator svg.svg-icon{color:var(--collapse-icon-color-collapsed)}.markdown-preview-view .collapse-indicator{float:left;cursor:var(--cursor)}.markdown-preview-view .collapse-indicator .svg-icon{vertical-align:middle}.markdown-preview-view li.is-collapsed>ol,.markdown-preview-view li.is-collapsed>ul{display:none}.markdown-preview-view .heading-collapse-indicator{margin-left:-22px;padding:0 6px}svg.svg-icon{height:var(--icon-size);width:var(--icon-size);stroke-width:var(--icon-stroke)}.view-actions{gap:0;align-items:center;--icon-size:var(--icon-s)}.clickable-icon{app-region:no-drag;background-color:transparent;display:flex;align-items:center;justify-content:center;padding:var(--size-2-2) var(--size-2-3);cursor:var(--cursor);border-radius:var(--clickable-icon-radius);color:var(--icon-color);opacity:var(--icon-opacity);transition:opacity .15s ease-in-out 0s;height:auto}.clickable-icon.is-active{opacity:var(--icon-opacity-hover);color:var(--icon-color-active);background-color:var(--background-modifier-active-hover)}.clickable-icon.mod-warning{color:var(--text-error)}.clickable-icon.mod-filled svg{fill:var(--icon-color)}.text-icon-button{app-region:no-drag;display:inline-flex;align-items:center;color:var(--text-muted);font-size:var(--font-ui-small);border-radius:var(--button-radius);padding:var(--size-2-1) var(--size-4-3) var(--size-2-1) var(--size-4-2);height:var(--input-height);font-weight:var(--input-font-weight);cursor:var(--cursor);font-family:inherit;gap:var(--size-2-2);user-select:none;white-space:nowrap}.text-icon-button .text-button-icon{display:flex;align-items:center;justify-content:center}.text-icon-button .text-button-icon svg.svg-icon{height:var(--icon-size);width:var(--icon-size);stroke-width:var(--icon-stroke)}.text-icon-button:focus{box-shadow:0 0 0 2px var(--background-modifier-border-focus);outline:0}.markdown-rendered.show-indentation-guide li>ol,.markdown-rendered.show-indentation-guide li>ul{position:relative}.markdown-rendered.show-indentation-guide li>ol::before,.markdown-rendered.show-indentation-guide li>ul::before{content:"​";position:absolute;display:block;left:-1em;top:0;bottom:0;border-right:var(--indentation-guide-width) solid var(--indentation-guide-color)}.input-label{display:inline-block;width:150px;text-align:right;margin-right:var(--size-4-2)}.input-button{padding:6px 14px;margin-left:14px;color:var(--text-muted);font-size:var(--font-ui-medium);position:relative;top:-1px}input.metadata-input-text,input[type=date],input[type=datetime-local],input[type=email],input[type=number],input[type=password],input[type=search],input[type=text],textarea{app-region:no-drag;background:var(--background-modifier-form-field);border:var(--input-border-width) solid var(--background-modifier-border);color:var(--text-normal);font-family:inherit;padding:var(--size-4-1) var(--size-4-2);font-size:var(--font-ui-small);border-radius:var(--input-radius);outline:0}input.metadata-input-text:active,input.metadata-input-text:focus,input[type=date]:active,input[type=date]:focus,input[type=datetime-local]:active,input[type=datetime-local]:focus,input[type=email]:active,input[type=email]:focus,input[type=number]:active,input[type=number]:focus,input[type=password]:active,input[type=password]:focus,input[type=search]:active,input[type=search]:focus,input[type=text]:active,input[type=text]:focus,textarea:active,textarea:focus{border-color:var(--background-modifier-border-focus);transition:box-shadow .15s ease-in-out 0s,border .15s ease-in-out 0s}input.metadata-input-text:active,input.metadata-input-text:focus,input.metadata-input-text:focus-visible,input[type=date]:active,input[type=date]:focus,input[type=date]:focus-visible,input[type=datetime-local]:active,input[type=datetime-local]:focus,input[type=datetime-local]:focus-visible,input[type=email]:active,input[type=email]:focus,input[type=email]:focus-visible,input[type=number]:active,input[type=number]:focus,input[type=number]:focus-visible,input[type=password]:active,input[type=password]:focus,input[type=password]:focus-visible,input[type=search]:active,input[type=search]:focus,input[type=search]:focus-visible,input[type=text]:active,input[type=text]:focus,input[type=text]:focus-visible,textarea:active,textarea:focus,textarea:focus-visible{box-shadow:0 0 0 2px var(--background-modifier-border-focus)}input.metadata-input-text::placeholder,input[type=date]::placeholder,input[type=datetime-local]::placeholder,input[type=email]::placeholder,input[type=number]::placeholder,input[type=password]::placeholder,input[type=search]::placeholder,input[type=text]::placeholder,textarea::placeholder{color:var(--text-faint)}input[type=email],input[type=number],input[type=password],input[type=search],input[type=text]{height:var(--input-height)}textarea{line-height:var(--line-height-tight)}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{display:none;pointer-events:none}input[type=number]::-webkit-inner-spin-button{appearance:none}input[type=date],input[type=datetime-local]{font-variant-numeric:tabular-nums;position:relative}input[type=date]::-webkit-calendar-picker-indicator,input[type=datetime-local]::-webkit-calendar-picker-indicator{position:absolute;left:var(--size-4-1);opacity:.5}body:not(.is-ios):not(.is-android) input[type=date],body:not(.is-ios):not(.is-android) input[type=datetime-local]{padding-left:var(--size-4-6)}input[type=range]{width:100px;appearance:none;background-color:var(--slider-track-background);border-radius:var(--slider-track-height);height:var(--slider-track-height);padding:0;outline:0}input[type=range]::-webkit-slider-runnable-track{height:6px;appearance:none}input[type=range]::-webkit-slider-thumb{appearance:none;height:var(--slider-thumb-height);width:var(--slider-thumb-width);border-radius:var(--slider-thumb-radius);cursor:default;background:#fff;border:var(--slider-thumb-border-width) solid var(--slider-thumb-border-color);position:relative;top:var(--slider-thumb-y);transition:all .1s linear 0s;box-shadow:rgba(0,0,0,.05) 0 1px 1px 0,rgba(0,0,0,.1) 0 2px 2px 0}input[type=range]::-webkit-slider-thumb:active,input[type=range]::-webkit-slider-thumb:hover{background:#fff;border-color:var(--background-modifier-border-focus);box-shadow:rgba(0,0,0,.1) 0 1px 2px 0,rgba(0,0,0,.2) 0 2px 3px 0;transition:all .1s linear 0s}input[type=color]{appearance:none;width:calc(var(--swatch-width) + 4px);background-color:transparent;border:none;cursor:var(--cursor);padding:0}input[type=color]::-webkit-color-swatch-wrapper{padding:2px}input[type=color]::-webkit-color-swatch{border:0;box-shadow:var(--swatch-shadow);border-radius:var(--swatch-radius);height:var(--swatch-height);width:var(--swatch-width);align-self:center}input[type=color]:focus-visible::-webkit-color-swatch,input[type=color]:focus::-webkit-color-swatch{box-shadow:var(--swatch-shadow),0 0 0 3px var(--background-modifier-border-focus)}select.mod-hidden{display:none}.notice-container{z-index:var(--layer-notice);position:fixed;top:22px;right:0;padding:10px;overflow:hidden}.notice{background-color:var(--background-modifier-message);border-radius:var(--radius-m);box-shadow:0 2px 8px var(--background-modifier-box-shadow);color:#fafafa;font-size:var(--font-ui-small);line-height:var(--line-height-tight);padding:.75em 1em;max-width:300px;margin-bottom:14px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;cursor:var(--cursor)}.debug-textarea{width:100%;height:50vh;max-height:80vh;font-family:var(--font-monospace);tab-size:4;resize:none}.modal-container{display:flex;align-items:center;justify-content:center;position:absolute;top:0;left:0;width:100%;height:100%;z-index:var(--layer-modal)}.modal-container.mod-dim .modal{box-shadow:var(--shadow-l)}.modal-bg{position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--background-modifier-cover)}.modal{--checkbox-size:var(--font-ui-medium);background-color:var(--modal-background);border-radius:var(--modal-radius);border:var(--modal-border-width) solid var(--modal-border-color);padding:var(--size-4-4);position:relative;min-height:100px;width:var(--dialog-width);max-width:var(--dialog-max-width);max-height:var(--dialog-max-height);display:flex;flex-direction:column;overflow:auto}.modal.mod-scrollable-content{padding:0;overflow:hidden}.modal.mod-scrollable-content .modal-title{padding:var(--size-4-4) var(--size-4-4) 0 var(--size-4-4)}.modal.mod-scrollable-content .modal-content{padding:0 var(--size-4-4) var(--size-4-4) var(--size-4-4);overflow:auto}body:not(.native-scrollbars) .modal-close-button{right:12px}.modal-close-button{cursor:var(--cursor);position:absolute;top:var(--size-2-3);right:var(--size-2-3);font-size:24px;line-height:20px;height:24px;width:24px;padding:0 var(--size-2-2);border-radius:var(--radius-s);color:var(--text-muted)}.modal-close-button::before{font-family:Inter,sans-serif;content:"×";font-weight:300}.modal-title{font-size:var(--font-ui-large);margin-bottom:.75em;font-weight:var(--font-semibold);text-align:left;line-height:var(--line-height-tight)}.modal-title:empty{display:none}.modal-content{flex:1 1 auto;font-size:var(--font-ui-medium)}.modal-button-container{margin-top:1.5em;display:flex;justify-content:flex-end;gap:var(--size-4-2);flex-wrap:wrap;font-size:var(--font-ui-medium)}.modal-button-container .mod-checkbox{flex-grow:1;display:flex;align-items:center;gap:var(--size-4-1)}.modal-button-container .mod-secondary{margin-right:auto}.modal.mod-scrollable-content>.modal-button-container{margin-top:0;border-top:1px solid var(--background-modifier-border);padding:var(--size-4-4)}.modal-checkbox-label{cursor:var(--cursor);margin-left:10px;user-select:none}.mod-warning{color:var(--text-error)}.mod-success{color:var(--text-success)}.modal .modal-nav-action{background-color:unset;margin-top:var(--size-4-1);position:absolute;top:0;width:unset}.modal .modal-nav-action.mod-secondary{left:0}.modal .modal-nav-action.mod-cta{color:var(--color-accent);font-weight:var(--font-semibold);right:0}.nav-buttons-container{flex-wrap:wrap;gap:var(--size-2-1)}.nav-buttons-container.has-separator{border-bottom:1px solid var(--background-modifier-border);padding-bottom:var(--size-2-3);margin-bottom:var(--size-4-2)}body{--pill-focus-width:calc(100% + 6px);--pill-focus-left-adjust:-4px}.multi-select-container{cursor:text;display:inline-flex;vertical-align:top;flex-wrap:wrap;min-height:var(--input-height);width:100%;background:var(--background-modifier-form-field);border:var(--input-border-width) solid var(--background-modifier-border);color:var(--text-normal);font-size:var(--font-ui-small);border-radius:var(--input-radius);outline:0;padding:var(--size-4-1);gap:var(--size-4-1)}.multi-select-pill{--icon-size:var(--icon-xs);--icon-stroke:var(--icon-xs-stroke-width);display:flex;align-items:center;background-color:var(--pill-background);border:var(--pill-border-width) solid var(--pill-border-color);border-radius:var(--pill-radius);color:var(--pill-color);cursor:var(--cursor);font-weight:var(--pill-weight);padding:var(--pill-padding-y) 0;line-height:1;max-width:100%;gap:var(--size-2-1);position:relative}.multi-select-pill:focus::after{content:"";display:block;position:absolute;pointer-events:none;border-radius:var(--pill-radius);left:var(--pill-focus-left-adjust);width:var(--pill-focus-width);height:100%;box-shadow:0 0 0 1px var(--background-modifier-border-focus),inset 0 0 0 1px var(--background-modifier-border-focus)}.multi-select-pill-content{margin-left:var(--pill-padding-x)}.multi-select-pill-remove-button{margin-right:min(var(--size-2-3),var(--pill-padding-x));cursor:var(--cursor);color:var(--pill-color-remove);border-radius:var(--radius-s);display:flex;align-items:center;--icon-size:var(--icon-xs);--icon-stroke:var(--icon-xs-stroke-width)}.multi-select-pill-remove-button:hover{color:var(--pill-color-remove-hover)}.multi-select-input{cursor:text;font-family:var(--font-interface);min-width:1ch;max-width:max-content;color:var(--text-normal);background-color:inherit;border:none;overflow-x:auto;white-space:nowrap}.multi-select-input::-webkit-scrollbar{display:none}.multi-select-input::before{content:"​"}.multi-select-input:empty::before{content:attr(placeholder);color:var(--text-faint);pointer-events:none}.multi-select-duplicate{animation:2s ease-in 0s 1 normal none running multi-select-highlight}body:not(.native-scrollbars) ::-webkit-scrollbar{width:12px;height:12px;border-radius:var(--radius-l);background-color:transparent}body:not(.native-scrollbars) ::-webkit-scrollbar-track{background-color:transparent}body:not(.native-scrollbars) ::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-bg);border-radius:var(--radius-l);background-clip:padding-box;border-style:solid;border-color:transparent;border-image:initial;border-width:3px 3px 3px 2px;min-height:45px}body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:active{border-radius:var(--radius-l)}body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:active,body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-active-thumb-bg)}body:not(.native-scrollbars) ::-webkit-scrollbar-corner{background:0 0}.checkbox-container{app-region:no-drag;cursor:var(--cursor);background-color:var(--background-modifier-border-hover);border-radius:var(--toggle-radius);display:inline-block;flex-shrink:0;height:calc(var(--toggle-thumb-height) + var(--toggle-border-width) * 2);position:relative;user-select:none;width:var(--toggle-width);box-shadow:rgba(0,0,0,.07) 0 4px 10px inset,rgba(0,0,0,.21) 0 0 1px inset;transition:box-shadow .15s ease-in-out 0s,outline .15s ease-in-out 0s,border .15s ease-in-out 0s,opacity .15s ease-in-out 0s;outline:0 solid var(--background-modifier-border-focus)}.checkbox-container input[type=checkbox]{position:absolute;opacity:0;left:0}.checkbox-container:focus-within{outline:var(--toggle-border-width) solid var(--background-modifier-border-focus)}.checkbox-container.is-enabled{background-color:var(--interactive-accent)}.checkbox-container.is-enabled::after{transform:translate3d(calc(var(--toggle-width) - var(--toggle-thumb-width) - var(--toggle-border-width)),0,0)}.checkbox-container.is-enabled:active::after{left:-4px}.checkbox-container::before{content:"";display:block;position:absolute;inset:0px;opacity:0}.checkbox-container::after{pointer-events:none;content:"";display:block;position:absolute;background-color:var(--toggle-thumb-color);width:var(--toggle-thumb-width);height:var(--toggle-thumb-height);margin:var(--toggle-border-width) 0 0 0;border-radius:var(--toggle-thumb-radius);transition:transform .15s ease-in-out 0s,width .1s ease-in-out 0s,left .1s ease-in-out 0s;left:0;transform:translate3d(var(--toggle-border-width),0,0);box-shadow:rgba(0,0,0,.15) 0 1px 2px}.checkbox-container:active::after{width:calc(var(--toggle-thumb-width) + var(--toggle-border-width))}.checkbox-container.mod-small{width:var(--toggle-s-width);height:calc(var(--toggle-s-thumb-height) + var(--toggle-s-border-width) * 2)}.checkbox-container.mod-small:focus-within{outline:var(--toggle-s-border-width) solid var(--background-modifier-border-focus)}.checkbox-container.mod-small::after{width:var(--toggle-s-thumb-width);height:var(--toggle-s-thumb-height);margin:var(--toggle-s-border-width) 0 0 0;transform:translate3d(var(--toggle-s-border-width),0,0)}.checkbox-container.mod-small.is-enabled::after{transform:translate3d(calc(var(--toggle-s-width) - var(--toggle-s-thumb-width) - var(--toggle-s-border-width)),0,0)}.checkbox-container.mod-small:active::after{width:calc(var(--toggle-s-thumb-width) + var(--toggle-s-border-width))}.tree-item-self{align-items:baseline;display:flex;border-radius:var(--radius-s);color:var(--nav-item-color);font-size:var(--nav-item-size);line-height:var(--line-height-tight);font-weight:var(--nav-item-weight);margin-bottom:var(--size-2-1);padding:var(--nav-item-padding);position:relative}.tree-item-self.mod-collapsible{padding:var(--nav-item-parent-padding)}.tree-item-self.mod-collapsible.is-being-dragged-over{border-radius:var(--radius-s);color:var(--nav-item-color-highlighted);background:hsla(var(--interactive-accent-hsl),.1)}.tree-item-self.mod-collapsible.is-being-dragged-over .collapse-icon{color:var(--nav-item-color-highlighted)}.tree-item-self.is-clickable{cursor:var(--cursor)}.tree-item-self.is-active,body:not(.is-grabbing) .tree-item-self.is-active:hover{color:var(--nav-item-color-active);background-color:var(--nav-item-background-active);font-weight:var(--nav-item-weight-active)}.tree-item-self.is-selected,body:not(.is-grabbing) .tree-item-self.is-selected:hover{color:var(--nav-item-color-selected);background-color:var(--nav-item-background-selected)}.tree-item-self.is-being-dragged,body:not(.is-grabbing) .tree-item-self.is-being-dragged:hover{color:var(--text-on-accent);background-color:var(--interactive-accent)}.tree-item-self.is-being-dragged .tree-item-icon,body:not(.is-grabbing) .tree-item-self.is-being-dragged:hover .tree-item-icon{color:var(--text-on-accent)}.tree-item-self .tree-item-icon{position:absolute;margin-left:calc(-1 * var(--size-4-5));width:var(--size-4-4);display:flex;align-items:center;justify-content:center;opacity:var(--icon-opacity);color:var(--icon-color);flex:0 0 auto}.tree-item-self .tree-item-icon::before{content:"​"}.tree-item-self .tree-item-icon .svg-icon:not(.right-triangle){--icon-size:var(--icon-xs);--icon-stroke:var(--icon-s-stroke-width)}.tree-item-flair-outer{padding-left:var(--size-4-1);margin-left:auto;display:flex;flex-shrink:0;align-items:center}.tree-item-flair{font-size:var(--font-ui-smaller);color:var(--text-faint);line-height:1;border-radius:var(--radius-s)}.tree-item-inner{overflow:hidden}.tree-item-inner-text{overflow:hidden;text-overflow:ellipsis}.tree-item-inner-subtext{color:var(--text-faint);font-size:85%}.tree-item-children{padding-left:var(--nav-item-children-padding-left);margin-left:var(--nav-item-children-margin-left);margin-bottom:1px;border-left:var(--nav-indentation-guide-width) solid var(--nav-indentation-guide-color)}audio{outline:0}.markdown-rendered audio{max-width:100%;outline:0}audio{width:100%;height:42px}audio::-webkit-media-controls-enclosure{border-radius:calc(var(--radius-m) - 1px);border:1px solid var(--background-modifier-border);background-color:var(--background-primary-alt)}audio::-webkit-media-controls-current-time-display,audio::-webkit-media-controls-time-remaining-display{font-family:var(--font-interface)}iframe{border:0}kbd{color:var(--code-normal);font-family:var(--font-monospace);background-color:var(--code-background);border-radius:var(--radius-s);font-size:var(--code-size);padding:.1em .25em}.popupWrapper{--pdf-popup-width:280px;font-size:var(--font-ui-medium);pointer-events:none;position:absolute;transform:translate(-50%,0);z-index:10000}.popupWrapper>div{margin:var(--size-4-3);background-color:var(--background-primary);border-radius:var(--radius-s);filter:drop-shadow(rgba(0, 0, 0, .2) 0px 0px 1px) drop-shadow(rgba(0, 0, 0, .3) 0px 1px 2px) drop-shadow(rgba(0, 0, 0, .3) 0px 4px 6px)}.popupWrapper>div::after{background:var(--background-primary);border-top-left-radius:2px;content:"";height:var(--size-4-3);left:calc(50% - 2px);position:absolute;top:-5px;transform:rotate(45deg);width:var(--size-4-3);z-index:-1}.popup{cursor:initial;display:flex;flex-direction:column;pointer-events:auto;user-select:text;white-space:normal;width:var(--pdf-popup-width);overflow-wrap:break-word}.popupContent{font-size:var(--font-ui-small);line-height:var(--line-height-tight);max-height:200px;overflow:auto;padding:var(--size-4-4)}.popupContent:empty{display:none}.popupMeta{--icon-size:var(--font-ui-small);--icon-stroke:2.5px;align-items:center;background-color:var(--background-secondary);border-top:1px solid var(--background-modifier-border);color:var(--text-muted);display:flex;font-size:var(--font-ui-smaller);gap:var(--size-4-1);justify-content:space-between;padding:var(--size-4-1) var(--size-4-2);border-bottom-left-radius:var(--radius-s);border-bottom-right-radius:var(--radius-s)}.popupContent:empty+.popupMeta{border-top:none;background-color:var(--background-primary);border-top-left-radius:var(--radius-s);border-top-right-radius:var(--radius-s)}.popupMeta .clickable-icon{margin-right:calc(var(--size-4-1) * -1);margin-left:calc(var(--size-2-1) * -1)}.popupDate{white-space:nowrap}.markdown-rendered video{max-width:100%;outline:0}.markdown-rendered blockquote{color:var(--blockquote-color);font-style:var(--blockquote-font-style);background-color:var(--blockquote-background-color);border-left:var(--blockquote-border-thickness) solid var(--blockquote-border-color);padding:0 0 0 var(--size-4-6);margin-inline:0px}.markdown-rendered blockquote>:first-child{margin-top:0}.markdown-rendered blockquote>:last-child{margin-bottom:0}.callout{--callout-color:var(--callout-default);--callout-icon:lucide-pencil}.callout[data-callout=abstract],.callout[data-callout=summary],.callout[data-callout=tldr]{--callout-color:var(--callout-summary);--callout-icon:lucide-clipboard-list}.callout[data-callout=info]{--callout-color:var(--callout-info);--callout-icon:lucide-info}.callout[data-callout=todo]{--callout-color:var(--callout-todo);--callout-icon:lucide-check-circle-2}.callout[data-callout=important]{--callout-color:var(--callout-important);--callout-icon:lucide-flame}.callout[data-callout=hint],.callout[data-callout=tip]{--callout-color:var(--callout-tip);--callout-icon:lucide-flame}.callout[data-callout=check],.callout[data-callout=done],.callout[data-callout=success]{--callout-color:var(--callout-success);--callout-icon:lucide-check}.callout[data-callout=faq],.callout[data-callout=help],.callout[data-callout=question]{--callout-color:var(--callout-question);--callout-icon:help-circle}.callout[data-callout=attention],.callout[data-callout=caution],.callout[data-callout=warning]{--callout-color:var(--callout-warning);--callout-icon:lucide-alert-triangle}.callout[data-callout=fail],.callout[data-callout=failure],.callout[data-callout=missing]{--callout-color:var(--callout-fail);--callout-icon:lucide-x}.callout[data-callout=danger],.callout[data-callout=error]{--callout-color:var(--callout-error);--callout-icon:lucide-zap}.callout[data-callout=bug]{--callout-color:var(--callout-bug);--callout-icon:lucide-bug}.callout[data-callout=example]{--callout-color:var(--callout-example);--callout-icon:lucide-list}.callout[data-callout=cite],.callout[data-callout=quote]{--callout-color:var(--callout-quote);--callout-icon:quote-glyph}.callout{overflow:hidden;border-style:solid;border-color:rgba(var(--callout-color),var(--callout-border-opacity));border-width:var(--callout-border-width);border-radius:var(--callout-radius);margin:1em 0;mix-blend-mode:var(--callout-blend-mode);background-color:rgba(var(--callout-color),.1);padding:var(--callout-padding)}.callout.is-collapsible .callout-title{cursor:var(--cursor)}.callout-title{padding:var(--callout-title-padding);display:flex;gap:var(--size-4-1);font-size:var(--callout-title-size);color:rgb(var(--callout-color));line-height:var(--line-height-tight);align-items:flex-start}.callout-content{overflow-x:auto;padding:var(--callout-content-padding);background-color:var(--callout-content-background)}.callout-icon{flex:0 0 auto;display:flex;align-items:center}.callout-icon .svg-icon{color:rgb(var(--callout-color))}.callout-icon::after{content:"​"}.callout-title-inner{font-weight:var(--bold-weight);color:var(--callout-title-color)}.callout-fold{display:flex;align-items:center;padding-right:var(--size-4-2)}.callout-fold::after{content:"​"}.callout-fold .svg-icon{transition:transform .1s ease-in-out 0s}.callout-fold.is-collapsed .svg-icon{transform:rotate(-90deg)}.markdown-rendered code{color:var(--code-normal);font-family:var(--font-monospace);background-color:var(--code-background);border-radius:var(--code-radius);font-size:var(--code-size);padding:.1em .25em}.markdown-rendered pre{position:relative;padding:var(--size-4-3) var(--size-4-4);min-height:38px;background-color:var(--code-background);border-radius:var(--code-radius);white-space:var(--code-white-space);overflow-x:auto}.markdown-rendered pre code{border:none;padding:0;background-color:transparent}.markdown-rendered pre:not(:hover)>button.copy-code-button{display:none}.markdown-rendered button.copy-code-button{margin:6px;padding:6px 8px;height:auto;background-color:transparent;box-shadow:none;color:var(--text-muted);font-size:var(--font-ui-smaller);font-family:var(--font-interface);position:absolute;top:0;right:0}code[class*=language-],pre[class*=language-]{color:var(--code-normal);background:0 0;overflow-wrap:break-word;white-space:pre-wrap;word-break:normal;font-family:var(--font-monospace);text-align:left;word-spacing:normal;line-height:var(--line-height-normal);hyphens:none}:not(pre)>code[class*=language-],pre[class*=language-]{background:var(--code-background)}pre[class*=language-]{overflow:hidden}code[class*=language-]{display:block;padding:1em;overflow:auto}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:var(--code-comment)}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.symbol,.token.tag{color:var(--code-tag)}.token.punctuation{color:var(--code-punctuation)}.token.boolean,.token.number{color:var(--code-value)}.token.attr-name,.token.char,.token.inserted,.token.selector,.token.string{color:var(--code-string)}.token.operator{color:var(--code-operator)}.token.atrule,.token.attr-value,.token.builtin,.token.class-name,.token.function,.token.property-access{color:var(--code-function)}.token.keyword{color:var(--code-keyword)}.token.important,.token.regex{color:var(--code-important)}.markdown-preview-view .markdown-embed .markdown-preview-view{--file-folding-offset:0px;height:100%;padding:0}.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h1,.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h2,.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h3,.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h4,.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h5,.markdown-preview-view .markdown-embed .markdown-preview-view .markdown-preview-pusher h6{margin-top:0}.file-embed,.markdown-embed{position:relative}.file-embed-link,.markdown-embed-link{position:absolute;top:4px;right:4px;color:var(--icon-color);opacity:var(--icon-opacity);cursor:var(--cursor-link);padding:var(--size-2-2);border-radius:var(--radius-s);display:flex;align-items:center;--icon-size:var(--icon-s);--icon-stroke:var(--icon-s-stroke-width)}.file-embed-title{display:flex;align-items:center;justify-content:center;gap:var(--size-4-2)}.file-embed-icon{color:var(--text-muted);display:flex}.file-embed{display:flex;justify-content:center;border-radius:var(--radius-m);background-color:var(--background-primary-alt)}.file-embed.mod-empty,.file-embed.mod-generic{cursor:var(--cursor-link);padding:var(--size-4-2);color:var(--text-muted);text-align:center;font-size:var(--font-smaller)}.markdown-embed-content{height:100%}.embed-title{align-items:center;display:flex;gap:var(--size-4-1);font-size:var(--font-text-size);font-weight:var(--bold-weight);text-align:left;text-overflow:ellipsis;white-space:nowrap;padding:0 0 var(--size-4-2) 0}.markdown-embed{font-style:var(--embed-font-style);background-color:var(--embed-background);border-top:var(--embed-border-top);border-right:var(--embed-border-right);border-bottom:var(--embed-border-bottom);border-left:var(--embed-border-left);margin:0;padding:var(--embed-padding)}.markdown-embed .markdown-preview-view{padding:0}.internal-embed:not(.image-embed){display:block}.internal-embed audio,.internal-embed img:not([width]),.internal-embed video{max-width:100%}.inline-embed .markdown-embed-content{height:fit-content;max-height:var(--embed-max-height);overflow:auto}.inline-embed .markdown-embed-content p:first-child{margin-top:0}.embed-iframe{width:100%;height:100%}iframe.external-embed{width:600px;max-width:100%;height:350px}.footnote-link{text-decoration:none}.footnotes{font-size:var(--footnote-size)}.footnote-ref{vertical-align:super}.footnote-backref{color:var(--text-faint);text-decoration:none}.markdown-rendered .frontmatter.mod-failed{position:relative}.markdown-rendered .frontmatter.mod-failed .mod-error{color:var(--text-error);font-size:var(--font-smaller)}.markdown-rendered .frontmatter.mod-failed::after{content:"";position:absolute;top:0;right:0;width:100%;height:100%;background-color:var(--background-modifier-error);opacity:.3;mix-blend-mode:var(--highlight-mix-blend-mode)}.metadata-container{--input-height:var(--metadata-input-height);border-radius:var(--metadata-border-radius);background-color:var(--metadata-background);border-color:var(--metadata-border-color);border-style:solid;border-width:var(--metadata-border-width);padding:var(--metadata-padding);color:var(--text-muted);position:relative;max-width:var(--metadata-max-width);margin-block-end:var(--p-spacing);transform:translateX(calc(var(--size-4-1) * -1))}.metadata-container .metadata-add-button{padding-left:var(--size-2-3);margin-top:.5em;font-size:var(--metadata-label-font-size)}.markdown-embed-content .metadata-container{display:none}.metadata-container.is-collapsed .metadata-property{display:none}.metadata-container:focus-within .metadata-property.is-selected{color:var(--nav-item-color-selected);background-color:var(--nav-item-background-selected)}.metadata-properties{display:flex;flex-direction:column;gap:var(--metadata-gap)}.metadata-properties-heading{display:inline-block;padding:var(--size-4-1);margin-bottom:var(--size-4-2);position:relative;line-height:1.2}.metadata-properties-heading::before{content:"";border-radius:var(--metadata-property-radius);position:absolute;display:inline-block;inset:0px}.metadata-properties-heading:focus::before{box-shadow:0 0 0 2px var(--background-modifier-border-focus)}.metadata-properties-heading .collapse-indicator{position:absolute;left:-22px;padding:0 6px}.metadata-properties-title{user-select:none;font-size:max(var(--font-ui-small), 1em);color:var(--text-normal);font-weight:var(--font-medium)}.metadata-input-text{background-color:transparent;width:100%;min-height:var(--input-height);border-width:0;resize:none;overflow-y:hidden}.metadata-input-text::-webkit-date-and-time-value{text-align:left}.metadata-input-text.mod-date{padding-right:0;width:auto}.metadata-property{position:relative;display:flex;align-items:start;padding:var(--metadata-property-padding);border-radius:var(--metadata-property-radius);overflow:hidden;background-color:var(--metadata-property-background)}.metadata-property:focus-within{background-color:var(--metadata-property-background-hover);--metadata-divider-color:var(--metadata-divider-color-focus);box-shadow:0 0 0 2px var(--background-modifier-border-focus)}.metadata-property-icon{cursor:var(--cursor);color:var(--icon-color);display:flex;align-items:center;padding:var(--size-4-1) 0;height:var(--input-height);user-select:none}.metadata-property-icon::before{content:"​";width:var(--size-4-1)}.metadata-input-number{background-color:transparent;width:100%;border-width:0}input[type=checkbox].metadata-input-checkbox{margin-left:var(--size-4-2)}.metadata-property-key{display:flex;align-self:stretch;align-items:flex-start;flex-direction:row;flex-shrink:0;border-bottom:var(--metadata-divider-width) solid var(--metadata-divider-color);background-color:var(--metadata-label-background);width:var(--metadata-label-width);min-width:var(--metadata-label-width)}.metadata-property-key:focus-within{background-color:var(--metadata-label-background-active)}input.metadata-property-key-input{border:none;flex-grow:1;color:var(--metadata-label-text-color);font-size:var(--metadata-label-font-size);font-weight:var(--metadata-label-font-weight);height:var(--input-height);background-color:transparent;display:flex;align-items:center;text-overflow:ellipsis;overflow:hidden}input.metadata-property-key-input:active,input.metadata-property-key-input:focus{background-color:transparent}.metadata-property button{margin-top:var(--size-4-2)}.metadata-property .multi-select-container,.metadata-property input[type=date],.metadata-property input[type=datetime-local],.metadata-property input[type=number],.metadata-property input[type=text]{border-radius:0;border:none}.metadata-property .multi-select-container:active,.metadata-property .multi-select-container:focus,.metadata-property .multi-select-container:hover,.metadata-property input[type=date]:active,.metadata-property input[type=date]:focus,.metadata-property input[type=date]:hover,.metadata-property input[type=datetime-local]:active,.metadata-property input[type=datetime-local]:focus,.metadata-property input[type=datetime-local]:hover,.metadata-property input[type=number]:active,.metadata-property input[type=number]:focus,.metadata-property input[type=number]:hover,.metadata-property input[type=text]:active,.metadata-property input[type=text]:focus,.metadata-property input[type=text]:hover{box-shadow:none;border:none}.metadata-property .metadata-input-number,.metadata-property .metadata-input-text,.metadata-property .multi-select-container{background-color:transparent}.metadata-property .metadata-input-number:hover,.metadata-property .metadata-input-text:hover,.metadata-property .multi-select-container:hover{background-color:transparent}.metadata-property .metadata-input-number:active,.metadata-property .metadata-input-number:focus,.metadata-property .metadata-input-number:focus-within,.metadata-property .metadata-input-text:active,.metadata-property .metadata-input-text:focus,.metadata-property .metadata-input-text:focus-within,.metadata-property .multi-select-container:active,.metadata-property .multi-select-container:focus,.metadata-property .multi-select-container:focus-within{background-color:transparent}.metadata-property .metadata-input-text{text-overflow:ellipsis;overflow:hidden}.metadata-property .multi-select-container input{background-color:transparent}.metadata-property .multi-select-container{padding:var(--size-4-1) var(--size-4-2)}.metadata-property[data-property-key=tags]{--pill-color:var(--tag-color);--pill-color-hover:var(--tag-color-hover);--pill-color-remove:var(--tag-color);--pill-color-remove-hover:var(--tag-color-hover);--pill-decoration:var(--tag-decoration);--pill-decoration-hover:var(--tag-decoration-hover);--pill-background:var(--tag-background);--pill-background-hover:var(--tag-background-hover);--pill-border-color:var(--tag-border-color);--pill-border-color-hover:var(--tag-border-color-hover);--pill-border-width:var(--tag-border-width);--pill-padding-x:var(--tag-padding-x);--pill-padding-y:var(--tag-padding-y);--pill-radius:var(--tag-radius);--pill-weight:var(--tag-weight);--pill-focus-width:100%;--pill-focus-left-adjust:0}.metadata-property[data-property-key=tags] .multi-select-pill{cursor:var(--cursor-link)}.metadata-property:not([data-property-key=tags]){--pill-border-width:0;--pill-padding-x:0;--pill-padding-y:0;--pill-color:var(--metadata-input-text-color)}.metadata-property:not([data-property-key=tags]) .multi-select-pill{line-height:var(--line-height-tight)}.metadata-property-warning-icon{--icon-size:var(--icon-s);position:absolute;right:var(--size-2-1);top:var(--size-2-1);bottom:var(--size-2-1);align-items:center;display:flex;color:var(--text-warning)}.metadata-property-value{display:flex;flex:1 1 auto;gap:var(--size-2-2);align-items:center;align-self:stretch;min-height:var(--input-height);background-color:var(--metadata-input-background);border-bottom:var(--metadata-divider-width) solid var(--metadata-divider-color);overflow:hidden}.metadata-property-value.mod-external-link:not(:placeholder-shown){text-decoration-line:var(--link-decoration);text-decoration-thickness:var(--link-decoration-thickness);text-decoration-color:var(--text-faint)}.metadata-property-value .mod-unknown{color:var(--text-warning);padding:var(--size-4-1) var(--size-4-2);font-size:var(--metadata-input-font-size);text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.metadata-property-value .multi-select-input{font-size:inherit}.metadata-property-value .metadata-link-inner,.metadata-property-value .multi-select-container,.metadata-property-value input{font-size:var(--metadata-input-font-size)}.metadata-property-value .multi-select-container{--background-modifier-form-field:transparent;--background-modifier-border:transparent}.metadata-property-value .multi-select-container .multi-select-pill.is-invalid{--pill-background:transparent;--pill-color:var(--text-error);--pill-color-remove:var(--text-error)}.metadata-property-value .external-link.multi-select-pill-content,.metadata-property-value .internal-link .multi-select-pill-content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.metadata-property-value .clickable-icon{--icon-size:var(--icon-xs);--icon-stroke:var(--icon-xs-stroke-width);margin-right:2px;margin-left:-4px;padding:4px}.metadata-property-value .clickable-icon:hover{background:0 0;color:var(--text-normal);cursor:var(--cursor-link)}.metadata-property-value:focus-within{background-color:var(--metadata-input-background-active)}.metadata-input-longtext{cursor:text;white-space:pre-wrap;-webkit-box-orient:vertical;-webkit-line-clamp:3;color:var(--metadata-input-text-color);font-size:var(--metadata-input-font-size);max-height:300px;overflow-y:auto;padding:var(--size-4-1) var(--size-4-2);width:100%}.metadata-input-longtext:focus{-webkit-line-clamp:unset}.metadata-input-longtext:not(:empty){display:-webkit-box}.metadata-input-longtext:empty::before{content:attr(placeholder);color:var(--text-faint)}.metadata-link{cursor:text;align-items:center;padding:var(--size-4-1) var(--size-4-2);display:flex;gap:var(--size-4-2);width:100%}.metadata-link-inner{cursor:var(--cursor-link);color:var(--link-color);text-decoration-line:var(--link-decoration);text-decoration-thickness:var(--link-decoration-thickness);text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.metadata-link-flair{--icon-size:var(--icon-xs);background-color:transparent;cursor:text;margin-left:auto;display:flex;align-items:center;justify-content:center;padding:var(--size-2-1);border-radius:var(--clickable-icon-radius);color:var(--icon-color);opacity:0;transition:opacity .15s ease-in-out 0s;height:auto}.markdown-preview-view.show-properties .metadata-container{display:var(--metadata-display-reading)}.markdown-rendered li h1,.markdown-rendered li h2,.markdown-rendered li h3,.markdown-rendered li h4,.markdown-rendered li h5{margin-top:0;margin-bottom:0}.markdown-rendered h1,h1{font-variant:var(--h1-variant);letter-spacing:-.015em;line-height:var(--h1-line-height);font-size:var(--h1-size);color:var(--h1-color);font-weight:var(--h1-weight);font-style:var(--h1-style);font-family:var(--h1-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h1 a,h1 a{font-weight:inherit}.markdown-rendered h2,h2{font-variant:var(--h2-variant);letter-spacing:-.015em;line-height:var(--h2-line-height);font-size:var(--h2-size);color:var(--h2-color);font-weight:var(--h2-weight);font-style:var(--h2-style);font-family:var(--h2-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h2 a,h2 a{font-weight:inherit}.markdown-rendered h3,h3{font-variant:var(--h3-variant);letter-spacing:-.015em;line-height:var(--h3-line-height);font-size:var(--h3-size);color:var(--h3-color);font-weight:var(--h3-weight);font-style:var(--h3-style);font-family:var(--h3-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h3 a,h3 a{font-weight:inherit}.markdown-rendered h4,h4{font-variant:var(--h4-variant);letter-spacing:.015em;line-height:var(--h4-line-height);font-size:var(--h4-size);color:var(--h4-color);font-weight:var(--h4-weight);font-style:var(--h4-style);font-family:var(--h4-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h4 a,h4 a{font-weight:inherit}.markdown-rendered h5,h5{font-variant:var(--h5-variant);letter-spacing:.015em;font-size:var(--h5-size);line-height:var(--h5-line-height);color:var(--h5-color);font-weight:var(--h5-weight);font-style:var(--h5-style);font-family:var(--h5-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h5 a,h5 a{font-weight:inherit}.markdown-rendered h6,h6{font-variant:var(--h6-variant);letter-spacing:.015em;font-size:var(--h6-size);line-height:var(--h6-line-height);color:var(--h6-color);font-weight:var(--h6-weight);font-style:var(--h6-style);font-family:var(--h6-font);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered h6 a,h6 a{font-weight:inherit}hr{border-right-width:initial;border-bottom-width:initial;border-left-width:initial;border-right-style:none;border-bottom-style:none;border-left-style:none;border-image:initial;border-color:var(--hr-color);margin:2rem 0}.markdown-rendered hr{border-right-width:initial;border-bottom-width:initial;border-left-width:initial;border-right-style:none;border-bottom-style:none;border-left-style:none;border-image:initial;border-color:var(--hr-color)}.markdown-preview-view img,.markdown-rendered img{image-rendering:-webkit-optimize-contrast}.markdown-preview-view img:not([width]),.markdown-rendered img:not([width]){max-width:100%;outline:0}.internal-query{margin:0;border-top:1px solid var(--background-modifier-border)}.internal-query .search-result-container{padding:var(--size-4-2);max-height:800px;overflow:auto;border:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-radius:var(--radius-m)}ol ol ul,ol ul,ol ul ul,ul ol ul,ul ul,ul ul ul{list-style-type:disc}ol{list-style-type:var(--list-numbered-style)}ol>li::marker,ul>li::marker{color:var(--list-marker-color)}ol>li.is-collapsed::marker,ul>li.is-collapsed::marker{color:var(--list-marker-color-collapsed)}.markdown-rendered ol,.markdown-rendered ul{padding-inline-start:var(--list-indent);margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered ol ol,.markdown-rendered ol ul,.markdown-rendered ul ol,.markdown-rendered ul ul{margin-block:0px}.markdown-rendered ol li p:first-of-type,.markdown-rendered ul li p:first-of-type{margin-block-start:0px}.markdown-rendered ol li p:last-of-type,.markdown-rendered ul li p:last-of-type{margin-block-end:0px}.markdown-rendered ol>li,.markdown-rendered ul>li{padding-top:var(--list-spacing);padding-bottom:var(--list-spacing)}.markdown-rendered .list-collapse-indicator{margin-inline-start:-3em;padding-inline-end:2em}.markdown-rendered .list-bullet{float:left;margin-inline-start:-1em}.markdown-rendered .task-list-item>.list-bullet{display:none}.markdown-rendered ul.has-list-bullet{list-style-type:"​"}.markdown-rendered ul.has-list-bullet>li::marker{color:transparent}.list-bullet{color:transparent;position:relative;display:inline-flex;justify-content:center;align-items:center;will-change:transform}.list-bullet::before{content:"​"}.list-bullet::after{position:absolute;content:"​";pointer-events:none;color:var(--list-marker-color);border-radius:var(--list-bullet-radius);width:var(--list-bullet-size);height:var(--list-bullet-size);border:var(--list-bullet-border);transform:var(--list-bullet-transform);background-color:var(--list-marker-color);transition:transform .15s ease 0s,box-shadow .15s ease 0s;will-change:transform}.list-bullet::selection{background-color:transparent!important}a{color:var(--link-color);outline:0;text-decoration-line:var(--link-decoration);text-decoration-thickness:var(--link-decoration-thickness);cursor:var(--cursor-link)}.external-link{color:var(--link-external-color);text-decoration-line:var(--link-external-decoration);background-position:right 4px;background-repeat:no-repeat;background-image:linear-gradient(transparent,transparent),url("https://publish.obsidian.md/public/images/874d8b8e340f75575caa.svg");background-size:13px;padding-right:16px;cursor:var(--cursor-link);filter:var(--link-external-filter)}.markdown-rendered .internal-link,.metadata-container .internal-link{cursor:var(--cursor-link);text-decoration-line:var(--link-decoration);color:var(--link-color)}.markdown-rendered .internal-link.is-unresolved,.metadata-container .internal-link.is-unresolved{color:var(--link-unresolved-color);opacity:var(--link-unresolved-opacity);filter:var(--link-unresolved-filter);text-decoration-style:var(--link-unresolved-decoration-style);text-decoration-color:var(--link-unresolved-decoration-color)}.inline-block{display:inline-block;vertical-align:middle}.hidden-token{display:inline;letter-spacing:-1ch;font-family:monospace;color:transparent;font-size:1px!important}mjx-container{outline:0}.markdown-rendered td,.markdown-rendered th{padding:var(--size-2-2) var(--size-4-2);border:var(--table-border-width) solid var(--table-border-color);max-width:var(--table-column-max-width)}.markdown-rendered td{font-size:var(--table-text-size);color:var(--table-text-color)}.markdown-rendered th{font-size:var(--table-header-size);font-weight:var(--table-header-weight);color:var(--table-header-color);font-family:var(--table-header-font);text-align:left;line-height:var(--line-height-tight)}.markdown-rendered th[align=center]{text-align:center}.markdown-rendered th[align=right]{text-align:right}.markdown-rendered tbody>tr>td,.markdown-rendered thead>tr>th{white-space:var(--table-white-space);text-overflow:ellipsis;overflow:hidden}.markdown-rendered tbody tr{background-color:var(--table-background)}.markdown-rendered tbody tr:nth-child(odd){background-color:var(--table-row-alt-background)}.markdown-rendered tbody tr>td:nth-child(2n+2){background-color:var(--table-column-alt-background)}.markdown-rendered tbody tr:last-child>td{border-bottom-width:var(--table-row-last-border-width)}.markdown-rendered tbody tr>td:first-child{border-left-width:var(--table-column-first-border-width)}.markdown-rendered tbody tr>td:last-child{border-right-width:var(--table-column-last-border-width)}.markdown-rendered thead tr{background-color:var(--table-header-background)}.markdown-rendered thead tr>th{border-top-width:var(--table-header-border-width);border-color:var(--table-header-border-color)}.markdown-rendered thead tr>th:nth-child(2n+2){background-color:var(--table-column-alt-background)}.markdown-rendered thead tr>th:first-child{border-left-width:var(--table-column-first-border-width)}.markdown-rendered thead tr>th:last-child{border-right-width:var(--table-column-last-border-width)}a.tag{background-color:var(--tag-background);border:var(--tag-border-width) solid var(--tag-border-color);border-radius:var(--tag-radius);color:var(--tag-color);font-size:var(--tag-size);font-weight:var(--tag-weight);text-decoration:var(--tag-decoration);padding:var(--tag-padding-y) var(--tag-padding-x);line-height:1}a.tag{background-color:var(--tag-background);border:var(--tag-border-width) solid var(--tag-border-color);border-radius:var(--tag-radius);color:var(--tag-color);font-size:var(--tag-size);font-weight:var(--tag-weight);text-decoration:var(--tag-decoration);padding:var(--tag-padding-y) var(--tag-padding-x);line-height:1}input[type=checkbox]{appearance:none;border-radius:var(--checkbox-radius);border:1px solid var(--checkbox-border-color);flex-shrink:0;padding:0;margin:0;margin-inline-end:6px;width:var(--checkbox-size);height:var(--checkbox-size);position:relative;transition:box-shadow .15s ease-in-out 0s}input[type=checkbox]:active,input[type=checkbox]:focus,input[type=checkbox]:hover{outline:0;border-color:var(--checkbox-border-color-hover)}input[type=checkbox]:focus-visible{box-shadow:0 0 0 2px var(--background-modifier-border-focus)}input[type=checkbox]:checked::after{content:"";top:-1px;left:-1px;position:absolute;width:var(--checkbox-size);height:var(--checkbox-size);display:block;background-color:var(--checkbox-marker-color);-webkit-mask-position:52% 52%;-webkit-mask-size:65%;-webkit-mask-repeat:no-repeat;-webkit-mask-image:url("data:image/svg+xml; utf8, ")}input[type=checkbox]:checked{background-color:var(--checkbox-color);border-color:var(--checkbox-color)}input[type=checkbox][data-indeterminate=true]:not(:checked)::after{content:"";position:absolute;top:calc(var(--checkbox-size)/ 2 - 2px);width:calc(var(--checkbox-size) - 6px);left:0;right:0;margin:0 auto;height:2px;display:block;border-radius:2px;background-color:var(--text-normal)}.task-list-item-checkbox{width:var(--checkbox-size);height:var(--checkbox-size)}.markdown-preview-view .task-list-item-checkbox{position:relative;top:.2em;margin-inline-end:.6em}ul>li.task-list-item{list-style:none}ul>li.task-list-item .task-list-item-checkbox{margin-inline-start:calc(var(--checkbox-size) * -1.5)}ul>li.task-list-item[data-task="X"],ul>li.task-list-item[data-task="x"]{text-decoration:var(--checklist-done-decoration);color:var(--checklist-done-color)}b,strong{font-weight:var(--bold-weight);color:var(--bold-color)}em,i{font-style:italic;color:var(--italic-color)}.markdown-rendered p{margin-block-start:var(--p-spacing);margin-block-end:var(--p-spacing)}.markdown-rendered mark{background-color:var(--text-highlight-bg);color:var(--text-normal)}.markdown-rendered mark .internal-link{color:var(--text-normal)}.view-action.mod-bookmark{--icon-color:var(--icon-color-active);--icon-color-hover:var(--icon-color-active)}.nav-buttons-container.has-separator{border-bottom:1px solid var(--background-modifier-border);padding-bottom:var(--size-2-3);margin-bottom:var(--size-4-2)}.nav-files-container{flex-grow:1;overflow:hidden auto;padding:0 var(--size-4-3) var(--size-4-6) var(--size-4-3);scroll-padding-block:var(--size-4-2)}.nav-folder.mod-root>.nav-folder-title{font-size:var(--vault-name-font-size);color:var(--vault-name-color);font-weight:var(--vault-name-font-weight);cursor:default}.nav-folder.mod-root>.nav-folder-title.is-being-dragged-over{background-color:hsla(var(--interactive-accent-hsl),.2)}.nav-folder.mod-root>.nav-folder-children{border-left:none;margin-left:0;padding-left:0}.nav-file-tag{background-color:var(--background-modifier-hover);border-radius:var(--radius-s);font-size:9px;font-weight:var(--font-semibold);letter-spacing:.05em;line-height:var(--line-height-normal);margin-left:var(--size-2-3);padding:0 var(--size-4-1);text-transform:uppercase;align-self:center}.nav-file-icon{display:inline-flex;align-items:center;margin-right:var(--size-2-3);position:relative;color:var(--icon-color);opacity:var(--icon-opacity)}.nav-files-container:not(.show-unsupported) .is-unsupported{display:none}.nav-file-title-content,.nav-folder-title-content{display:inline-block;overflow-wrap:anywhere;overflow:hidden;white-space:var(--nav-item-white-space);text-overflow:ellipsis}.nav-files-container{flex-grow:1;overflow:hidden auto;padding:0 var(--size-4-3) var(--size-4-6) var(--size-4-3);scroll-padding-block:var(--size-4-2)}.nav-folder.mod-root>.nav-folder-title{font-size:var(--vault-name-font-size);color:var(--vault-name-color);font-weight:var(--vault-name-font-weight);cursor:default}.nav-folder.mod-root>.nav-folder-title.is-being-dragged-over{background-color:hsla(var(--interactive-accent-hsl),.2)}.nav-folder.mod-root .nav-folder>.nav-folder-children{padding-left:var(--nav-item-children-padding-left);margin:0 0 0 var(--nav-item-children-margin-left);border-left:var(--nav-indentation-guide-width) solid var(--nav-indentation-guide-color)}.nav-file{border-radius:var(--radius-s)}.nav-folder-title{padding:var(--nav-item-parent-padding)}.nav-file-title{padding:var(--nav-item-padding)}.nav-file-title,.nav-folder-title{margin-bottom:var(--size-2-1);display:flex;border-radius:var(--radius-s);cursor:var(--cursor);color:var(--nav-item-color);font-size:var(--nav-item-size);font-weight:var(--nav-item-weight);line-height:var(--line-height-tight)}.nav-file-title.is-active,.nav-folder-title.is-active,body:not(.is-grabbing) .nav-file-title.is-active:hover,body:not(.is-grabbing) .nav-folder-title.is-active:hover{color:var(--nav-item-color-active);background-color:var(--nav-item-background-active);font-weight:var(--nav-item-weight-active)}.nav-file-title.is-selected,.nav-folder-title.is-selected,body:not(.is-grabbing) .nav-file-title.is-selected:hover,body:not(.is-grabbing) .nav-folder-title.is-selected:hover{color:var(--nav-item-color-selected);background-color:var(--nav-item-background-selected)}.nav-file-title.is-being-dragged,.nav-folder-title.is-being-dragged,body:not(.is-grabbing) .nav-file-title.is-being-dragged,body:not(.is-grabbing) .nav-folder-title.is-being-dragged{background-color:var(--interactive-accent);color:var(--text-on-accent)}.nav-file-title.is-being-dragged .nav-folder-collapse-indicator,.nav-folder-title.is-being-dragged .nav-folder-collapse-indicator,body:not(.is-grabbing) .nav-file-title.is-being-dragged .nav-folder-collapse-indicator,body:not(.is-grabbing) .nav-folder-title.is-being-dragged .nav-folder-collapse-indicator{color:var(--text-on-accent)}.nav-file-title.is-being-dragged .nav-file-tag,.nav-folder-title.is-being-dragged .nav-file-tag,body:not(.is-grabbing) .nav-file-title.is-being-dragged .nav-file-tag,body:not(.is-grabbing) .nav-folder-title.is-being-dragged .nav-file-tag{color:var(--text-normal)}.nav-folder-title.is-being-dragged-over{border-radius:var(--radius-s);color:var(--nav-item-color-highlighted);background:hsla(var(--interactive-accent-hsl),.1)}.nav-folder-title.is-being-dragged-over .collapse-icon{color:var(--nav-item-color-highlighted)}.file-tree-item-checkbox,.file-tree-item-icon{flex-shrink:0}.file-tree-item-title{flex-grow:1;word-break:break-word}.file-tree-item-icon{--icon-size:var(--icon-s);--icon-stroke:var(--icon-s-stroke-width);margin-right:var(--size-4-1);color:var(--icon-color);position:relative;top:var(--size-2-1)}.file-tree .tree-item-inner{display:flex;align-items:center;position:relative;width:100%}.file-tree .tree-item-flair{line-height:1;padding:var(--size-2-1) var(--size-2-3);color:var(--text-on-accent)}.file-tree .is-selected{color:var(--text-normal)}.file-tree .mod-changed.is-selected{background-color:hsla(var(--interactive-accent-hsl),.2)}.file-tree .mod-changed .tree-item-flair{color:var(--text-accent-hover)}.file-tree .mod-new.is-selected{background-color:rgba(var(--background-modifier-success-rgb),.2)}.file-tree .mod-new .tree-item-flair{color:var(--text-success)}.file-tree .mod-deleted.is-selected,.file-tree .mod-to-delete.is-selected{background-color:rgba(var(--background-modifier-error-rgb),.2)}.file-tree .mod-deleted .tree-item-flair,.file-tree .mod-to-delete .tree-item-flair{color:var(--text-error)}.file-tree .mod-to-delete .tree-item-flair{display:none}.file-tree .mod-to-delete.is-selected .tree-item-flair{display:block}.file-tree .clickable-icon{display:flex;--icon-size:var(--icon-s);--icon-stroke:var(--icon-s-stroke-width)}.graph-view.color-fill{color:var(--graph-node)}.graph-view.color-fill-focused{color:var(--graph-node-focused)}.graph-view.color-fill-tag{color:var(--graph-node-tag)}.graph-view.color-fill-attachment{color:var(--graph-node-attachment)}.graph-view.color-fill-unresolved{color:var(--graph-node-unresolved);opacity:.5}.graph-view.color-fill-1{color:var(--text-muted)}.graph-view.color-fill-2{color:var(--text-muted)}.graph-view.color-fill-3{color:var(--text-muted)}.graph-view.color-fill-4{color:var(--text-muted)}.graph-view.color-fill-5{color:var(--text-muted)}.graph-view.color-fill-6{color:var(--text-muted)}.graph-view.color-arrow{color:var(--text-normal);opacity:.5}.graph-view.color-circle{color:var(--graph-node-focused)}.graph-view.color-line{color:var(--graph-line)}.graph-view.color-text{color:var(--graph-text)}.graph-view.color-fill-highlight{color:var(--interactive-accent)}.graph-view.color-line-highlight{color:var(--interactive-accent)}.graph-controls{border-radius:var(--radius-m);position:absolute;right:var(--size-4-3);top:var(--size-4-3);padding:0;background-color:var(--background-primary);width:var(--graph-controls-width);overflow:auto}.graph-controls:not(.is-close){max-height:calc(100% - var(--size-4-4));border:1px solid var(--background-modifier-border);box-shadow:var(--shadow-s)}.graph-controls.is-close{min-width:inherit;width:auto;background-color:var(--background-primary);border:1px solid transparent;padding:var(--size-2-3)}.graph-controls.is-close>.graph-control-section{display:none}.graph-controls input[type=range],.graph-controls input[type=text]{width:100%;font-size:var(--font-ui-small)}.graph-controls .mod-cta{margin-top:var(--size-2-3);width:100%}.graph-controls::-webkit-scrollbar,.graph-controls::-webkit-scrollbar-thumb{display:none}.graph-color-group{--swatch-height:18px;--swatch-width:18px;position:relative;display:flex;align-items:center;padding:0 0 6px;transition:top .2s ease-in-out 0s}.graph-color-group input[type=color]{margin:0 2px 0 6px}.graph-color-group .clickable-icon{padding:var(--size-2-2)}.graph-color-button-container{text-align:center;margin-bottom:10px}.graph-color-button-container button{margin:0;width:100%}.graph-control-section.mod-color-groups .tree-item-children.is-grabbing .graph-color-groups-container{padding-bottom:40px}.graph-controls-button{display:none;z-index:1}.graph-controls-button.mod-close,.graph-controls-button.mod-reset{position:absolute;top:var(--size-4-2);right:var(--size-4-2);padding:var(--size-2-2)}.graph-controls:not(.is-close) .graph-controls-button.mod-close,.graph-controls:not(.is-close) .graph-controls-button.mod-reset{display:flex}.graph-controls-button.mod-reset{right:36px}.graph-controls.is-close .graph-controls-button.mod-open{display:flex}.graph-controls-button.mod-animate{margin-top:var(--size-4-2)}.graph-controls.is-close .graph-controls-button.mod-animate{display:flex}.graph-control-section{padding:var(--size-2-3) var(--size-4-3);border-bottom:1px solid var(--background-modifier-border)}.graph-control-section:last-child{border-bottom:none}.graph-control-section:last-child .tree-item-children{padding-bottom:var(--size-4-4)}.graph-control-section>.tree-item-self{padding-left:var(--size-4-4)}.graph-control-section .tree-item-children{margin:0;padding:var(--size-4-1) 0;border-left:none}.metadata-container{container:metadata/inline-size}@container (width < 250px){.metadata-property{flex-wrap:wrap}.metadata-property .metadata-property-key{width:100%;border-bottom:0}.metadata-property .metadata-property-value{margin-top:-2px;padding-left:calc(var(--icon-size) + var(--size-4-1))}}.site-list-container{border-top:1px solid var(--background-modifier-border);margin-bottom:var(--size-4-4)}.site-list-container .list-item:last-child{padding-top:var(--size-4-4)}.site-list-item-name{flex-grow:1}.slug-input{text-transform:lowercase}.passwords-container{margin-bottom:var(--size-4-4)}.password-item{border-radius:var(--radius-s);padding:var(--size-4-2) var(--size-4-4);margin:var(--size-4-1) 0}.tree-item.mod-custom-nav.hidden .tree-item-self{color:var(--text-faint)}.tree-item.mod-custom-nav .tree-item-inner{display:flex;align-items:center;position:relative}.tree-list{padding:var(--size-4-4) 0}.tree-list-title{font-size:var(--font-ui-medium);font-weight:var(--font-semibold)}.tree-list-action{align-items:center;display:flex;color:var(--text-muted);font-size:var(--font-ui-small)}.search-input-container{position:relative}.search-input-container::before{top:calc((var(--input-height) - var(--search-icon-size))/ 2);left:8px;position:absolute;content:"";height:var(--search-icon-size);width:var(--search-icon-size);display:block;background-color:var(--search-icon-color);-webkit-mask-image:url("data:image/svg+xml,");-webkit-mask-repeat:no-repeat}.search-input-container input{display:block;width:100%;padding-right:28px;padding-left:36px}.global-search-input-container.search-input-container input{padding-right:56px}.search-input-clear-button{position:absolute;background:0 0;border-radius:50%;color:var(--search-clear-button-color);cursor:var(--cursor);top:0;right:2px;bottom:0;line-height:0;height:var(--input-height);width:28px;margin:auto;padding:0;text-align:center;display:flex;justify-content:center;align-items:center;transition:color .15s ease-in-out 0s}.search-input-clear-button::after{content:"";height:var(--search-clear-button-size);width:var(--search-clear-button-size);display:block;background-color:currentcolor;-webkit-mask-image:url("data:image/svg+xml,");-webkit-mask-repeat:no-repeat}.search-input-clear-button:active,.search-input-clear-button:hover{color:var(--text-normal);transition:color .15s ease-in-out 0s}.search-input-suggest-button{position:absolute;left:0;top:0;color:var(--text-faint);cursor:var(--cursor);padding:var(--size-4-1) var(--size-4-2);opacity:0;z-index:10}.search-result-container{padding:var(--size-4-3) var(--size-4-3) var(--size-4-4);position:relative;flex:1 0 0px}.search-result-container.mod-global-search{overflow-y:auto}.search-result-container::before{content:" ";position:absolute;top:0;width:0;height:3px}.search-suggest-info-text{color:var(--text-muted);margin-left:4px}.search-suggest-icon{padding:4px;border-radius:var(--radius-s)}.search-suggest-icon{align-items:center;display:flex}.search-suggest-item{padding:var(--size-4-1) var(--size-4-2);border-radius:var(--radius-s)}.search-suggest-item.mod-group{align-items:center;margin:0;color:var(--text-muted);padding:0 0 0 var(--size-4-2);cursor:default;font-weight:var(--font-semibold);font-size:var(--font-ui-smaller);border-radius:0}.search-suggest-item.mod-group:not(:first-child){border-top:1px solid var(--background-modifier-border);margin-top:6px;padding:6px 6px 0 14px;margin-left:-6px;margin-right:-6px}.search-suggest-item.mod-group.is-selected,.search-suggest-item.mod-group:hover{background-color:initial}.search-empty-state{color:var(--text-faint);font-size:var(--font-ui-small);margin:0 0 var(--size-4-3);padding-left:var(--size-4-2)}.search-result{word-break:break-word}.search-result:not(.is-collapsed) .search-result-file-title{color:var(--nav-item-color-active)}.search-result-file-matches{font-size:var(--font-ui-smaller);line-height:var(--line-height-tight);background-color:var(--search-result-background);border-radius:var(--radius-s);overflow:hidden;margin:var(--size-4-1) 0 var(--size-4-2);color:var(--text-muted);box-shadow:0 0 0 1px var(--background-modifier-border)}.search-result-file-matches:empty{display:none}.search-info-more-matches{color:var(--text-faint)}.search-result-file-match{cursor:var(--cursor);position:relative;padding:var(--size-4-2) var(--size-4-5) var(--size-4-2) var(--size-4-3);white-space:pre-wrap;width:100%;border-bottom:1px solid var(--background-modifier-border)}.search-result-file-match:last-child{border-bottom:none}.search-result-file-match:hover .search-result-file-match-replace-button{display:block}.search-result-file-match-replace-button{display:none;position:absolute;height:auto;bottom:5px;right:24px;padding:var(--size-4-1) var(--size-4-2);color:var(--text-muted);font-size:var(--font-ui-smaller)}.search-result-hover-button{position:absolute;display:flex;right:2px;border-radius:var(--radius-s);color:var(--text-faint);padding:1px 3px}.search-result-hover-button.mod-top{top:2px}.search-result-hover-button.mod-bottom{bottom:2px}.search-result-file-matched-text{color:var(--text-normal);background-color:var(--text-highlight-bg)}.search-info-container{color:var(--text-muted);padding:var(--size-4-1) var(--size-4-4) var(--size-4-1);font-size:var(--font-ui-smaller)}.search-info-children{padding-left:20px;border-left:1px solid var(--background-modifier-border);margin:1px 0}.copy-search-result-container{display:flex;flex-direction:column}.copy-search-result-textarea{height:300px;max-height:20vh;resize:none}.search-result-file-match-destination-file-container{margin-top:var(--size-2-3)}.search-result-file-match-destination-file{display:inline-flex;background-color:var(--interactive-normal);border-radius:var(--radius-s);box-shadow:var(--input-shadow);color:var(--text-muted);padding:var(--size-2-2) var(--size-2-3);margin-bottom:var(--size-2-1)}.search-result-file-match-destination-file-icon{--icon-size:var(--icon-xs);--icon-stroke:var(--icon-xs-stroke-width);margin-right:var(--size-4-1);display:flex;color:var(--text-faint)}.search-result-file-match-destination-file-icon .svg-icon{align-self:center}.search-result-file-match-destination-file-name{white-space:pre-wrap;word-break:break-all}.search-results-info{color:var(--text-muted);display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--background-modifier-border);margin:var(--size-4-1) var(--size-4-2) 0;padding-bottom:var(--size-4-1);white-space:nowrap}.search-results-result-count{font-size:var(--font-ui-smaller)}.search-row{display:flex;margin:var(--size-4-2);gap:var(--size-4-1)}.search-params{margin:var(--size-4-1) var(--size-4-4)}.search-params input[type=range],.search-params input[type=text]{width:100%;font-size:var(--font-ui-small)}.search-params .mod-cta{margin-top:var(--size-2-3);width:100%}.search-params::-webkit-scrollbar,.search-params::-webkit-scrollbar-thumb{display:none}.global-search-input-container{flex-grow:1}.more-options-icon{--icon-size:10px;background-color:var(--icon-color);border-radius:50%;color:var(--background-secondary);display:flex;margin-left:var(--size-2-3);opacity:var(--icon-opacity);padding:.5px}.clickable-icon:hover .more-options-icon{background-color:var(--icon-color-hover);opacity:var(--icon-opacity-hover)}.slides-container{position:fixed;top:0;left:0;height:100vh;width:100vw;transition:-webkit-transform .8s ease 0s;background-color:#191919;z-index:var(--layer-slides);border:none}.slides-container li .collapse-indicator{display:none}.slides-close-btn{display:inline-block;position:absolute;top:var(--size-4-2);right:var(--size-4-2);color:var(--text-faint);cursor:var(--cursor);z-index:1}.reveal input[type=checkbox]{width:24px;height:24px}.reveal .footnote-item,.reveal .task-list-item{list-style:none}.reveal .task-list-item{margin-left:-1.5em}.tag-pane-tag.is-active{background-color:var(--interactive-accent);color:var(--text-on-accent)}.tag-pane-tag.is-active .tag-pane-tag-count{background-color:var(--background-modifier-hover);color:var(--text-normal)}.tag-container{font-size:var(--font-ui-small);padding:var(--size-4-3) var(--size-4-3) var(--size-4-8);overflow:auto}.tree-item-children .tag-pane-tag .tag-pane-tag-parent{display:none}.mod-canvas-color-1{--canvas-color:var(--canvas-color-1)}.mod-canvas-color-2{--canvas-color:var(--canvas-color-2)}.mod-canvas-color-3{--canvas-color:var(--canvas-color-3)}.mod-canvas-color-4{--canvas-color:var(--canvas-color-4)}.mod-canvas-color-5{--canvas-color:var(--canvas-color-5)}.mod-canvas-color-6{--canvas-color:var(--canvas-color-6)}body{--canvas-color:192,192,192}body.theme-dark{--canvas-color:126,126,126}.canvas-wrapper{position:absolute;width:100%;height:100%;left:0;top:0;--resizer-size:20px;--shadow-stationary:0px 0.5px 1px 0.5px rgba(0, 0, 0, 0.1);--shadow-drag:0px 2px 10px rgba(0, 0, 0, 0.1);--shadow-border-accent:0 0 0 2px var(--color-accent);--zoom-multiplier:1;background-color:var(--canvas-background);overflow:hidden;contain:strict;touch-action:none;user-select:none}.canvas-wrapper.is-dragging{cursor:grabbing}.canvas-wrapper.is-dragging iframe:not(.is-controlled),.canvas-wrapper.is-dragging webview{pointer-events:none}.canvas-wrapper.is-screenshotting{z-index:999999}.canvas-wrapper.is-screenshotting *{pointer-events:none!important}.canvas-mover{position:absolute;width:100%;height:100%;left:0;top:0;cursor:grab}.canvas-mover:active{cursor:grabbing}.canvas-background{position:absolute;width:100%;height:100%;left:0;top:0;pointer-events:none}.canvas-background circle{fill:var(--canvas-dot-pattern)}.canvas{position:absolute;width:100%;height:100%;left:0;top:0;transform-origin:0px 0px;pointer-events:none}.canvas>*{pointer-events:initial}.canvas-selection{pointer-events:none;position:absolute;background-color:hsla(var(--color-accent-hsl),.1);border:2px solid var(--color-accent);z-index:-1}.canvas-selection.mod-group-selection{border-width:3px;border-radius:3px;background-color:hsla(var(--color-accent-hsl),.03);border-color:hsla(var(--color-accent-hsl),.3);pointer-events:initial}.canvas-wrapper:not(.mod-readonly) .canvas-selection.mod-group-selection{cursor:grab}.canvas-wrapper:not(.mod-readonly) .canvas-selection.mod-group-selection:active{cursor:grabbing}.canvas-selection.mod-node-highlight{border-radius:var(--radius-m)}.canvas-controls{right:var(--size-4-2);top:var(--size-4-2);gap:var(--size-4-2);display:flex;flex-direction:column}.canvas-control-group{border-radius:var(--radius-s);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);box-shadow:var(--input-shadow);display:flex;flex-direction:column;overflow:hidden}.canvas-control-item{border-radius:0;box-shadow:none;height:auto;display:flex;line-height:1;font-size:inherit;align-items:center;justify-content:center;cursor:var(--cursor);padding:var(--size-4-2);border-bottom:1px solid var(--background-modifier-border);color:var(--text-muted);background-color:var(--interactive-normal);--icon-size:var(--icon-s);--icon-stroke:var(--icon-s-stroke-width)}.canvas-control-item:last-child{border-bottom:none}.canvas-control-item.is-active{color:var(--color-accent)}.canvas-control-item.is-disabled svg{color:var(--text-faint)}.canvas-control-item svg{pointer-events:none}.canvas-node-container{background-color:var(--background-primary);border-radius:var(--radius-m);border:2px solid rgb(var(--canvas-color));contain:strict;display:flex;flex-direction:column;overflow:hidden;position:absolute;left:0;top:0;width:100%;height:100%;box-shadow:var(--shadow-stationary)}.canvas-node-label{position:absolute;left:0;top:calc(-1 * var(--size-4-1) * var(--zoom-multiplier));transform:translate(0,-100%) scale(var(--zoom-multiplier));transform-origin:left bottom;max-width:calc(100% / var(--zoom-multiplier));overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--canvas-card-label-color);--icon-size:1em}body:not(.is-ios) .canvas-wrapper.mod-animating .canvas-node-label{transition:transform .5s cubic-bezier(.16, 1, .3, 1) 0s}.canvas-node-label svg{position:relative;top:2px;margin-right:var(--size-4-1)}.canvas-node-label.mod-hover-label{opacity:0}.canvas-wrapper.mod-zoomed-out .canvas-node-label{display:none}.canvas-node-placeholder{display:flex;align-items:center;justify-content:center;text-align:center;width:100%;height:100%;overflow:hidden;overflow-wrap:anywhere;padding:var(--size-4-6);font-size:32px;font-weight:var(--font-semibold)}.canvas-node-placeholder::after{border-radius:var(--radius-s);content:" ";display:block;position:absolute;top:var(--size-4-4);right:var(--size-4-4);bottom:var(--size-4-4);left:var(--size-4-4);background-color:rgba(var(--canvas-color),.1)}.canvas-icon-placeholder{display:flex;width:40%;height:40%}.canvas-icon-placeholder svg{opacity:.3;color:rgb(var(--canvas-color));width:100%;height:100%}.canvas-node-interaction-layer{position:absolute;width:0;height:0;pointer-events:none}.canvas-node-interaction-layer>*{pointer-events:initial}.canvas-node{--shadow-border-themed-inset:inset 0 0 0 1px rgb(var(--canvas-color));--shadow-border-themed:0 0 0 2px rgb(var(--canvas-color));position:absolute;width:0;height:0}.canvas-node.is-dragging{pointer-events:none}.canvas-node.is-dragging .canvas-node-container{box-shadow:var(--shadow-drag)}.canvas-node.is-focused,.canvas-node.is-selected{touch-action:initial}.canvas-node.is-focused .canvas-node-label,.canvas-node.is-selected .canvas-node-label{color:var(--text-muted)}.canvas-node.is-focused .canvas-node-container,.canvas-node.is-selected .canvas-node-container{border-color:var(--color-accent);box-shadow:var(--shadow-stationary),var(--shadow-border-accent)}.canvas-node.is-focused.is-dragging .canvas-node-container,.canvas-node.is-selected.is-dragging .canvas-node-container{box-shadow:var(--shadow-drag),var(--shadow-border-accent)}.canvas-node.is-themed .canvas-node-container{border-color:rgba(var(--canvas-color),.7);box-shadow:inset 0 0 0 1px rgba(var(--canvas-color),.7),var(--shadow-stationary)}.canvas-node.is-focused.is-themed .canvas-node-container,.canvas-node.is-selected.is-themed .canvas-node-container{border-color:rgb(var(--canvas-color));box-shadow:var(--shadow-border-themed-inset),var(--shadow-border-themed)}.canvas-node.is-focused.is-themed.is-dragging .canvas-node-container,.canvas-node.is-selected.is-themed.is-dragging .canvas-node-container{box-shadow:var(--shadow-border-themed-inset),var(--shadow-border-themed)}.canvas-node.is-dummy{cursor:grabbing}.canvas-node.is-dummy .canvas-node-container{border:4px solid var(--color-accent);box-shadow:rgba(0,0,0,.15) 0 2px 10px;background-color:hsla(var(--color-accent-hsl),.2)}.canvas-node.is-focused:not(.is-dragging) .canvas-node-content-blocker{display:none}.canvas-node-content-blocker{position:absolute;width:100%;height:100%;left:0;top:0;z-index:var(--layer-cover)}.canvas-node-group:not(.is-focused):not(.is-selected){pointer-events:none}.canvas-node-group .canvas-node-resizer{pointer-events:initial}.canvas-node-group .canvas-node-container{background-color:transparent}.canvas-node-group .canvas-node-content{background-color:rgba(var(--canvas-color),.07)}.canvas-group-label{position:absolute;left:0;top:calc(-1 * var(--size-4-1) * var(--zoom-multiplier));transform:translate(0,-100%) scale(var(--zoom-multiplier));transform-origin:left bottom;max-width:calc(100% / var(--zoom-multiplier));overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:initial;font-size:1.5em;padding:var(--size-4-1) var(--size-4-2);border-radius:var(--radius-s);color:var(--text-muted);background-color:rgba(var(--canvas-color),.1);line-height:1}body:not(.is-ios) .canvas-wrapper.mod-animating .canvas-group-label{transition:transform .5s cubic-bezier(.16, 1, .3, 1) 0s}.canvas-wrapper:not(.mod-readonly) .canvas-group-label{cursor:grab}.canvas-wrapper:not(.mod-readonly) .canvas-group-label:active{cursor:grabbing}.canvas-node-content{backface-visibility:hidden;width:100%;height:100%;overflow:hidden;position:relative}.canvas-node-content.markdown-embed{border:none;padding:0}.canvas-node-content.markdown-embed .inline-title{cursor:text}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view{padding:0 var(--size-4-6);display:flex;flex-direction:column}.canvas-wrapper:not(.mod-readonly) .canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view{user-select:none}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view::after,.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view::before{content:" ";display:block;min-height:min(calc(var(--canvas-node-height) * .1 - 3px),var(--size-4-6));max-height:var(--size-4-4);flex:1 1 0px}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view>.markdown-preview-sizer{flex:1 0 0px}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view .callout{mix-blend-mode:normal}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view .markdown-preview-pusher+div>:first-child{margin-top:0}.canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view .markdown-preview-sizer>div:last-child>:last-child{margin-bottom:0}.is-focused .canvas-node-content.markdown-embed>.markdown-embed-content>.markdown-preview-view{transform:translateZ(0)}.canvas-node.is-themed .canvas-node-content{background-color:rgba(var(--canvas-color),.07)}.canvas-node-content.media-embed{justify-content:center;align-items:center;display:flex}.canvas-node-content.media-embed audio,.canvas-node-content.media-embed img,.canvas-node-content.media-embed video{flex-shrink:0;flex-grow:1}.canvas-node-content.media-embed audio,.canvas-node-content.media-embed img:not([width]),.canvas-node-content.media-embed video{max-width:100%}.canvas-node-resizer{position:absolute;height:calc(var(--resizer-size) * var(--zoom-multiplier));width:calc(var(--resizer-size) * var(--zoom-multiplier))}.is-selected .canvas-node-resizer{pointer-events:none}.canvas-wrapper.mod-readonly .canvas-node-resizer{display:none}.canvas-node-resizer[data-resize=top]{left:0;right:0;width:auto;top:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:ns-resize}.canvas-node-resizer[data-resize=bottom]{left:0;right:0;width:auto;bottom:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:ns-resize}.canvas-node-resizer[data-resize=left]{top:0;bottom:0;height:auto;left:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:ew-resize}.canvas-node-resizer[data-resize=right]{top:0;bottom:0;height:auto;right:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:ew-resize}.canvas-node-resizer[data-resize=topright]{right:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);top:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:nesw-resize}.canvas-node-resizer[data-resize=bottomright]{right:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);bottom:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:nwse-resize}.canvas-node-resizer[data-resize=topleft]{left:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);top:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:nwse-resize}.canvas-node-resizer[data-resize=bottomleft]{left:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);bottom:calc(var(--resizer-size) * var(--zoom-multiplier) * -.5);cursor:nesw-resize}.canvas-node-connection-point{width:calc(var(--resizer-size) * var(--zoom-multiplier));height:calc(var(--resizer-size) * var(--zoom-multiplier));position:absolute;pointer-events:all;cursor:pointer}.canvas-node-connection-point[data-side=top]{top:1px;left:calc(50% - var(--resizer-size) * var(--zoom-multiplier)/ 2)}.canvas-node-connection-point[data-side=right]{right:1px;top:calc(50% - var(--resizer-size) * var(--zoom-multiplier)/ 2)}.canvas-node-connection-point[data-side=bottom]{bottom:1px;left:calc(50% - var(--resizer-size) * var(--zoom-multiplier)/ 2)}.canvas-node-connection-point[data-side=left]{left:1px;top:calc(50% - var(--resizer-size) * var(--zoom-multiplier)/ 2)}.canvas-node-connection-point::after{content:" ";background-color:var(--color-accent);border-radius:50%;border:3px solid var(--background-modifier-border);box-sizing:border-box;display:block;height:calc(var(--resizer-size) * var(--zoom-multiplier));opacity:0;position:relative;width:calc(var(--resizer-size) * var(--zoom-multiplier));left:0;top:0}.canvas-snaps{position:absolute;width:100%;height:100%;left:0;top:0;overflow:visible;pointer-events:none;opacity:.6}.canvas-snaps line{stroke-width:1px;stroke:var(--color-accent)}.canvas-snaps circle{fill:var(--color-accent)}.canvas-edges{position:absolute;width:100%;height:100%;left:0;top:0;overflow:visible;pointer-events:none}.canvas-edges>*{pointer-events:initial}.canvas-edges path.canvas-display-path{pointer-events:none;stroke-width:calc(3px * var(--zoom-multiplier));stroke:rgb(var(--canvas-color));fill:none;transition:stroke-width .1s ease-out 0s}.canvas-edges path.canvas-interaction-path{pointer-events:stroke;stroke-width:calc(24px * var(--zoom-multiplier));stroke-linecap:round;stroke:transparent;fill:none;transition:stroke .1s ease-out 0s}.canvas-wrapper:not(.mod-readonly) .canvas-edges path.canvas-interaction-path{cursor:grab}.canvas-wrapper:not(.mod-readonly) .canvas-edges path.canvas-interaction-path:active{cursor:grabbing}.canvas-edges polygon.canvas-path-end{pointer-events:none;stroke:rgb(var(--canvas-color));fill:rgb(var(--canvas-color));stroke-linecap:round;stroke-linejoin:round;stroke-width:1px;transform-box:fill-box;transform:scale(var(--zoom-multiplier));transform-origin:center top}.canvas-edges g.is-focused path.canvas-display-path,.canvas:not(.is-connecting) .canvas-edges g:hover path.canvas-display-path{stroke-width:calc(5.5px * var(--zoom-multiplier))}.canvas-edges g.is-focused path.canvas-interaction-path,.canvas:not(.is-connecting) .canvas-edges g:hover path.canvas-interaction-path{stroke:rgba(var(--canvas-color),0.1)}.canvas-path-label-wrapper{position:absolute;width:fit-content;height:fit-content}.canvas-path-label{font-size:calc(var(--font-ui-large) * var(--zoom-multiplier));background-color:var(--background-primary);border-radius:var(--radius-s);padding:calc(var(--size-2-3) * var(--zoom-multiplier));line-height:var(--line-height-tight);white-space:pre-wrap;transform:translate(-50%,-50%);text-align:center;max-width:calc(17em * var(--zoom-multiplier))}.canvas-color-picker-item{cursor:var(--cursor);width:24px;height:24px;margin:2px;border-radius:12px;border:2px solid var(--background-primary);background-color:rgb(var(--canvas-color))}.canvas-color-picker-item.is-active{box-shadow:0 0 0 2px rgb(var(--canvas-color))}.canvas-color-picker-item input[type=color]{margin:-4px 0 0 -2px;--swatch-width:20px;--swatch-height:20px;opacity:0}.canvas-color-picker-item.canvas-color-picker-custom:not(.is-active){background:conic-gradient(var(--color-red),var(--color-yellow),var(--color-green),var(--color-blue),var(--color-purple),var(--color-red))}.canvas-empty-embed-container{align-items:center;display:flex;flex-direction:column;gap:var(--size-4-6);justify-content:center;height:100%;padding:var(--size-4-3);text-align:center}.canvas-empty-embed-action-list{display:flex;flex-direction:column;gap:var(--size-4-3)}.canvas-empty-embed-action-list button{font-size:var(--font-text-size);padding:var(--size-4-5) var(--size-4-9)}.canvas-help{display:flex;flex-direction:column;gap:var(--size-4-3)}.canvas-instruction{display:flex;justify-content:space-between}.canvas-instruction-desc{display:flex;gap:var(--size-4-1)}.canvas-minimap{width:100%;height:100%;padding:var(--size-4-1)}.inline-embed>.canvas-minimap{max-height:var(--embed-canvas-max-height)}.canvas-minimap rect{stroke-width:5px;stroke:var(--background-modifier-border);fill:var(--background-modifier-border);fill-opacity:0.65}.canvas-minimap rect.is-themed{stroke:rgb(var(--canvas-color));fill:rgb(var(--canvas-color));fill-opacity:0.5}.canvas-minimap path{stroke:rgb(192,192,192);fill:none}.canvas-minimap path.is-themed{stroke:rgb(var(--canvas-color))}.canvas-cursor{position:absolute;width:1px;height:1px;border:5px solid var(--color-accent);border-radius:5px;pointer-events:none}.canvas-watermark *{font-family:var(--font-default)!important}.starter{user-select:none;padding-top:0!important}.starter-screen{display:flex;flex-direction:column;background-color:var(--background-primary);width:100%;height:100%}.starter-screen-inner{flex-grow:1;display:flex;height:calc(100% - 24px)}.splash{align-items:center;background-color:var(--background-primary);display:flex;flex-direction:column;justify-content:center;flex:1 1 auto;text-align:center;padding:36px 0 0}.splash-brand{flex:0 0 content;padding:20px 0}.splash-brand-logo-text{margin-top:20px;color:#fff}.splash-brand-version{color:var(--text-muted);margin-top:8px;font-size:var(--font-ui-small)}.help-options-container{flex:1 0 0px;overflow:auto;width:100%;max-width:82%;text-align:left;padding:var(--size-4-6) 0}.help-options-container::-webkit-scrollbar{display:none}.open-vault-options-container::-webkit-scrollbar{display:none}.quick-start-container{margin-bottom:10px}.quick-start-container button{font-size:var(--font-ui-medium);padding:8px 60px}.open-folder-input[type=text]{font-size:var(--font-ui-small);width:200px;height:28px}.browse-folder-button{margin-left:10px}.open-folder-button{margin-top:14px;padding:6px 36px}.starter .notice{top:38px}:root{--safe-area-inset-top:env(safe-area-inset-top);--safe-area-inset-bottom:env(safe-area-inset-bottom);--safe-area-inset-left:env(safe-area-inset-left);--safe-area-inset-right:env(safe-area-inset-right)}.mod-tappable{transition:opacity .15s ease-in-out 0s}.mod-tappable.mod-tap{opacity:.5}.mod-fade{--scroll-fade-offset-right:0;--scroll-fade-offset-left:0}.mod-fade:not(.mod-at-start)::before{content:" ";position:absolute;top:0;left:var(--scroll-fade-offset-left);width:30px;height:100%;background:linear-gradient(to right,var(--background-primary),transparent)}.mod-fade:not(.mod-at-end)::after{content:" ";position:absolute;top:0;right:var(--scroll-fade-offset-right);width:30px;height:100%;background:linear-gradient(to right,transparent,var(--background-primary))}.workspace-drawer .nav-buttons-container::-webkit-scrollbar,.workspace-drawer .nav-buttons-container::-webkit-scrollbar-thumb,.workspace-drawer .workspace-drawer-actions::-webkit-scrollbar,.workspace-drawer .workspace-drawer-actions::-webkit-scrollbar-thumb{visibility:hidden}.workspace-drawer-ribbon::-webkit-scrollbar,.workspace-drawer-ribbon::-webkit-scrollbar-thumb{visibility:hidden;width:0}.pull-action{position:absolute;background-color:var(--background-secondary);z-index:var(--layer-popover);color:var(--text-muted);font-size:90%;transition:background-color 150ms ease-in-out 0s}.pull-action.mod-activated{background-color:var(--interactive-accent);color:var(--text-on-accent)}.pull-down-action{top:0;left:0;right:0;width:96%;max-width:500px;margin:var(--safe-area-inset-top) auto 0 auto;padding:var(--size-4-3) var(--size-4-4);text-align:center;border-radius:40px}.pull-out-action{top:50%;padding:var(--size-4-3) var(--size-4-4);border-radius:40px;margin:0 var(--size-4-4)}.mobile-toolbar-options-list::-webkit-scrollbar{width:0!important;height:0!important} \ No newline at end of file diff --git a/docs/lib/styles/plugin-styles.css b/docs/lib/styles/plugin-styles.css new file mode 100644 index 0000000..c375e7e --- /dev/null +++ b/docs/lib/styles/plugin-styles.css @@ -0,0 +1,2 @@ +body{--color-fade-speed:0.2s}.sidebar{font-size:14px;display:contents;z-index:1}.sidebar-container{background-color:var(--background-secondary);height:100%;z-index:inherit;transition:width ease-in-out,flex-basis ease-in-out,min-width ease-in-out,background-color var(--color-fade-speed) ease-in-out,box-shadow ease-in-out;transition-duration:.2s;overflow:hidden;flex:none}.floating-sidebars .sidebar:not(.is-collapsed) .sidebar-container{box-shadow:0 0 50px 3em rgba(0,0,0,.4)}.sidebar-gutter{height:100%;width:3em;padding:12px;background-color:hsla(var(--color-accent-hsl),.25);padding-left:calc(12px / 2);padding-right:calc(12px / 2);transition:width ease-in-out,padding-left ease-in-out,padding-right ease-in-out,background-color var(--color-fade-speed) ease-in-out,border ease-in-out;transition-duration:.2s;z-index:1;flex:none;position:relative;pointer-events:none}.sidebar.is-collapsed .sidebar-gutter{background-color:transparent}.sidebar-left .sidebar-gutter{border-left:solid var(--divider-width) var(--divider-color);border-radius:0 var(--radius-l) var(--radius-l) 0}.sidebar-right .sidebar-gutter{border-right:solid var(--divider-width) var(--divider-color);margin-left:auto;border-radius:var(--radius-l) 0 0 var(--radius-l)}.sidebar-gutter.is-collapsed,body.loading .sidebar-gutter{height:100%;width:0!important;padding:var(--sidebar-margin);padding-left:0;padding-right:0}.sidebar-sizer{width:calc(100vw - var(--sidebar-width) - var(--content-width));height:100%;transition:width .2s ease-in-out,min-width .2s ease-in-out;min-width:var(--sidebar-width)}.sidebar.is-collapsed .sidebar-sizer,body.loading .sidebar-sizer{width:0;min-width:0}.sidebar-left .sidebar-sizer{float:left}.sidebar-right .sidebar-sizer{float:right}.sidebar-content-positioner{width:var(--sidebar-width);height:100%}.sidebar-left .sidebar-content-positioner{float:right}.sidebar-right .sidebar-content-positioner{float:left}.sidebar-content{width:var(--sidebar-width);line-height:var(--line-height-tight);display:flex;flex-direction:column;padding:var(--sidebar-margin);height:100%;transition:width ease-in-out,padding-left ease-in-out,padding-right ease-in-out;transition-duration:.2s}.sidebar-left .sidebar-content{float:left}.sidebar-right .sidebar-content{float:right}.sidebar-section-header{margin:0 0 1em 0;text-transform:uppercase;letter-spacing:.06em;font-weight:600}.clickable-icon.sidebar-collapse-icon{width:100%;background-color:transparent;height:75px;border-radius:12px;padding:13%;display:block;max-width:3em;max-height:3em;color:var(--icon-color-focused);pointer-events:all}.clickable-icon.sidebar-collapse-icon svg.svg-icon{width:100%;height:fit-content}body{transition:background-color var(--color-fade-speed) ease-in-out}.webpage-container{display:flex;flex-direction:row;height:100%;width:100%;position:fixed;align-items:stretch;justify-content:center}.document-container{opacity:0;flex-basis:var(--content-width);width:var(--content-width);height:100%;z-index:0;display:flex;flex-direction:column;align-items:center;padding:1em;max-width:100%;padding-right:0}@keyframes hide{from{opacity:1}to{opacity:0}}@keyframes show{from{opacity:0}to{opacity:1}}.document-container.hide{animation:hide .3s forwards ease-in-out}body:not(.loading) .document-container.show{opacity:0;animation:show .3s forwards ease-in-out;animation-delay:.2s}.floating-sidebars .document-container{position:absolute}.markdown-reading-view{display:none}.document-container>.markdown-preview-view{display:flex;padding-bottom:30em;align-items:flex-start;justify-content:center;overflow-x:hidden;overflow-y:scroll}.document-container>.markdown-preview-view>.markdown-preview-sizer{padding:1.5em;padding-bottom:50vh;width:100%;position:absolute;background-color:var(--background-primary);border-radius:var(--window-radius);max-width:var(--line-width);flex-basis:var(--line-width);transition:background-color var(--color-fade-speed) ease-in-out}.markdown-rendered img:not([width]),.view-content img:not([width]){max-width:100%;outline:0}.document-container>.view-content.embed{display:flex;padding:1em;height:100%;width:100%;align-items:center;justify-content:center}.document-container>.view-content.embed>*{max-width:100%;max-height:100%;object-fit:contain}.document-container>.view-content{overflow-x:scroll;contain:content;padding:0;margin:0;height:100%}.tree-container{position:relative;height:100%;width:auto;margin-top:3em;margin-bottom:0}.tree-container .tree-header{display:flex;flex-direction:row;align-items:center;position:absolute;top:-3em}.tree-container .tree-header .sidebar-section-header{margin:1em;margin-left:0}.tree-container:has(.tree-scroll-area:empty){display:none}.tree-container .tree-scroll-area{width:100%;height:100%;max-height:100%;overflow-y:scroll;padding-right:1em;border-radius:var(--radius-m);position:absolute}.tree-container .tree-item{display:flex;flex-direction:column;align-items:flex-start;padding:0}.tree-container .tree-item-children{padding:0;margin-left:0;border-left:none;width:100%}.tree-item-title *{padding:0;margin:0;overflow:visible}.tree-container .tree-item.mod-active>.tree-item-contents>.tree-item-link{color:var(--color-accent)}.tree-container .tree-item-contents{position:relative;display:flex;flex-direction:row;align-items:center;border-radius:.4em;color:var(--nav-item-color);width:100%;margin-left:var(--tree-horizontal-spacing)}.tree-container .tree-item-contents:active{color:var(--nav-item-color-active)}.tree-container .tree-item-link{width:100%;height:100%;transition:background-color .1s;border-radius:var(--radius-s);padding-left:calc(var(--tree-horizontal-spacing) * 2 + var(--collapse-arrow-size));padding-bottom:calc(var(--tree-vertical-spacing)/ 2);padding-top:calc(var(--tree-vertical-spacing)/ 2);color:var(--nav-item-color);text-decoration:none}.tree-container .mod-tree-file>.tree-item-contents>.tree-item-link{padding-left:calc(var(--tree-horizontal-spacing) * 2)}.tree-container .tree-item-icon.collapse-icon{margin-left:calc(0px - var(--collapse-arrow-size) - var(--tree-horizontal-spacing));position:absolute}.tree-container .tree-item.mod-tree-folder>.tree-item-contents>.tree-item-icon.collapse-icon{width:100%}.collapse-icon>svg{color:unset!important}.collapse-icon:hover{color:var(--nav-item-color-hover)}.tree-container .clickable-icon{width:3.2em;height:2.2em}.tree-container .tree-item.is-collapsed>.tree-item-contents>.tree-item-link>.tree-item-icon.collapse-icon>svg{transition:transform .1s ease-in-out;transform:rotate(-90deg)}.tree-container .tree-item-link:hover{cursor:pointer;color:var(--nav-item-color-hover);text-decoration:underline}.tree-container>.tree-scroll-area>* .tree-item{margin-left:calc(var(--tree-horizontal-spacing) * 2 + var(--collapse-arrow-size)/ 2);border-left:var(--nav-indentation-guide-width) solid var(--nav-indentation-guide-color)}.tree-container .tree-scroll-area>*>*>.tree-item{margin-left:calc(var(--tree-horizontal-spacing) + var(--collapse-arrow-size)/ 2)}.tree-container:not(.mod-nav-indicator) .tree-scroll-area>*>*>.tree-item{margin-left:0}.tree-container .tree-item.mod-active{border-left:var(--nav-indentation-guide-width) solid var(--color-accent)}.tree-container .tree-item:hover:not(.mod-active):not(.mod-collapsible):not(:has(.tree-item:hover)){border-left:var(--nav-indentation-guide-width) solid var(--nav-item-color-hover)}.tree-container .tree-item-contents:hover{background-color:var(--nav-item-background-hover)}.webpage-container .tree-container .tree-item:not(.mod-collapsible)>.tree-item-children>.tree-item,.webpage-container .tree-container:not(.mod-nav-indicator) .tree-item,.webpage-container .tree-container>.tree-scroll-area>.tree-item{border-left:none!important}.webpage-container .tree-container .tree-item:not(.mod-collapsible)>.tree-item-children>.tree-item>.tree-item-contents,.webpage-container .tree-container:not(.mod-nav-indicator) .tree-item .tree-item-contents,.webpage-container .tree-container>.tree-scroll-area>.tree-item>.tree-item-contents{margin-left:0!important;width:calc(100% + var(--tree-horizontal-spacing))}.tree-container.outline-tree .tree-item[data-depth='1']>.tree-item-contents>.tree-item-link{font-weight:900;font-size:1.1em;margin-left:0;padding-left:1em}.heading-wrapper{transition:height ease-in-out,margin-bottom ease-in-out;transition-duration:.2s;display:block}html>body>.webpage-container>.document-container .heading-wrapper,html>body>.webpage-container>.document-container>.markdown-preview-view>.markdown-preview-sizer>*{margin-inline:0!important;margin:0!important;padding:0!important;width:100%;max-width:100%}body>.webpage-container>.document-container .heading-wrapper>.heading{margin:0;margin-inline:0;margin-block:0}.markdown-preview-sizer>.heading-wrapper>h1{margin-top:var(--p-spacing)!important}body>.webpage-container>.document-container .heading-children>div:not(.heading-wrapper):first-child>*{margin-top:0}.heading-children{transition:height ease-in-out,margin-bottom ease-in-out;transition-duration:.2s;display:inline-block;width:100%;padding-top:1em}.heading-children.is-collapsed{padding-top:0}.heading-wrapper.is-animating>.heading-children,.heading-wrapper.is-collapsed>.heading-children{overflow:hidden}.heading-wrapper>.heading>.heading-after{display:none}.heading-wrapper.is-collapsed>.heading>.heading-after{display:inline-block;margin-left:.3em;opacity:.4;font-size:1em;cursor:auto;user-select:none}.heading-wrapper.is-hidden>*{display:none}.heading-wrapper.is-hidden{visibility:hidden}.collapse-icon svg.svg-icon{color:var(--nav-collapse-icon-color);stroke-width:4px;width:var(--collapse-arrow-size);height:var(--collapse-arrow-size);transition:transform .1s ease-in-out 0s;min-width:10px;min-height:10px}div.is-collapsed>*>.heading-collapse-indicator.collapse-icon>svg{transition:transform .1s ease-in-out;transform:rotate(-90deg)}body .webpage-container>.document-container .heading-collapse-indicator,body .webpage-container>.document-container .heading-collapse-indicator>svg{position:relative!important}a{overflow-wrap:anywhere}*{overflow-wrap:break-word}.theme-toggle-container{--toggle-width:3.5em;--toggle-height:1.75em;--border-radius:calc(var(--toggle-height) / 2);--handle-width:calc(var(--toggle-height) * 0.65);--handle-radius:calc(var(--handle-width) / 2);--handle-margin:calc((var(--toggle-height) / 2.0) - var(--handle-radius));--handle-translation:calc(var(--toggle-width) - var(--handle-width) - (var(--handle-margin) * 2));display:inline-block;cursor:pointer;margin:10px}.clickable-icon,.sidebar-section-header{transition:color var(--color-fade-speed) ease-in-out}@keyframes toggle-slide-right{0%{width:var(--handle-width);transform:translateX(0)}50%{width:calc(var(--toggle-width) * .5)}90%{width:var(--handle-width)}100%{transform:translateX(var(--handle-translation))}}@keyframes toggle-slide-left{0%{width:var(--handle-width);transform:translateX(calc(var(--handle-translation) - ((var(--toggle-width) * .33) - var(--handle-width))))}70%{width:calc(var(--toggle-width) * .5)}100%{width:var(--handle-width);transform:translateX(0)}}@keyframes toggle-expand-right{0%{width:var(--handle-width)}100%{width:calc(var(--toggle-width) * .33)}}@keyframes toggle-expand-left{0%{width:var(--handle-width);transform:translateX(var(--handle-translation))}100%{width:calc(var(--toggle-width) * .33);transform:translateX(calc(var(--handle-translation) - ((var(--toggle-width) * .33) - var(--handle-width))))}}@keyframes toggle-contract{0%{width:calc(var(--toggle-width) * .33)}100%{width:var(--handle-width)}}.theme-toggle-input{display:none;z-index:1000}.toggle-background{position:relative;width:var(--toggle-width);height:var(--toggle-height);border-radius:var(--border-radius);background-color:var(--background-modifier-border);transition:background-color var(--color-fade-speed);z-index:1000}.toggle-background::before{content:"";position:absolute;left:var(--handle-margin);top:var(--handle-margin);height:var(--handle-width);width:var(--handle-width);border-radius:var(--handle-radius);background-color:var(--text-normal);box-shadow:inset 0 1px 1px rgba(0,0,0,.2);animation:toggle-slide-left .2s ease-in-out normal both;z-index:1000}.theme-toggle-input:checked~.toggle-background::before{animation:toggle-slide-right .2s ease-in-out normal both}.theme-toggle-input:active~.toggle-background::before{animation:toggle-expand-right .2s ease-in-out normal both}.theme-toggle-input:active:checked~.toggle-background::before{animation:toggle-expand-left .2s ease-in-out normal both}.toggle-background::after{content:"";position:absolute;right:var(--handle-margin);top:calc(var(--handle-margin));height:var(--handle-width);width:var(--handle-width);transition:transform .3s;background:url('https://api.iconify.design/lucide/moon.svg?color=white') no-repeat center center;transform:scale(.9)}.theme-toggle-input:checked~.toggle-background::after{transform:translateX(calc(var(--handle-translation) * -1)) scale(.9);background:url('https://api.iconify.design/lucide/sun.svg') no-repeat center center}#graph-canvas{width:100%;height:100%;aspect-ratio:1/1}.graph-view-container.expanded{position:fixed;width:60%;height:90%;right:20%;top:5%;background-color:var(--background-secondary);z-index:100}body.is-phone .graph-view-container.expanded{position:fixed;width:94%;height:94%;right:3%;top:3%}body.is-tablet .graph-view-container.expanded{position:fixed;width:90%;right:5%}body.is-small-screen .graph-view-container.expanded{position:fixed;width:80%;right:10%}.graph-view-container{position:relative;width:100%;aspect-ratio:1/1;display:flex;transition:background-color var(--color-fade-speed) ease-in-out;touch-action:none;border:1px solid var(--modal-border-color);border-radius:var(--modal-radius)}.graph-icon{cursor:pointer;color:var(--text-muted)}.graph-view-container .graph-icon>svg{width:24px;height:24px;background-color:var(--color-base-00);outline-width:6px;outline-color:var(--color-base-00);outline-offset:-1px;outline-style:solid;border-radius:100px;margin:10px}.graph-view-placeholder{padding:0;width:auto;aspect-ratio:1/1;position:relative;flex:none}.graph-view-placeholder:has(.expanded){border-radius:var(--modal-radius);border:1px solid var(--modal-border-color)}.scale-down{transition:transform .2s ease-in-out;transform:scale(.9)}.scale-up{transition:transform .2s ease-in-out;transform:scale(1)}.graph-expand{position:absolute;top:5px;right:5px}.canvas{translate:0 0;scale:1 1;will-change:translate,scale}.canvas-controls{display:none;cursor:default!important}.canvas-card-menu{display:none;cursor:default!important}.canvas-node-content-blocker{pointer-events:none}body.is-phone .sidebar{font-size:1.06em}body.is-phone{--collapse-arrow-size:0.5em;--tree-vertical-spacing:0.8em;--tree-horizontal-spacing:0.45em}body.post-load .sidebar-sizer{transition-delay:0.5s!important;transition-duration:.4s!important}body.post-load .sidebar-gutter{transition-delay:0.8s!important;transition-duration:.4s!important}.loading-icon{--width:80px;--height:80px;display:inline-block;position:fixed;left:calc(50% - var(--width)/ 2);top:calc(50% - var(--height)/ 2);width:var(--width);height:var(--height);opacity:0;transition:opacity .5s ease-in-out;pointer-events:none}.loading-icon.shown{opacity:1}.loading-icon div{position:absolute;top:33px;width:13px;height:13px;border-radius:50%;background:var(--color-accent);animation-timing-function:cubic-bezier(0,1,1,0)}.loading-icon div:first-child{left:8px;animation:lds-ellipsis1 .6s infinite}.loading-icon div:nth-child(2){left:8px;animation:lds-ellipsis2 .6s infinite}.loading-icon div:nth-child(3){left:32px;animation:lds-ellipsis2 .6s infinite}.loading-icon div:nth-child(4){left:56px;animation:lds-ellipsis3 .6s infinite}.loading-icon:not(.shown) div{animation-play-state:paused}@keyframes lds-ellipsis1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes lds-ellipsis3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes lds-ellipsis2{0%{transform:translate(0,0)}100%{transform:translate(24px,0)}}@media print{.outline-container,.sidebar,.theme-toggle-container,.theme-toggle-container-inline,.theme-toggle-input,.toggle-background{display:none!important}}body.resizing :not(:is(.sidebar-sizer,.sidebar-gutter)){transition:none!important}.document-container .kanban-plugin{position:absolute;padding:0;margin:0;height:100%}.document-container .kanban-plugin{font-family:var(--font-text, var(--default-font));font-size:.875rem;line-height:var(--line-height-tight);width:unset;overflow-y:unset;overflow-wrap:unset;color:unset;user-select:unset;-webkit-user-select:unset}.document-container .kanban-plugin__item-button-wrapper,.kanban-plugin__item-postfix-button.clickable-icon,.kanban-plugin__lane-grip,.kanban-plugin__lane-settings-button.clickable-icon{display:none}.excalidraw-plugin rect,.excalidraw-svg rect{fill:transparent}body.theme-dark .excalidraw-plugin svg.dark,body.theme-dark .excalidraw-svg svg.dark,body.theme-light .excalidraw-plugin svg.light,body.theme-light .excalidraw-svg svg.light{filter:invert(93%) hue-rotate(180deg)}.excalidraw-plugin>svg{width:100%;height:100%}.excalidraw-plugin{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;width:100%;padding:10px}.columnParent{display:flex;padding:15px 20px;flex-wrap:wrap;gap:20px}.columnParent{white-space:normal}.columnChild{flex-grow:1;flex-basis:0px}.view-content:has(.mm-mindmap){overflow-y:none}.view-content .mm-mindmap{transform:scale(1);translate:-4000px -4000px;top:70%;left:50%;position:absolute;overflow:hidden;width:100vw} +:root{--obsidian-columns-gap:20px;--obsidian-columns-padding:15px 20px;--obsidian-columns-min-width:100px;--obsidian-columns-def-span:1}div[data-callout=col].callout,div[data-callout^=col-md].callout{background-color:rgba(0,0,0,0);padding:0;border-style:none}.columnParent,div[data-callout=col].callout>div.callout-content{display:flex!important;padding:var(--obsidian-columns-padding);flex-wrap:wrap;gap:var(--obsidian-columns-gap);white-space:normal}div[data-callout=col].callout>div.callout-title,div[data-callout^=col-md].callout>div.callout-title{display:none}.cm-preview-code-block .admonition-content .columnParent p{white-space:pre-wrap}.columnChild,div[data-callout=col].callout>div.callout-content>*{flex-grow:var(--obsidian-columns-def-span);flex-basis:calc(var(--obsidian-columns-min-width) * var(--obsidian-columns-def-span));width:calc(var(--obsidian-columns-min-width) * var(--obsidian-columns-def-span))}div[data-callout=col].callout>div.callout-content>div[data-callout^=col-md].callout{flex-grow:var(--obsidian-columns-custom-span);flex-basis:calc(var(--obsidian-columns-min-width) * var(--obsidian-columns-custom-span));width:calc(var(--obsidian-columns-min-width) * var(--obsidian-columns-custom-span));background-color:rgba(0,0,0,0);padding:0;border-style:none}div[data-callout=col].callout>div.callout-content>div[data-callout$="-0.5"].callout{--obsidian-columns-custom-span:0.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-1"].callout,div[data-callout=col].callout>div.callout-content>div[data-callout=col-md].callout{--obsidian-columns-custom-span:1}div[data-callout=col].callout>div.callout-content>div[data-callout$="-1.5"].callout{--obsidian-columns-custom-span:1.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-2"].callout{--obsidian-columns-custom-span:2}div[data-callout=col].callout>div.callout-content>div[data-callout$="-2.5"].callout{--obsidian-columns-custom-span:2.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-3"].callout{--obsidian-columns-custom-span:3}div[data-callout=col].callout>div.callout-content>div[data-callout$="-3.5"].callout{--obsidian-columns-custom-span:3.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-4"].callout{--obsidian-columns-custom-span:4}div[data-callout=col].callout>div.callout-content>div[data-callout$="-4.5"].callout{--obsidian-columns-custom-span:4.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-5"].callout{--obsidian-columns-custom-span:5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-5.5"].callout{--obsidian-columns-custom-span:5.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-6"].callout{--obsidian-columns-custom-span:6}div[data-callout=col].callout>div.callout-content>div[data-callout$="-6.5"].callout{--obsidian-columns-custom-span:6.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-7"].callout{--obsidian-columns-custom-span:7}div[data-callout=col].callout>div.callout-content>div[data-callout$="-7.5"].callout{--obsidian-columns-custom-span:7.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-8"].callout{--obsidian-columns-custom-span:8}div[data-callout=col].callout>div.callout-content>div[data-callout$="-8.5"].callout{--obsidian-columns-custom-span:8.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-9"].callout{--obsidian-columns-custom-span:9}div[data-callout=col].callout>div.callout-content>div[data-callout$="-9.5"].callout{--obsidian-columns-custom-span:9.5}div[data-callout=col].callout>div.callout-content>div[data-callout$="-10"].callout{--obsidian-columns-custom-span:10}.math-booster-version-2-release-note-modal{--table-border-width:2px}.editor-suggest-setting-indented-heading{margin-left:var(--size-4-1)}.editor-suggest-setting-indented-heading+.setting-item,.editor-suggest-setting-indented-heading+.setting-item+.setting-item{margin-left:var(--size-4-3)}.math-booster-modal-top{padding:var(--size-4-3)}.math-booster-modal-top .setting-item{border-top:none;border-bottom:1px solid var(--background-modifier-border)}.math-booster-setting-item-description{padding-bottom:.75em}.math-booster-search-item-description{color:var(--text-faint)}.math-booster-backlink-modal{width:var(--file-line-width)}.math-booster-backlink-preview{border:var(--border-width) solid var(--background-modifier-border);border-radius:var(--radius-s);padding:var(--size-4-6)}.math-booster-begin-proof{padding-right:10px;font-family:CMU Serif,Times,Noto Serif JP;font-weight:700}.math-booster-begin-proof-en{font-style:italic}.math-booster-end-proof{float:right}.math-booster-add-profile{display:flex;flex-direction:row;justify-content:space-between}.math-booster-add-profile>input{width:200px}.math-booster-button-container{display:flex;flex-direction:row;justify-content:flex-end;gap:var(--size-4-2);padding:var(--size-4-2)}.math-booster-preview{cursor:text}.HyperMD-quote.cm-line.math-booster-preview-container{text-indent:-15px;padding-inline-start:19px}.cm-embed-block .math-booster-preview-edit-button{padding:var(--size-2-2) var(--size-2-3);position:absolute;right:var(--size-2-2);top:var(--size-2-2);opacity:0;display:flex;border-radius:var(--radius-s)}.cm-embed-block:hover .math-booster-preview-edit-button{transition:0s;opacity:1}.math-booster-preview-enabled .HyperMD-quote.cm-line .math.math-block.cm-embed-block:has(> mjx-container.MathJax[display=true]:not(.math-booster-preview)){display:none}.theorem-callout{position:relative}.theorem-callout-setting-button{padding-bottom:var(--size-2-2);padding-right:var(--size-2-3);position:absolute;right:var(--size-2-2);bottom:var(--size-2-2);opacity:0}.theorem-callout:hover .theorem-callout-setting-button{transition:0s;opacity:1}.theorem-callout-font-family-inherit{font-family:inherit!important}.math-booster-label-form,.math-booster-title-form{width:300px}.math-booster-dependency-validation{color:#fff;display:inline-block;border-radius:1em;margin-right:var(--size-3);cursor:default;pointer-events:none}.math-booster-dependency-validation svg{width:16px!important;height:16px!important}.math-booster-dependency-validation.valid{background-color:#7dc535;visibility:visible}.theme-dark .math-booster-dependency-validation.valid{background-color:#588b24}.math-booster-dependency-validation.invalid{background-color:#ea5555;visibility:visible}:has(> .theorem-callout-framed) .theorem-callout{--callout-color:var(--text-normal);background-color:rgba(0,0,0,0);border:solid var(--border-width);border-radius:var(--size-2-3)}:has(> .theorem-callout-framed) .theorem-callout .callout-icon{display:none}:has(> .theorem-callout-framed) .theorem-callout .callout-title-inner{font-family:CMU Serif,Times,Noto Sans JP;font-weight:bolder}:has(> .theorem-callout-framed) .theorem-callout .callout-title-inner .theorem-callout-subtitle{font-weight:400}:has(> .theorem-callout-framed) .theorem-callout .callout-content{font-family:CMU Serif,Times,Noto Serif JP}:has(> .theorem-callout-framed) :not(.theorem-callout-axiom):not(.theorem-callout-definition):not(.theorem-callout-remark).theorem-callout-en .callout-content{font-style:italic}:has(> .theorem-callout-plain) .theorem-callout{--callout-color:var(--text-normal);background-color:rgba(0,0,0,0);padding-left:0;padding-right:0;border:none;box-shadow:none}:has(> .theorem-callout-plain) .theorem-callout .callout-icon{display:none}:has(> .theorem-callout-plain) .theorem-callout .callout-title-inner{font-family:CMU Serif,Times,Noto Sans JP;font-weight:bolder}:has(> .theorem-callout-plain) .theorem-callout .callout-title-inner .theorem-callout-subtitle{font-weight:400}:has(> .theorem-callout-plain) .theorem-callout .callout-content{font-family:CMU Serif,Times,Noto Serif JP}:has(> .theorem-callout-plain) :not(.theorem-callout-axiom):not(.theorem-callout-definition):not(.theorem-callout-remark).theorem-callout-en .callout-content{font-style:italic}:has(> .theorem-callout-mathwiki) .theorem-callout{--callout-color:248,248,255;font-family:CMU Serif,Times,Noto Serif JP}:has(> .theorem-callout-mathwiki) .theorem-callout .callout-title-inner{padding-left:5px}:has(> .theorem-callout-mathwiki) .theorem-callout-subtitle{font-weight:400}:has(> .theorem-callout-mathwiki) .theorem-callout-en .callout-content{font-style:italic}:has(> .theorem-callout-mathwiki) .theorem-callout-axiom{--callout-icon:''}:has(> .theorem-callout-mathwiki) .theorem-callout-definition{--callout-icon:''}:has(> .theorem-callout-mathwiki) .theorem-callout-theorem{--callout-icon:''}:has(> .theorem-callout-mathwiki) .theorem-callout-proposition{--callout-icon:''}:has(> .theorem-callout-mathwiki) .theorem-callout-example{--callout-icon:''}:has(> .theorem-callout-vivid) .theorem-callout{--callout-color:238,15,149;border-top:none;border-bottom:none;border-left:var(--size-2-2) solid rgb(var(--callout-color));border-right:none;border-radius:0;box-shadow:none;padding:0;font-family:CMU Serif,Times,Noto Serif JP}:has(> .theorem-callout-vivid) .theorem-callout .callout-title{padding:var(--size-2-3);padding-left:var(--size-4-3)}:has(> .theorem-callout-vivid) .theorem-callout .callout-icon{display:none}:has(> .theorem-callout-vivid) .theorem-callout .callout-title-inner{font-family:Inter;font-weight:400;color:rgb(var(--callout-color))}:has(> .theorem-callout-vivid) .theorem-callout-subtitle{font-weight:lighter}:has(> .theorem-callout-vivid) .theorem-callout .callout-content{background-color:var(--background-primary);padding:1px 20px 2px 20px} diff --git a/docs/lib/styles/snippets.css b/docs/lib/styles/snippets.css new file mode 100644 index 0000000..64b180e --- /dev/null +++ b/docs/lib/styles/snippets.css @@ -0,0 +1 @@ +.cm-blockid{opacity:.2}.cm-active .cm-blockid{opacity:.7}.markdown-source-view.mod-cm6 div.edit-block-button{display:none}.better-search-views-file-match{font-size:var(--nav-item-size)!important}.theme-dark{filter:brightness(1.3)}.HyperMD-codeblock,code{line-height:1.3!important}.HyperMD-footnote,li[data-footnote-id]{color:var(--text-muted)}.cm-comment{font-family:var(--font-monospace)!important;color:var(--code-comment)!important}.theorem-callout{--callout-color:248,248,255;background-color:rgb(255,255,255,.03);border:solid;border-radius:6px}.theorem-callout .callout-icon{display:none}.theorem-callout .callout-title-inner{font-style:bold}.theorem-callout-subtitle{font-weight:400}.webpage-container{--h1-size:1.8em;--h2-size:1.5em;--h3-size:1.324em;--h4-size:1.266em;--h5-size:1.266em;--h6-size:1em}.callout .callout-content>*{margin-block-start:0.7rem;margin-block-end:0.7rem}.file-tree>.tree-scroll-area{top:3em} \ No newline at end of file diff --git a/docs/lib/styles/theme.css b/docs/lib/styles/theme.css new file mode 100644 index 0000000..64bf972 --- /dev/null +++ b/docs/lib/styles/theme.css @@ -0,0 +1 @@ +body{--font-editor-theme:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,Ubuntu,sans-serif;--font-editor:var(--font-editor-override),var(--font-text-override),var(--font-editor-theme)}body{--blockquote-style:normal;--blockquote-color:var(--text-muted);--blockquote-border-thickness:1px;--blockquote-border-color:var(--quote-opening-modifier);--embed-block-shadow-hover:none;--font-ui-smaller:11px;--normal-weight:400;--bold-weight:600;--link-weight:inherit;--inline-title-margin-bottom:1rem;--h1-size:1.125em;--h2-size:1.05em;--h3-size:1em;--h4-size:0.90em;--h5-size:0.85em;--h6-size:0.85em;--h1-weight:600;--h2-weight:600;--h3-weight:500;--h4-weight:500;--h5-weight:500;--h6-weight:400;--h1-variant:normal;--h2-variant:normal;--h3-variant:normal;--h4-variant:normal;--h5-variant:small-caps;--h6-variant:small-caps;--h1-style:normal;--h2-style:normal;--h3-style:normal;--h4-style:normal;--h5-style:normal;--h6-style:normal;--line-width:40rem;--line-height:1.5;--max-width:88%;--max-col-width:18em;--icon-muted:0.5;--nested-padding:1.1em;--folding-offset:32px;--list-edit-offset:0.5em;--list-indent:2em;--list-spacing:0.075em;--input-height:32px;--header-height:40px;--metadata-label-width:9rem;--metadata-label-font-size:var(--font-adaptive-small);--metadata-input-font-size:var(--font-adaptive-small);--mobile-left-sidebar-width:280pt;--mobile-right-sidebar-width:240pt;--top-left-padding-y:0px;--image-muted:0.7;--image-radius:4px;--heading-spacing:2em;--p-spacing:1.75rem;--border-width:1px;--table-border-width:var(--border-width);--file-margins:var(--size-4-2) var(--size-4-12)}.mod-macos{--top-left-padding-y:24px}.is-phone{--metadata-label-font-size:var(--font-adaptive-smaller);--metadata-input-font-size:var(--font-adaptive-smaller)}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.is-phone{--border-width:0.75px}}body{--base-h:0;--base-s:0%;--base-l:96%;--accent-h:201;--accent-s:17%;--accent-l:50%}.theme-dark,.theme-light{--color-red-rgb:208,66,85;--color-orange-rgb:213,118,63;--color-yellow-rgb:229,181,103;--color-green-rgb:168,195,115;--color-cyan-rgb:115,187,178;--color-blue-rgb:108,153,187;--color-purple-rgb:158,134,200;--color-pink-rgb:176,82,121;--color-red:#d04255;--color-orange:#d5763f;--color-yellow:#e5b567;--color-green:#a8c373;--color-cyan:#73bbb2;--color-blue:#6c99bb;--color-purple:#9e86c8;--color-pink:#b05279}.theme-light,.theme-light.minimal-default-light,body .excalidraw{--bg1:white;--bg2:hsl(var(--base-h), var(--base-s), var(--base-l));--bg3:hsla(var(--base-h), var(--base-s), calc(var(--base-l) - 50%), 0.12);--ui1:hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 6%));--ui2:hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 12%));--ui3:hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 20%));--tx1:hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 90%));--tx2:hsl(var(--base-h), calc(var(--base-s) - 20%), calc(var(--base-l) - 45%));--tx3:hsl(var(--base-h), calc(var(--base-s) - 10%), calc(var(--base-l) - 25%));--tx4:hsl(var(--base-h), calc(var(--base-s) - 10%), calc(var(--base-l) - 60%));--ax1:hsl(var(--accent-h), var(--accent-s), var(--accent-l));--ax2:hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) - 8%));--ax3:hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) + 6%));--hl1:hsla(var(--accent-h), 50%, calc(var(--base-l) - 20%), 30%);--hl2:rgba(255, 225, 0, 0.5);--sp1:white}.excalidraw.theme--dark,.theme-dark,.theme-dark.minimal-default-dark,.theme-light.minimal-light-contrast .titlebar,.theme-light.minimal-light-contrast.minimal-status-off .status-bar{--accent-l:60%;--base-l:15%;--bg1:hsl(var(--base-h), var(--base-s), var(--base-l));--bg2:hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 2%));--bg3:hsla(var(--base-h), var(--base-s), calc(var(--base-l) + 40%), 0.12);--ui1:hsl(var(--base-h), var(--base-s), calc(var(--base-l) + 6%));--ui2:hsl(var(--base-h), var(--base-s), calc(var(--base-l) + 12%));--ui3:hsl(var(--base-h), var(--base-s), calc(var(--base-l) + 20%));--tx1:hsl(var(--base-h), calc(var(--base-s) - 10%), calc(var(--base-l) + 67%));--tx2:hsl(var(--base-h), calc(var(--base-s) - 20%), calc(var(--base-l) + 45%));--tx3:hsl(var(--base-h), calc(var(--base-s) - 10%), calc(var(--base-l) + 20%));--tx4:hsl(var(--base-h), calc(var(--base-s) - 10%), calc(var(--base-l) + 50%));--ax1:hsl(var(--accent-h), var(--accent-s), var(--accent-l));--ax2:hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) + 8%));--ax3:hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) - 5%));--hl1:hsla(var(--accent-h), 50%, 40%, 30%);--hl2:rgba(255, 177, 80, 0.3);--sp1:white}.theme-light.minimal-light-white{--background-primary:white;--background-secondary:white;--background-secondary-alt:white;--ribbon-background:white;--titlebar-background:white;--bg1:white}.theme-dark.minimal-dark-black{--base-d:0%;--titlebar-background:black;--background-primary:black;--background-secondary:black;--background-secondary-alt:black;--ribbon-background:black;--background-modifier-hover:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 10%));--tx1:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 75%));--tx2:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 50%));--tx3:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 25%));--ui1:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 12%));--ui2:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 20%));--ui3:hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 30%))}.theme-light{--mono100:black;--mono0:white}.theme-dark{--mono100:white;--mono0:black}.theme-dark,.theme-light,.theme-light.minimal-light-contrast .titlebar,.theme-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left,.theme-light.minimal-light-contrast.minimal-status-off .status-bar{--background-modifier-accent:var(--ax3);--background-modifier-border-focus:var(--ui3);--background-modifier-border-hover:var(--ui2);--background-modifier-border:var(--ui1);--background-modifier-form-field-highlighted:var(--bg1);--background-modifier-form-field:var(--bg1);--background-modifier-success:var(--color-green);--background-modifier-hover:var(--bg3);--background-modifier-active-hover:var(--bg3);--background-primary:var(--bg1);--background-primary-alt:var(--bg2);--background-secondary:var(--bg2);--background-secondary-alt:var(--bg1);--background-table-rows:var(--bg2);--checkbox-color:var(--ax3);--code-normal:var(--tx1);--divider-color:var(--ui1);--frame-divider-color:var(--ui1);--icon-color-active:var(--tx1);--icon-color-focused:var(--tx1);--icon-color-hover:var(--tx2);--icon-color:var(--tx2);--icon-hex:var(--mono0);--interactive-accent-hover:var(--ax1);--interactive-accent:var(--ax3);--interactive-hover:var(--ui1);--list-marker-color:var(--tx3);--modal-border-color:var(--ui2);--nav-item-background-active:var(--bg3);--nav-item-background-hover:var(--bg3);--nav-item-color:var(--tx2);--nav-item-color-active:var(--tx1);--nav-item-color-hover:var(--tx1);--nav-collapse-icon-color:var(--tx2);--nav-collapse-icon-color-collapsed:var(--tx2);--nav-indentation-guide-color:var(--ui1);--prompt-border-color:var(--ui3);--quote-opening-modifier:var(--ui2);--ribbon-background:var(--bg2);--scrollbar-active-thumb-bg:var(--ui3);--scrollbar-bg:transparent;--scrollbar-thumb-bg:var(--ui1);--search-result-background:var(--bg1);--tab-text-color-focused-active:var(--tx1);--tab-outline-color:var(--ui1);--text-accent-hover:var(--ax2);--text-accent:var(--ax1);--text-blockquote:var(--tx2);--text-bold:var(--tx1);--text-code:var(--tx4);--text-error:var(--color-red);--text-faint:var(--tx3);--text-highlight-bg:var(--hl2);--text-italic:var(--tx1);--text-muted:var(--tx2);--text-normal:var(--tx1);--text-on-accent:var(--sp1);--text-selection:var(--hl1);--title-color-inactive:var(--tx2);--title-color:var(--tx1);--titlebar-background:var(--bg2);--titlebar-background-focused:var(--bg2);--titlebar-text-color-focused:var(--tx1);--workspace-background-translucent:hsla(var(--base-h), var(--base-s), var(--base-l), 0.7)}.theme-dark .view-actions,.theme-light .view-actions{--icon-color-active:var(--ax1)}.theme-light.minimal-light-contrast{--workspace-background-translucent:rgba(0, 0, 0, 0.6)}.theme-light.minimal-light-contrast .theme-dark{--tab-container-background:var(--bg2);--ribbon-background-collapsed:var(--bg2)}.theme-light{--interactive-normal:var(--bg1);--interactive-accent-rgb:220,220,220;--active-line-bg:rgba(0, 0, 0, 0.035);--background-modifier-cover:hsla(var(--base-h), calc(var(--base-s) - 70%), calc(var(--base-l) - 20%), 0.5);--text-highlight-bg-active:rgba(0, 0, 0, 0.1);--background-modifier-error:rgba(255, 0, 0, 0.14);--background-modifier-error-hover:rgba(255, 0, 0, 0.08);--shadow-color:rgba(0, 0, 0, 0.1);--btn-shadow-color:rgba(0, 0, 0, 0.05)}.theme-dark{--interactive-normal:var(--bg3);--interactive-accent-rgb:66,66,66;--active-line-bg:rgba(255, 255, 255, 0.04);--background-modifier-cover:hsla(var(--base-h), var(--base-s), calc(var(--base-l) - 12%), 0.5);--text-highlight-bg-active:rgba(255, 255, 255, 0.1);--background-modifier-error:rgba(255, 20, 20, 0.12);--background-modifier-error-hover:rgba(255, 20, 20, 0.18);--background-modifier-box-shadow:rgba(0, 0, 0, 0.3);--shadow-color:rgba(0, 0, 0, 0.3);--btn-shadow-color:rgba(0, 0, 0, 0.2)}.theme-light.minimal-light-white{--background-table-rows:var(--bg2)}.theme-light.minimal-light-tonal{--background-primary:var(--bg2);--background-primary-alt:var(--bg3);--background-table-rows:var(--bg3)}.theme-dark.minimal-dark-tonal{--ribbon-background:var(--bg1);--background-secondary:var(--bg1);--background-table-rows:var(--bg3)}.theme-dark.minimal-dark-black{--background-primary-alt:var(--bg3);--background-table-rows:var(--bg3);--modal-border:var(--ui2);--active-line-bg:rgba(255, 255, 255, 0.085);--background-modifier-form-field:var(--bg3);--background-modifier-cover:hsla(var(--base-h), var(--base-s), calc(var(--base-d) + 8%), 0.9);--background-modifier-box-shadow:rgba(0, 0, 0, 1)}body{--font-adaptive-normal:var(--font-text-size, var(--editor-font-size));--font-adaptive-small:calc(var(--font-ui-small) * 1.07);--font-adaptive-smaller:var(--font-ui-small);--font-adaptive-smallest:var(--font-ui-smaller);--line-width-wide:calc(var(--line-width) + 12.5%);--font-code:calc(var(--font-adaptive-normal) * 0.9);--table-text-size:calc(var(--font-adaptive-normal) * 0.875)}.minimal-dev-block-width .mod-root .workspace-leaf-content:after{display:flex;align-items:flex-end;content:"\00a0pane\00a0";font-size:12px;color:gray;font-family:var(--font-monospace);width:100%;max-width:100%;height:100vh;top:0;z-index:999;position:fixed;pointer-events:none}.minimal-dev-block-width.minimal-readable .mod-root .view-header:after{display:flex;align-items:flex-end;color:green;font-size:12px;font-family:var(--font-monospace);content:" ";width:var(--folding-offset);height:100vh;border-left:1px solid green;border-right:1px solid green;background-color:rgba(0,128,0,.1);top:0;left:max(calc(50% - var(--line-width)/ 2 - 1px),calc(50% - var(--max-width)/ 2 - 1px));z-index:999;position:fixed;pointer-events:none}.minimal-dev-block-width.minimal-readable-off .mod-root .view-header:after{display:flex;align-items:flex-end;color:green;font-size:12px;font-family:var(--font-monospace);content:" ";width:var(--folding-offset);height:100vh;border-left:1px solid green;border-right:1px solid green;background-color:rgba(0,128,0,.1);top:0;left:calc(50% - var(--max-width)/ 2 - 1px);z-index:999;position:fixed;pointer-events:none}.minimal-dev-block-width .mod-root .view-content:before{display:flex;align-items:flex-end;content:"\00a0max\00a0";font-size:12px;color:red;width:var(--max-width);height:100vh;border-left:1px solid red;border-right:1px solid red;top:0;left:50%;transform:translate(-50%,0);z-index:999;position:fixed;pointer-events:none}.minimal-dev-block-width.minimal-readable .mod-root .view-header:before{display:flex;align-items:flex-end;content:"\00a0wide\00a0";font-size:12px;color:orange;font-family:var(--font-monospace);width:var(--line-width-wide);max-width:var(--max-width);height:100vh;border-left:1px solid orange;border-right:1px solid orange;background-color:rgba(255,165,0,.05);top:0;left:50%;transform:translate(-50%,0);z-index:999;position:fixed;pointer-events:none}.minimal-dev-block-width.minimal-readable .mod-root .view-content:after{display:flex;align-items:flex-end;color:#00f;font-size:12px;font-family:var(--font-monospace);content:"\00a0normal";width:var(--line-width);max-width:var(--max-width);height:100vh;border-left:1px solid #00f;border-right:1px solid #00f;background-color:rgba(0,0,255,.08);top:0;left:50%;transform:translate(-50%,0);z-index:999;position:fixed;pointer-events:none}.CodeMirror-wrap>div>textarea{opacity:0}.markdown-source-view.mod-cm6 hr{border-width:2px}.mod-cm6 .cm-editor .cm-line{padding-left:0;padding-right:0}.cm-editor .cm-content{padding-top:.5em}.markdown-source-view{color:var(--text-normal)}.markdown-source-view.mod-cm6 .cm-sizer{display:block}.markdown-source-view.mod-cm6 .cm-scroller{padding-left:0;padding-right:0}.cm-s-obsidian .cm-line.HyperMD-header{padding-top:calc(var(--p-spacing)/ 2)}.markdown-rendered .mod-header+div>*{margin-block-start:0}body :not(.canvas-node) .markdown-source-view.mod-cm6 .cm-gutters{position:absolute!important;z-index:0;margin-inline-end:0}body :not(.canvas-node) .markdown-source-view.mod-cm6.is-rtl .cm-gutters{right:0}body{--line-number-color:var(--text-faint);--line-number-color-active:var(--text-muted)}.markdown-source-view.mod-cm6 .cm-gutters{color:var(--line-number-color)!important}.markdown-source-view.mod-cm6 .cm-editor .cm-gutterElement.cm-active .cm-heading-marker,.markdown-source-view.mod-cm6 .cm-editor .cm-lineNumbers .cm-gutterElement.cm-active{color:var(--line-number-color-active)}.cm-editor .cm-lineNumbers{background-color:var(--gutter-background)}.cm-editor .cm-lineNumbers .cm-gutterElement{min-width:var(--folding-offset);padding-inline-end:0.5em}.is-rtl .cm-editor .cm-lineNumbers .cm-gutterElement{text-align:left}@media (max-width:400pt){.cm-editor .cm-lineNumbers .cm-gutterElement{padding-inline-end:4px;padding-inline-start:8px}}.cm-editor .cm-lineNumbers .cm-gutterElement{font-variant-numeric:tabular-nums}.cm-editor .cm-gutterElement.cm-active .cm-heading-marker,.cm-editor .cm-lineNumbers .cm-gutterElement.cm-active{color:var(--text-muted)}.markdown-source-view.mod-cm6 .edit-block-button{cursor:var(--cursor);color:var(--text-faint);background-color:var(--background-primary);top:0;opacity:0;transition:opacity .2s;padding:4px 4px 4px 9px}.markdown-source-view.mod-cm6 .edit-block-button svg{margin:0!important}.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block>.edit-block-button{width:30px!important;padding-left:7px!important}.is-live-preview:not(.is-readable-line-width) .cm-embed-block>.edit-block-button{padding-left:0!important;margin-left:0!important;padding:4px}.markdown-source-view.mod-cm6 .edit-block-button:hover{background-color:var(--background-primary);color:var(--text-muted)}.markdown-source-view.mod-cm6 .edit-block-button svg{opacity:1}.markdown-source-view.mod-cm6 .edit-block-button:hover svg{opacity:1}.markdown-source-view.mod-cm6 .cm-embed-block{padding:0;border:0;border-radius:0}.markdown-source-view.mod-cm6 .cm-embed-block:hover{border:0}.metadata-container{--input-height:2rem}.markdown-source-view .metadata-container{transform:translateX(-4px)}body.metadata-heading-off .metadata-properties-heading{display:none}.metadata-add-property-off .mod-root .metadata-add-button{display:none}.metadata-dividers{--metadata-divider-width:1px;--metadata-gap:0px}.metadata-icons-off .workspace-leaf-content[data-type=all-properties] .tree-item-inner{margin-left:-16px}.metadata-icons-off .workspace-leaf-content[data-type=all-properties] .tree-item-icon{display:none}.metadata-icons-off .metadata-property-icon{display:none}figure{margin-inline-start:0;margin-inline-end:0}.markdown-preview-view .mod-highlighted{transition:background-color .3s ease;background-color:var(--text-selection);color:inherit}.inline-title{padding-top:16px}.minimal-status-off .status-bar{--status-bar-position:static;--status-bar-radius:0;--status-bar-border-width:1px 0 0 0;--status-bar-background:var(--background-secondary);--status-bar-border-color:var(--ui1)}body:not(.minimal-status-off) .status-bar{background-color:var(--background-primary);--status-bar-border-width:0}.status-bar{transition:color .2s linear;color:var(--text-faint);font-size:var(--font-adaptive-smallest)}.status-bar .sync-status-icon.mod-success,.status-bar .sync-status-icon.mod-working{color:var(--text-faint)}.status-bar:hover,.status-bar:hover .sync-status-icon.mod-success,.status-bar:hover .sync-status-icon.mod-working{color:var(--text-muted);transition:color .2s linear}.status-bar .plugin-sync:hover .sync-status-icon.mod-success,.status-bar .plugin-sync:hover .sync-status-icon.mod-working{color:var(--text-normal)}.status-bar .status-bar-item{cursor:var(--cursor)!important}.status-bar .status-bar-item.cMenu-statusbar-button:hover,.status-bar .status-bar-item.mod-clickable:hover,.status-bar .status-bar-item.plugin-editor-status:hover,.status-bar .status-bar-item.plugin-sync:hover{text-align:center;background-color:var(--background-modifier-hover)!important}.tab-stack-top-flipped{--tab-stacked-text-transform:rotate(180deg);--tab-stacked-text-align:right}.tab-stack-center{--tab-stacked-text-align:center}.tab-stack-center-flipped{--tab-stacked-text-transform:rotate(180deg);--tab-stacked-text-align:center}.tab-stack-bottom{--tab-stacked-text-transform:rotate(180deg)}.tab-stack-bottom-flipped{--tab-stacked-text-align:right}.view-header-title,.view-header-title-parent{text-overflow:ellipsis}.view-header-title-container:not(.mod-at-end):after{display:none}body:not(.is-mobile) .view-actions .view-action:last-child{margin-left:-1px}.minimal-focus-mode .workspace-ribbon:not(.is-collapsed)~.mod-root .view-header:hover .view-actions,.mod-right.is-collapsed~.mod-root .view-header:hover .view-actions,.view-action.is-active:hover,.workspace-ribbon.mod-left.is-collapsed~.mod-root .view-header:hover .view-actions,body:not(.minimal-focus-mode) .workspace-ribbon:not(.is-collapsed)~.mod-root .view-actions{opacity:1;transition:opacity .25s ease-in-out}.view-header-title-container{opacity:0;transition:opacity .1s ease-in-out}.view-header-title-container:focus-within{opacity:1;transition:opacity .1s ease-in-out}.view-header:hover .view-header-title-container,.workspace-tab-header-container:hover+.workspace-tab-container .view-header-title-container{opacity:1;transition:opacity .1s ease-in-out}.is-phone .view-header-title-container,.minimal-tab-title-visible .view-header-title-container{opacity:1}.minimal-tab-title-hidden .view-header-title-container{opacity:0}.minimal-tab-title-hidden .view-header-title-container:focus-within{opacity:1;transition:opacity .1s ease-in-out}.minimal-tab-title-hidden .view-header:hover .view-header-title-container,.minimal-tab-title-hidden .workspace-tab-header-container:hover+.workspace-tab-container .view-header-title-container{opacity:0}body.window-title-off .titlebar-text{display:none}.titlebar-button-container.mod-right{background-color:transparent!important}.is-hidden-frameless.theme-dark:not(.minimal-dark-black):not(.colorful-frame),.is-hidden-frameless.theme-light:not(.minimal-light-tonal):not(.colorful-frame):not(.minimal-light-white){--titlebar-background:var(--bg1)}.is-hidden-frameless.theme-dark:not(.minimal-dark-black):not(.colorful-frame).is-focused .sidebar-toggle-button.mod-right,.is-hidden-frameless.theme-dark:not(.minimal-dark-black):not(.colorful-frame).is-focused .workspace-ribbon.mod-left.is-collapsed,.is-hidden-frameless.theme-dark:not(.minimal-dark-black):not(.colorful-frame).is-focused .workspace-tabs.mod-top,.is-hidden-frameless.theme-light:not(.minimal-light-tonal):not(.colorful-frame):not(.minimal-light-white).is-focused .sidebar-toggle-button.mod-right,.is-hidden-frameless.theme-light:not(.minimal-light-tonal):not(.colorful-frame):not(.minimal-light-white).is-focused .workspace-ribbon.mod-left.is-collapsed,.is-hidden-frameless.theme-light:not(.minimal-light-tonal):not(.colorful-frame):not(.minimal-light-white).is-focused .workspace-tabs.mod-top{--titlebar-background-focused:var(--bg1)}.is-hidden-frameless.theme-dark:not(.minimal-dark-black):not(.colorful-frame):not(.minimal-dark-tonal):not(.minimal-light-white) .workspace-ribbon.mod-left:not(.is-collapsed),.is-hidden-frameless.theme-light:not(.minimal-light-tonal):not(.colorful-frame):not(.minimal-light-white):not(.minimal-dark-tonal):not(.minimal-light-white) .workspace-ribbon.mod-left:not(.is-collapsed){--titlebar-background:var(--bg2)}.mod-macos.is-hidden-frameless:not(.is-popout-window) .sidebar-toggle-button.mod-right{right:0;padding-right:var(--size-4-2)}body.is-focused{--titlebar-background-focused:var(--background-secondary)}.is-hidden-frameless:not(.colorful-frame) .mod-left-split .mod-top .workspace-tab-header-container{--tab-container-background:var(--background-secondary)}.mod-root .workspace-tab-header-status-icon{color:var(--text-muted)}.modal button:not(.mod-warning),.modal.mod-settings button:not(.mod-cta):not(.mod-warning),.modal.mod-settings button:not(.mod-warning){white-space:nowrap;transition:background-color .2s ease-out,border-color .2s ease-out}button.mod-warning{border:1px solid var(--background-modifier-error);color:var(--text-error);box-shadow:0 1px 1px 0 var(--btn-shadow-color);transition:background-color .2s ease-out}button.mod-warning:hover{border:1px solid var(--background-modifier-error);color:var(--text-error);box-shadow:0 2px 3px 0 var(--btn-shadow-color);transition:background-color .2s ease-out}.document-replace,.document-search{max-width:100%;padding:0}.document-search-container{margin:0 auto;max-width:var(--max-width);width:var(--line-width)}.is-mobile .CodeMirror-foldgutter-open:after,.is-mobile span[title="Fold line"]:after{transform:translateX(-2px)!important}body.is-mobile .CodeMirror-foldgutter-folded:after,body.is-mobile span[title="Unfold line"]:after{content:"›";font-family:sans-serif;transform:translateY(-2px);transform:rotate(-90deg) translateY(2px) translateX(-.45em)}body.is-mobile .CodeMirror-foldgutter-open:after,body.is-mobile span[title="Fold line"]:after{content:"›";font-family:sans-serif;transform:rotate(360deg)}body{--ig-adjust-reading:-0.95em;--ig-adjust-edit:2px}.markdown-rendered.show-indentation-guide li.task-list-item>ol::before,.markdown-rendered.show-indentation-guide li.task-list-item>ul::before,.markdown-rendered.show-indentation-guide li>ol::before,.markdown-rendered.show-indentation-guide li>ul::before{left:var(--ig-adjust-reading)}.markdown-source-view.mod-cm6 .cm-indent::before{transform:translateX(var(--ig-adjust-edit))}.is-mobile .markdown-rendered.show-indentation-guide li>ol::before,.is-mobile .markdown-rendered.show-indentation-guide li>ul::before{left:calc(0em + var(--ig-adjust-reading))}.is-mobile .markdown-source-view.mod-cm6 .cm-indent::before{transform:translateX(calc(2px + var(--ig-adjust-edit)))}.modal-button-container .mod-checkbox{--checkbox-radius:4px}.modal-container.mod-confirmation .modal{width:480px;min-width:0}.theme-light{--progress-outline:rgba(0, 0, 0, 0.05)}.theme-dark{--progress-outline:rgba(255, 255, 255, 0.04)}body{--progress-complete:var(--text-accent)}.markdown-preview-view progress,.markdown-rendered progress,.markdown-source-view.is-live-preview progress{width:220px}.markdown-preview-view progress[value]::-webkit-progress-bar,.markdown-rendered progress[value]::-webkit-progress-bar,.markdown-source-view.is-live-preview progress[value]::-webkit-progress-bar{box-shadow:inset 0 0 0 var(--border-width) var(--progress-outline)}.markdown-preview-view progress[value^='1']::-webkit-progress-value,.markdown-preview-view progress[value^='2']::-webkit-progress-value,.markdown-preview-view progress[value^='3']::-webkit-progress-value,.markdown-rendered progress[value^='1']::-webkit-progress-value,.markdown-rendered progress[value^='2']::-webkit-progress-value,.markdown-rendered progress[value^='3']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='1']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='2']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='3']::-webkit-progress-value{background-color:var(--color-red)}.markdown-preview-view progress[value^='4']::-webkit-progress-value,.markdown-preview-view progress[value^='5']::-webkit-progress-value,.markdown-rendered progress[value^='4']::-webkit-progress-value,.markdown-rendered progress[value^='5']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='4']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='5']::-webkit-progress-value{background-color:var(--color-orange)}.markdown-preview-view progress[value^='6']::-webkit-progress-value,.markdown-preview-view progress[value^='7']::-webkit-progress-value,.markdown-rendered progress[value^='6']::-webkit-progress-value,.markdown-rendered progress[value^='7']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='6']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='7']::-webkit-progress-value{background-color:var(--color-yellow)}.markdown-preview-view progress[value^='8']::-webkit-progress-value,.markdown-preview-view progress[value^='9']::-webkit-progress-value,.markdown-rendered progress[value^='8']::-webkit-progress-value,.markdown-rendered progress[value^='9']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='8']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value^='9']::-webkit-progress-value{background-color:var(--color-green)}.markdown-preview-view progress[value='1']::-webkit-progress-value,.markdown-preview-view progress[value='100']::-webkit-progress-value,.markdown-rendered progress[value='1']::-webkit-progress-value,.markdown-rendered progress[value='100']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='1']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='100']::-webkit-progress-value{background-color:var(--progress-complete)}.markdown-preview-view progress[value='0']::-webkit-progress-value,.markdown-preview-view progress[value='2']::-webkit-progress-value,.markdown-preview-view progress[value='3']::-webkit-progress-value,.markdown-preview-view progress[value='4']::-webkit-progress-value,.markdown-preview-view progress[value='5']::-webkit-progress-value,.markdown-preview-view progress[value='6']::-webkit-progress-value,.markdown-preview-view progress[value='7']::-webkit-progress-value,.markdown-preview-view progress[value='8']::-webkit-progress-value,.markdown-preview-view progress[value='9']::-webkit-progress-value,.markdown-rendered progress[value='0']::-webkit-progress-value,.markdown-rendered progress[value='2']::-webkit-progress-value,.markdown-rendered progress[value='3']::-webkit-progress-value,.markdown-rendered progress[value='4']::-webkit-progress-value,.markdown-rendered progress[value='5']::-webkit-progress-value,.markdown-rendered progress[value='6']::-webkit-progress-value,.markdown-rendered progress[value='7']::-webkit-progress-value,.markdown-rendered progress[value='8']::-webkit-progress-value,.markdown-rendered progress[value='9']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='0']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='2']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='3']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='4']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='5']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='6']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='7']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='8']::-webkit-progress-value,.markdown-source-view.is-live-preview progress[value='9']::-webkit-progress-value{background-color:var(--color-red)}body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar,body:not(.native-scrollbars) ::-webkit-scrollbar{width:11px;background-color:transparent}body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar:horizontal,body:not(.native-scrollbars) ::-webkit-scrollbar:horizontal{height:11px}body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-corner,body:not(.native-scrollbars) ::-webkit-scrollbar-corner{background-color:transparent}body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-track,body:not(.native-scrollbars) ::-webkit-scrollbar-track{background-color:transparent}body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb,body:not(.native-scrollbars) ::-webkit-scrollbar-thumb{background-clip:padding-box;border-radius:20px;border:3px solid transparent;background-color:var(--background-modifier-border);border-width:3px 3px 3px 3px;min-height:45px}body:not(.hider-scrollbars).styled-scrollbars .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:hover,body:not(.hider-scrollbars).styled-scrollbars .modal .vertical-tab-header::-webkit-scrollbar-thumb:hover,body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb:hover,body:not(.native-scrollbars) .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:hover,body:not(.native-scrollbars) .modal .vertical-tab-header::-webkit-scrollbar-thumb:hover,body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:hover{background-color:var(--background-modifier-border-hover)}body:not(.hider-scrollbars).styled-scrollbars .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:active,body:not(.hider-scrollbars).styled-scrollbars .modal .vertical-tab-header::-webkit-scrollbar-thumb:active,body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb:active,body:not(.native-scrollbars) .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:active,body:not(.native-scrollbars) .modal .vertical-tab-header::-webkit-scrollbar-thumb:active,body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:active{background-color:var(--background-modifier-border-focus)}.tooltip{transition:none;animation:none}.tooltip.mod-left,.tooltip.mod-right{animation:none}.tooltip.mod-error{color:var(--text-error)}.markdown-preview-view blockquote{padding:0 0 0 var(--nested-padding);font-size:var(--blockquote-size)}.markdown-source-view.mod-cm6 .HyperMD-quote,.markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote{font-size:var(--blockquote-size)}.is-live-preview .cm-hmd-indent-in-quote{color:var(--text-faint)}.is-live-preview.is-readable-line-width>.cm-callout .callout{max-width:var(--max-width);margin:0 auto}.callouts-outlined .callout .callout-title{background-color:var(--background-primary);margin-top:-24px;z-index:200;width:fit-content;padding:0 .5em;margin-left:-.75em;letter-spacing:.05em;font-variant-caps:all-small-caps}.callouts-outlined .callout{overflow:visible;--callout-border-width:1px;--callout-border-opacity:0.5;--callout-title-size:0.8em;--callout-blend-mode:normal;background-color:transparent}.callouts-outlined .cm-embed-block.cm-callout{padding-top:12px}.callouts-outlined .callout-content .callout{margin-top:18px}body{--checkbox-radius:50%;--checkbox-top:2px;--checkbox-left:0px;--checkbox-margin:0px 6px 0px -2em}.checkbox-square{--checkbox-size:calc(var(--font-text-size) * 0.85);--checkbox-radius:4px;--checkbox-top:1px;--checkbox-left:0px;--checkbox-margin:0px 8px 0px -2em}body.minimal-strike-lists{--checklist-done-decoration:line-through}body:not(.minimal-strike-lists){--checklist-done-decoration:none;--checklist-done-color:var(--text-normal)}.markdown-preview-section>.contains-task-list{padding-bottom:.5em}.mod-cm6 .HyperMD-task-line[data-task] .cm-formatting-list-ol~.task-list-label .task-list-item-checkbox{margin:1px}.markdown-preview-view .task-list-item-checkbox{position:relative;top:var(--checkbox-top);left:var(--checkbox-left);line-height:0}.markdown-preview-view ul>li.task-list-item{text-indent:0;line-height:var(--line-height)}.is-mobile .mod-cm6 .HyperMD-task-line[data-task] .task-list-item-checkbox{margin-inline-start:-.4em}.is-mobile .markdown-preview-view input[type=checkbox].task-list-item-checkbox{top:.2em}.minimal-code-scroll{--code-white-space:pre}.minimal-code-scroll .HyperMD-codeblock.HyperMD-codeblock-bg{overflow-y:scroll;white-space:pre}.minimal-code-scroll .cm-hmd-codeblock{white-space:pre!important}@media print{.print{--code-background:#eee!important}}body{--embed-max-height:none;--embed-decoration-style:solid;--embed-decoration-color:var(--background-modifier-border-hover)}.embed-strict{--embed-background:transparent;--embed-border-left:0;--embed-padding:0}.embed-strict .markdown-embed-content{--folding-offset:0px}.embed-strict .el-embed-heading.el-p>p{margin-block-start:0;margin-block-end:0}.embed-strict .internal-embed .markdown-embed,.embed-strict .markdown-preview-view .markdown-embed,.embed-strict.markdown-preview-view .markdown-embed{padding:0}.embed-strict .internal-embed .markdown-embed .markdown-embed-title,.embed-strict .markdown-embed-title{display:none}.embed-strict .internal-embed:not([src*="#^"]) .markdown-embed-link{width:24px;opacity:0}.embed-underline .internal-embed:not(.pdf-embed){text-decoration-line:underline;text-decoration-style:var(--embed-decoration-style);text-decoration-color:var(--embed-decoration-color)}.embed-hide-title .markdown-embed-title{display:none}.contextual-typography .embed-strict .internal-embed .markdown-preview-view .markdown-preview-sizer>div,.embed-strict.contextual-typography .internal-embed .markdown-preview-view .markdown-preview-sizer>div{margin:0;width:100%}.markdown-embed .markdown-preview-view .markdown-preview-sizer{padding-bottom:0!important}.markdown-preview-view.is-readable-line-width .markdown-embed .markdown-preview-sizer,.markdown-preview-view.markdown-embed .markdown-preview-sizer{max-width:100%;width:100%;min-height:0!important;padding-bottom:0!important}.markdown-embed .markdown-preview-section div:last-child p,.markdown-embed .markdown-preview-section div:last-child ul{margin-block-end:2px}.markdown-preview-view .markdown-embed{margin-top:var(--nested-padding);padding:0 calc(var(--nested-padding)/ 2) 0 var(--nested-padding)}.internal-embed:not([src*="#^"]) .markdown-embed-link{right:0;width:100%}.file-embed-link,.markdown-embed-link{top:0;right:0;text-align:right;justify-content:flex-end}.file-embed-link svg,.markdown-embed-link svg{width:16px;height:16px}.markdown-embed .file-embed-link,.markdown-embed .markdown-embed-link{opacity:.6;transition:opacity .1s linear}.markdown-embed .file-embed-link:hover,.markdown-embed .markdown-embed-link:hover{opacity:1}.markdown-embed .file-embed-link:hover:hover,.markdown-embed .markdown-embed-link:hover:hover{background-color:transparent;--icon-color:var(--text-accent)}.file-embed-link:hover,.markdown-embed-link:hover{color:var(--text-muted)}.markdown-embed .markdown-preview-view{padding:0}.internal-embed .markdown-embed{border:0;border-left:1px solid var(--quote-opening-modifier);border-radius:0}a[href*="obsidian://search"]{background-image:url("data:image/svg+xml,")}.theme-dark a[href*="obsidian://search"]{background-image:url("data:image/svg+xml,")}div>ol,div>ul{padding-inline-start:1.4em}ul>li{min-height:1.4em}ol>li{margin-inline-start:0}ol{margin-inline-start:0;list-style:default}body{--adaptive-list-edit-offset:var(--list-edit-offset)}.is-rtl{--adaptive-list-edit-offset:calc(var(--list-edit-offset)*-1)}.markdown-source-view.mod-cm6 .cm-content .HyperMD-list-line{transform:translateX(var(--adaptive-list-edit-offset))}.markdown-preview-view ol>li,.markdown-preview-view ul>li,.markdown-source-view ol>li,.markdown-source-view ul>li,.mod-cm6 .HyperMD-list-line.cm-line{padding-top:var(--list-spacing);padding-bottom:var(--list-spacing)}.is-mobile ul>li:not(.task-list-item)::marker{font-size:.8em}.is-mobile .markdown-rendered ol,.is-mobile .markdown-rendered ul{padding-inline-start:var(--list-indent)}.is-mobile .markdown-rendered div>ol,.is-mobile .markdown-rendered div>ul{padding-inline-start:2em}.is-mobile .el-ol>ol,.is-mobile .el-ul>ul{margin-inline-start:0}.is-mobile .workspace-leaf-content:not([data-type=search]) .workspace-leaf-content[data-type=markdown] .nav-buttons-container{border-bottom:none;padding-top:5px}.is-mobile .mod-root .workspace-leaf-content[data-type=markdown] .search-input-container{width:calc(100% - 160px)}.embedded-backlinks .nav-header~.search-input-container{width:calc(100% - 140px);margin-top:12px}.embedded-backlinks .nav-buttons-container{position:absolute;right:0;top:14px}.embedded-backlinks .backlink-pane>.tree-item-self,.embedded-backlinks .backlink-pane>.tree-item-self:hover{text-transform:none;color:var(--text-normal);font-size:var(--font-adaptive-normal);font-weight:500;letter-spacing:unset}body{--pdf-dark-opacity:1}.theme-dark:not(.pdf-shadows-on),.theme-light:not(.pdf-shadows-on){--pdf-shadow:none;--pdf-thumbnail-shadow:none}.theme-dark:not(.pdf-shadows-on) .pdf-viewer .page,.theme-light:not(.pdf-shadows-on) .pdf-viewer .page{border:0}.theme-dark:not(.pdf-shadows-on) .pdf-sidebar-container .thumbnailSelectionRing,.theme-light:not(.pdf-shadows-on) .pdf-sidebar-container .thumbnailSelectionRing{padding:0}.theme-dark:not(.pdf-shadows-on) .pdf-sidebar-container .thumbnail::after,.theme-light:not(.pdf-shadows-on) .pdf-sidebar-container .thumbnail::after{right:var(--size-4-2);bottom:var(--size-4-2)}.theme-dark{--pdf-thumbnail-shadow:0 0 1px 0 rgba(0, 0, 0, 0.6);--pdf-shadow:0 0 1px 0 rgba(0, 0, 0, 0.6)}.theme-dark .pdf-viewer .canvasWrapper{opacity:var(--pdf-dark-opacity)}.theme-dark.pdf-invert-dark .workspace-leaf-content[data-type=pdf] .pdf-thumbnail-view .thumbnailImage,.theme-dark.pdf-invert-dark .workspace-leaf-content[data-type=pdf] .pdf-viewer .canvasWrapper{filter:invert(1) hue-rotate(180deg);mix-blend-mode:screen}.theme-light.pdf-blend-light .workspace-leaf-content[data-type=pdf] .pdf-thumbnail-view .thumbnailImage,.theme-light.pdf-blend-light .workspace-leaf-content[data-type=pdf] .pdf-viewer .canvasWrapper{mix-blend-mode:multiply}body{--table-header-border-width:0;--table-column-first-border-width:0;--table-column-last-border-width:0;--table-row-last-border-width:0;--table-edge-cell-padding-first:0;--table-edge-cell-padding-last:10px;--table-cell-padding:4px 10px;--table-header-size:var(--table-text-size)}.markdown-source-view.mod-cm6 table{border-collapse:collapse}.markdown-preview-view table,.markdown-source-view.mod-cm6 table{border:var(--border-width) solid var(--border-color);border-collapse:collapse}.markdown-preview-view td,.markdown-preview-view th,.markdown-source-view.mod-cm6 td,.markdown-source-view.mod-cm6 th{padding:var(--table-cell-padding)}.markdown-preview-view td:first-child,.markdown-preview-view th:first-child,.markdown-source-view.mod-cm6 td:first-child,.markdown-source-view.mod-cm6 th:first-child{padding-left:var(--table-edge-cell-padding-first)}.markdown-preview-view td:last-child,.markdown-preview-view th:last-child,.markdown-source-view.mod-cm6 td:last-child,.markdown-source-view.mod-cm6 th:last-child{padding-right:var(--table-edge-cell-padding-last)}.markdown-preview-view th,.markdown-source-view.mod-cm6 .dataview.table-view-table thead.table-view-thead tr th,.table-view-table>thead>tr>th{padding:var(--table-cell-padding)}.markdown-preview-view th:first-child,.markdown-source-view.mod-cm6 .dataview.table-view-table thead.table-view-thead tr th:first-child,.table-view-table>thead>tr>th:first-child{padding-left:var(--table-edge-cell-padding-first)}.markdown-preview-view th:last-child,.markdown-source-view.mod-cm6 .dataview.table-view-table thead.table-view-thead tr th:last-child,.table-view-table>thead>tr>th:last-child{padding-right:var(--table-edge-cell-padding-last)}.is-live-preview .el-table{width:100%;max-width:100%}.cm-hmd-table-sep-dummy,.cm-s-obsidian .HyperMD-table-row span.cm-hmd-table-sep{color:var(--text-faint);font-weight:400}body.minimal-unstyled-tags{--tag-background:transparent;--tag-background-hover:transparent;--tag-border-width:0px;--tag-padding-x:0;--tag-padding-y:0;--tag-size:inherit;--tag-color-hover:var(--text-accent-hover)}body.minimal-unstyled-tags.is-mobile.theme-dark{--tag-background:transparent}body:not(.minimal-unstyled-tags){--tag-size:0.8em;--tag-padding-y:0.2em;--tag-background:transparent;--tag-background-hover:transparent;--tag-color:var(--text-muted);--tag-border-width:1px;--tag-border-color:var(--background-modifier-border);--tag-border-color-hover:var(--background-modifier-border-hover);--tag-color-hover:var(--text-normal)}body.is-mobile.theme-dark{--tag-background:transparent}h1,h2,h3,h4{letter-spacing:-.02em}body,button,input{font-family:var(--font-interface)}.cm-s-obsidian span.cm-error{color:var(--color-red)}.markdown-preview-view,.popover,.workspace-leaf-content[data-type=markdown]{font-family:var(--font-text)}.cm-s-obsidian,.markdown-preview-view,.markdown-source-view.mod-cm6.is-live-preview .cm-scroller,body{font-size:var(--font-adaptive-normal);font-weight:var(--normal-weight);line-height:var(--line-height)}.cm-s-obsidian,.markdown-source-view,.markdown-source-view.mod-cm6 .cm-scroller{line-height:var(--line-height);font-family:var(--font-editor)}.cm-s-obsidian span.cm-formatting-task{line-height:var(--line-height)}.active-line-on .cm-line.cm-active,.active-line-on .markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote.cm-active{background-color:var(--active-line-bg);box-shadow:-25vw 0 var(--active-line-bg),25vw 0 var(--active-line-bg)}body{--content-margin:auto;--content-margin-start:max(calc(50% - var(--line-width)/2), calc(50% - var(--max-width)/2));--content-line-width:min(var(--line-width), var(--max-width))}.markdown-preview-view .markdown-preview-sizer.markdown-preview-sizer{max-width:100%;margin-inline:auto;width:100%}.markdown-source-view.mod-cm6.is-readable-line-width .cm-content,.markdown-source-view.mod-cm6.is-readable-line-width .cm-sizer{max-width:100%;width:100%}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div,.markdown-source-view.mod-cm6.is-readable-line-width .cm-sizer>.embedded-backlinks,.markdown-source-view.mod-cm6.is-readable-line-width .cm-sizer>.inline-title,.markdown-source-view.mod-cm6.is-readable-line-width .cm-sizer>.metadata-container{max-width:var(--max-width);width:var(--line-width);margin-inline:var(--content-margin)!important}.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>:not(div){max-width:var(--content-line-width);margin-inline-start:var(--content-margin-start)!important}.is-readable-line-width{--file-margins:1rem 0 0 0}.is-mobile .markdown-preview-view{--folding-offset:0}.minimal-line-nums .workspace-leaf-content[data-type=markdown]{--file-margins:var(--size-4-8) var(--size-4-8) var(--size-4-8) 48px}.minimal-line-nums .workspace-leaf-content[data-type=markdown].is-rtl{--file-margins:var(--size-4-8) 48px var(--size-4-8) var(--size-4-8)}.minimal-line-nums .workspace-leaf-content[data-type=markdown] .is-readable-line-width{--file-margins:1rem 0 0 var(--folding-offset)}.minimal-line-nums .workspace-leaf-content[data-type=markdown] .is-readable-line-width.is-rtl{--file-margins:1rem var(--folding-offset) 0 0}.minimal-line-nums .mod-left-split .markdown-preview-view,.minimal-line-nums .mod-left-split .markdown-source-view.mod-cm6 .cm-scroller,.minimal-line-nums .mod-right-split .markdown-preview-view,.minimal-line-nums .mod-right-split .markdown-source-view.mod-cm6 .cm-scroller{--file-margins:var(--size-4-5) var(--size-4-5) var(--size-4-5) 48px}.view-content .reader-mode-content.is-readable-line-width .markdown-preview-sizer{max-width:var(--max-width);width:var(--line-width)}.markdown-preview-view .inline-embed{--max-width:100%}body{--container-table-width:var(--line-width);--container-table-max-width:var(--max-width);--table-max-width:none;--table-width:auto;--table-margin:inherit;--container-img-width:var(--line-width);--container-img-max-width:var(--max-width);--img-max-width:100%;--img-width:auto;--img-margin-start:var(--content-margin-start);--img-line-width:var(--content-line-width);--container-chart-width:var(--line-width);--container-chart-max-width:var(--max-width);--chart-max-width:none;--chart-width:auto;--container-map-width:var(--line-width);--container-map-max-width:var(--max-width);--map-max-width:none;--map-width:auto;--container-iframe-width:var(--line-width);--container-iframe-max-width:var(--max-width);--iframe-max-width:none;--iframe-width:auto}body .wide{--line-width:var(--line-width-wide);--container-table-width:var(--line-width-wide);--container-img-width:var(--line-width-wide);--container-iframe-width:var(--line-width-wide);--container-map-width:var(--line-width-wide);--container-chart-width:var(--line-width-wide)}body .max{--line-width:var(--max-width);--container-table-width:var(--max-width);--container-img-width:var(--max-width);--container-iframe-width:var(--max-width);--container-map-width:var(--max-width);--container-chart-width:var(--max-width)}table.dataview{--table-min-width:min(var(--line-width), var(--max-width))}.cards table.dataview{--table-width:100%;--table-min-width:none}.maximize-tables-auto{--container-table-max-width:100%;--container-table-width:100%;--table-max-width:100%;--table-margin:var(--content-margin-start) auto;--table-width:auto}.maximize-tables-auto .cards{--container-table-max-width:var(--max-width)}.maximize-tables-auto .cards .block-language-dataview{--table-margin:auto}.maximize-tables{--container-table-max-width:100%;--container-table-width:100%;--table-min-width:min(var(--line-width), var(--max-width));--table-max-width:100%;--table-margin:auto;--table-width:auto;--table-edge-cell-padding-first:10px}.table-100,.table-max,.table-wide{--table-max-width:100%;--table-width:100%}.table-wide{--container-table-width:var(--line-width-wide);--table-edge-cell-padding-first:0px}.table-max{--container-table-width:var(--max-width);--table-edge-cell-padding-first:0px;--table-margin:0}.table-100{--container-table-width:100%;--container-table-max-width:100%;--table-edge-cell-padding-first:20px;--table-margin:0}.table-100 .dataview.list-view-ul{max-width:var(--max-width);width:var(--line-width);margin-inline:auto}.img-100,.img-max,.img-wide{--img-max-width:100%;--img-width:100%}.img-wide{--container-img-width:var(--line-width-wide);--img-line-width:var(--line-width-wide);--img-margin-start:calc(50% - var(--line-width-wide)/2)}.img-max{--container-img-width:var(--max-width);--img-line-width:var(--max-width);--img-margin-start:calc(50% - var(--max-width)/2)}.img-100{--container-img-width:100%;--container-img-max-width:100%;--img-line-width:100%;--img-margin-start:0}.map-100,.map-max,.map-wide{--map-max-width:100%;--map-width:100%}.map-wide{--container-map-width:var(--line-width-wide)}.map-max{--container-map-width:var(--max-width)}.map-100{--container-map-width:100%;--container-map-max-width:100%}.chart-100,.chart-max,.chart-wide{--chart-max-width:100%;--chart-width:100%}.chart-wide{--container-chart-width:var(--line-width-wide)}.chart-max{--container-chart-width:var(--max-width)}.chart-100{--container-chart-width:100%;--container-chart-max-width:100%}.iframe-100,.iframe-max,.iframe-wide{--iframe-max-width:100%;--iframe-width:100%}.iframe-wide{--container-iframe-width:var(--line-width-wide)}.iframe-max{--container-iframe-width:var(--max-width)}.iframe-100{--container-iframe-width:100%;--container-iframe-max-width:100%}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer .cm-table-widget,.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(>.block-language-dataview>table),.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(>.block-language-dataviewjs),.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(>table),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content .cm-table-widget,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(>.block-language-dataview>table),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(>.block-language-dataviewjs),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(>table){width:var(--container-table-width);max-width:var(--container-table-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer table,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content table{width:var(--table-width);max-width:var(--table-max-width);margin-inline:var(--table-margin);min-width:var(--table-min-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer .block-language-dataviewjs>:is(p,h1,h2,h3,h4,h5,h6),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content .block-language-dataviewjs>:is(p,h1,h2,h3,h4,h5,h6){width:var(--line-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer .block-language-dataviewjs>.dataview-error,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content .block-language-dataviewjs>.dataview-error{margin:0 auto;width:var(--content-line-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer .dataview.dataview-error-box,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content .dataview.dataview-error-box{margin-inline:var(--table-margin)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>.image-embed,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>.image-embed{padding-top:.25rem;padding-bottom:.25rem}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>.image-embed,.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(.image-embed),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>.image-embed,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(.image-embed){width:var(--container-img-width);max-width:var(--container-img-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>.image-embed img,.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(.image-embed) img,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>.image-embed img,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(.image-embed) img{max-width:var(--img-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>img,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>img{max-width:var(--img-line-width);margin-inline-start:var(--img-margin-start)!important}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-chart),.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-dataviewjs canvas),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-chart),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-dataviewjs canvas){width:var(--container-chart-width);max-width:var(--container-chart-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-chart) canvas,.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-dataviewjs canvas) canvas,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-chart) canvas,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-dataviewjs canvas) canvas{max-width:var(--map-chart-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-leaflet),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-leaflet){width:var(--container-map-width);max-width:var(--container-map-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.block-language-leaflet) iframe,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.block-language-leaflet) iframe{max-width:var(--map-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.cm-html-embed),.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(>iframe),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.cm-html-embed),.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(>iframe){width:var(--container-iframe-width);max-width:var(--container-iframe-max-width)}.markdown-preview-view.is-readable-line-width .markdown-preview-sizer div:has(>.cm-html-embed) iframe,.markdown-preview-view.is-readable-line-width .markdown-preview-sizer>div:has(>iframe) iframe,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content div:has(>.cm-html-embed) iframe,.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>div:has(>iframe) iframe{max-width:var(--iframe-max-width);width:var(--iframe-width)}.borders-none{--divider-width:0px;--tab-outline-width:0px}body{--cards-min-width:180px;--cards-max-width:1fr;--cards-mobile-width:120px;--cards-image-height:400px;--cards-padding:1.2em;--cards-image-fit:contain;--cards-background:transparent;--cards-border-width:1px;--cards-aspect-ratio:auto;--cards-columns:repeat(auto-fit, minmax(var(--cards-min-width), var(--cards-max-width)))}@media (max-width:400pt){body{--cards-min-width:var(--cards-mobile-width)}}.cards.table-100 table.dataview tbody,.table-100 .cards table.dataview tbody{padding:.25rem .75rem}.cards table.dataview{--table-width:100%;--table-edge-cell-padding-first:calc(var(--cards-padding)/2);--table-edge-cell-padding-last:calc(var(--cards-padding)/2);--table-cell-padding:calc(var(--cards-padding)/3) calc(var(--cards-padding)/2);line-height:1.3}.cards table.dataview tbody{clear:both;padding:.5rem 0;display:grid;grid-template-columns:var(--cards-columns);grid-column-gap:0.75rem;grid-row-gap:0.75rem}.cards table.dataview>tbody>tr{background-color:var(--cards-background);border:var(--cards-border-width) solid var(--background-modifier-border);display:flex;flex-direction:column;margin:0;padding:0 0 calc(var(--cards-padding)/3) 0;border-radius:6px;overflow:hidden;transition:box-shadow .15s linear;max-width:var(--cards-max-width)}.cards table.dataview>tbody>tr:hover{border:var(--cards-border-width) solid var(--background-modifier-border-hover);box-shadow:0 4px 6px 0 rgba(0,0,0,.05),0 1px 3px 1px rgba(0,0,0,.025);transition:box-shadow .15s linear}.cards table.dataview tbody>tr>td:first-child{font-weight:var(--bold-weight);border:none}.cards table.dataview tbody>tr>td:first-child a{display:block}.cards table.dataview tbody>tr>td:last-child{border:none}.cards table.dataview tbody>tr>td:not(:first-child){font-size:calc(var(--table-text-size) * .9);color:var(--text-muted)}.cards table.dataview tbody>tr>td>*{padding:calc(var(--cards-padding)/3) 0}.cards table.dataview tbody>tr>td:not(:last-child):not(:first-child){padding:4px 0;border-bottom:1px solid var(--background-modifier-border);width:calc(100% - var(--cards-padding));margin:0 calc(var(--cards-padding)/2)}.cards table.dataview tbody>tr>td a{text-decoration:none}.cards table.dataview tbody>tr>td>button{width:100%;margin:calc(var(--cards-padding)/2) 0}.cards table.dataview tbody>tr>td:last-child>button{margin-bottom:calc(var(--cards-padding)/6)}.cards table.dataview tbody>tr>td>ul{width:100%;padding:.25em 0!important;margin:0 auto!important}.cards table.dataview tbody>tr>td:has(img){padding:0!important;background-color:var(--background-secondary);display:block;margin:0;width:100%}.cards table.dataview tbody>tr>td img{aspect-ratio:var(--cards-aspect-ratio);width:100%;object-fit:var(--cards-image-fit);max-height:var(--cards-image-height);background-color:var(--background-secondary);vertical-align:bottom}.markdown-source-view.mod-cm6.cards .dataview.table-view-table>tbody>tr>td,.trim-cols .cards table.dataview tbody>tr>td{white-space:normal}.links-int-on .cards table{--link-decoration:none}.markdown-source-view.mod-cm6.cards .edit-block-button{top:0}.cards.table-100 table.dataview thead>tr,.table-100 .cards table.dataview thead>tr{right:.75rem}.cards.table-100 table.dataview thead:before,.table-100 .cards table.dataview thead:before{margin-right:.75rem}.theme-light .cards table.dataview thead:before{background-image:url('data:image/svg+xml;utf8,')}.cards table.dataview thead{user-select:none;width:180px;display:block;float:right;position:relative;text-align:right;height:24px;padding-bottom:0}.cards table.dataview thead:hover:before{opacity:.5;background-color:var(--background-modifier-hover)}.cards table.dataview thead:before{content:'';position:absolute;right:0;top:0;width:10px;height:16px;background-repeat:no-repeat;cursor:var(--cursor);text-align:right;padding:var(--size-4-1) var(--size-4-2);margin-bottom:2px;border-radius:var(--radius-s);font-weight:500;font-size:var(--font-adaptive-small);opacity:.25;background-position:center center;background-size:16px;background-image:url('data:image/svg+xml;utf8,')}.cards table.dataview thead>tr{top:-1px;position:absolute;display:none;z-index:9;border:1px solid var(--background-modifier-border-hover);background-color:var(--background-secondary);box-shadow:var(--shadow-s);padding:6px;border-radius:var(--radius-m);flex-direction:column;margin:24px 0 0 0;width:100%}.cards table.dataview thead:hover>tr{display:flex}.cards table.dataview thead>tr>th{display:block;padding:3px 30px 3px 6px!important;border-radius:var(--radius-s);width:100%;font-weight:400;color:var(--text-normal);cursor:var(--cursor);border:none;font-size:var(--font-ui-small)}.cards table.dataview thead>tr>th[sortable-style=sortable-asc],.cards table.dataview thead>tr>th[sortable-style=sortable-desc]{color:var(--text-normal)}.cards table.dataview thead>tr>th:hover{color:var(--text-normal);background-color:var(--background-modifier-hover)}.list-cards.markdown-preview-view .list-bullet,.list-cards.markdown-preview-view .list-collapse-indicator,.list-cards.markdown-preview-view.markdown-rendered.show-indentation-guide li>ul::before{display:none}.list-cards.markdown-preview-view div>ul{display:grid;gap:.75rem;grid-template-columns:var(--cards-columns);padding:0;line-height:var(--line-height-tight)}.list-cards.markdown-preview-view div>ul>li{background-color:var(--cards-background);padding:calc(var(--cards-padding)/2);border-radius:var(--radius-s);border:var(--cards-border-width) solid var(--background-modifier-border);overflow:hidden}.list-cards.markdown-preview-view div>ul .image-embed{padding:0;display:block;background-color:var(--background-secondary);border-radius:var(--image-radius)}.list-cards.markdown-preview-view div>ul .image-embed img{aspect-ratio:var(--cards-aspect-ratio);object-fit:var(--cards-image-fit);max-height:var(--cards-image-height);background-color:var(--background-secondary);vertical-align:bottom}.list-cards.markdown-preview-view div>ul>li>a{--link-decoration:none;--link-external-decoration:none;font-weight:var(--bold-weight)}.list-cards.markdown-preview-view div ul>li:hover{border-color:var(--background-modifier-border-hover)}.list-cards.markdown-preview-view div ul ul{display:block;width:100%;color:var(--text-muted);font-size:var(--font-smallest);margin:calc(var(--cards-padding)/-4) 0;padding:calc(var(--cards-padding)/2) 0}.list-cards.markdown-preview-view div ul ul ul{padding-bottom:calc(var(--cards-padding)/4)}.list-cards.markdown-preview-view div ul ul>li{display:block}.cards.cards-16-9,.list-cards.cards-16-9{--cards-aspect-ratio:16/9}.cards.cards-1-1,.list-cards.cards-1-1{--cards-aspect-ratio:1/1}.cards.cards-2-1,.list-cards.cards-2-1{--cards-aspect-ratio:2/1}.cards.cards-2-3,.list-cards.cards-2-3{--cards-aspect-ratio:2/3}.cards.cards-cols-1,.list-cards.cards-cols-1{--cards-columns:repeat(1, minmax(0, 1fr))}.cards.cards-cols-2,.list-cards.cards-cols-2{--cards-columns:repeat(2, minmax(0, 1fr))}.cards.cards-cover,.list-cards.cards-cover{--cards-image-fit:cover}.cards.cards-align-bottom table.dataview tbody>tr>td:last-child,.list-cards.cards-align-bottom table.dataview tbody>tr>td:last-child{margin-top:auto}@media (max-width:400pt){.cards table.dataview tbody>tr>td:not(:first-child){font-size:80%}}@media (min-width:400pt){.cards-cols-3{--cards-columns:repeat(3, minmax(0, 1fr))}.cards-cols-4{--cards-columns:repeat(4, minmax(0, 1fr))}.cards-cols-5{--cards-columns:repeat(5, minmax(0, 1fr))}.cards-cols-6{--cards-columns:repeat(6, minmax(0, 1fr))}.cards-cols-7{--cards-columns:repeat(7, minmax(0, 1fr))}.cards-cols-8{--cards-columns:repeat(8, minmax(0, 1fr))}}.cm-formatting.cm-formatting-task.cm-property{font-family:var(--font-monospace);font-size:90%}input[data-task="!"]:checked,input[data-task="*"]:checked,input[data-task="-"]:checked,input[data-task="<"]:checked,input[data-task=">"]:checked,input[data-task="I"]:checked,input[data-task="b"]:checked,input[data-task="c"]:checked,input[data-task="d"]:checked,input[data-task="f"]:checked,input[data-task="k"]:checked,input[data-task="l"]:checked,input[data-task="p"]:checked,input[data-task="u"]:checked,input[data-task="w"]:checked,li[data-task="!"]>input:checked,li[data-task="!"]>p>input:checked,li[data-task="*"]>input:checked,li[data-task="*"]>p>input:checked,li[data-task="-"]>input:checked,li[data-task="-"]>p>input:checked,li[data-task="<"]>input:checked,li[data-task="<"]>p>input:checked,li[data-task=">"]>input:checked,li[data-task=">"]>p>input:checked,li[data-task="I"]>input:checked,li[data-task="I"]>p>input:checked,li[data-task="b"]>input:checked,li[data-task="b"]>p>input:checked,li[data-task="c"]>input:checked,li[data-task="c"]>p>input:checked,li[data-task="d"]>input:checked,li[data-task="d"]>p>input:checked,li[data-task="f"]>input:checked,li[data-task="f"]>p>input:checked,li[data-task="k"]>input:checked,li[data-task="k"]>p>input:checked,li[data-task="l"]>input:checked,li[data-task="l"]>p>input:checked,li[data-task="p"]>input:checked,li[data-task="p"]>p>input:checked,li[data-task="u"]>input:checked,li[data-task="u"]>p>input:checked,li[data-task="w"]>input:checked,li[data-task="w"]>p>input:checked{--checkbox-marker-color:transparent;border:none;border-radius:0;background-image:none;background-color:currentColor;-webkit-mask-size:var(--checkbox-icon);-webkit-mask-position:50% 50%}input[data-task=">"]:checked,li[data-task=">"]>input:checked,li[data-task=">"]>p>input:checked{color:var(--text-faint);transform:rotate(90deg);-webkit-mask-position:50% 100%;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z' /%3E%3C/svg%3E")}input[data-task="<"]:checked,li[data-task="<"]>input:checked,li[data-task="<"]>p>input:checked{color:var(--text-faint);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z' clip-rule='evenodd' /%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="?"]:checked,li[data-task="?"]>input:checked,li[data-task="?"]>p>input:checked{--checkbox-marker-color:transparent;background-color:var(--color-yellow);border-color:var(--color-yellow);background-position:50% 50%;background-size:200% 90%;background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cpath fill="white" fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215c0 1.344-.665 2.288-1.79 2.973c-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712c1.03-.632 1.397-1.135 1.397-2.028c0-.979-.758-1.698-1.926-1.698c-1.009 0-1.71.529-1.938 1.402c-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09c0-.618-.473-1.092-1.095-1.092c-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z"%2F%3E%3C%2Fsvg%3E')}.theme-dark input[data-task="?"]:checked,.theme-dark li[data-task="?"]>input:checked,.theme-dark li[data-task="?"]>p>input:checked{background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cpath fill="black" fill-opacity="0.8" fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215c0 1.344-.665 2.288-1.79 2.973c-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712c1.03-.632 1.397-1.135 1.397-2.028c0-.979-.758-1.698-1.926-1.698c-1.009 0-1.71.529-1.938 1.402c-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09c0-.618-.473-1.092-1.095-1.092c-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z"%2F%3E%3C%2Fsvg%3E')}input[data-task="/"]:checked,li[data-task="/"]>input:checked,li[data-task="/"]>p>input:checked{background-image:none;background-color:transparent;position:relative;overflow:hidden}input[data-task="/"]:checked:after,li[data-task="/"]>input:checked:after,li[data-task="/"]>p>input:checked:after{top:0;left:0;content:" ";display:block;position:absolute;background-color:var(--background-modifier-accent);width:calc(50% - .5px);height:100%;-webkit-mask-image:none}input[data-task="!"]:checked,li[data-task="!"]>input:checked,li[data-task="!"]>p>input:checked{color:var(--color-orange);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="\""]:checked,input[data-task="“"]:checked,li[data-task="\""]>input:checked,li[data-task="\""]>p>input:checked,li[data-task="“"]>input:checked,li[data-task="“"]>p>input:checked{--checkbox-marker-color:transparent;background-position:50% 50%;background-color:var(--color-cyan);border-color:var(--color-cyan);background-size:75%;background-repeat:no-repeat;background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"%3E%3Cpath fill="white" d="M6.5 10c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.318.142-.686.238-1.028.466c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.945c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.317.143-.686.238-1.028.467c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.944c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 17.5 10z"%2F%3E%3C%2Fsvg%3E')}.theme-dark input[data-task="\""]:checked,.theme-dark input[data-task="“"]:checked,.theme-dark li[data-task="\""]>input:checked,.theme-dark li[data-task="\""]>p>input:checked,.theme-dark li[data-task="“"]>input:checked,.theme-dark li[data-task="“"]>p>input:checked{background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"%3E%3Cpath fill="black" fill-opacity="0.7" d="M6.5 10c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.318.142-.686.238-1.028.466c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.945c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.317.143-.686.238-1.028.467c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.944c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 17.5 10z"%2F%3E%3C%2Fsvg%3E')}input[data-task="-"]:checked,li[data-task="-"]>input:checked,li[data-task="-"]>p>input:checked{color:var(--text-faint);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E")}body:not(.tasks) .markdown-preview-view ul li[data-task="-"].task-list-item.is-checked,body:not(.tasks) .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:is([data-task="-"]),body:not(.tasks) li[data-task="-"].task-list-item.is-checked{color:var(--text-faint);text-decoration:line-through solid var(--text-faint) 1px}input[data-task="*"]:checked,li[data-task="*"]>input:checked,li[data-task="*"]>p>input:checked{color:var(--color-yellow);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z' /%3E%3C/svg%3E")}input[data-task="l"]:checked,li[data-task="l"]>input:checked,li[data-task="l"]>p>input:checked{color:var(--color-red);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="i"]:checked,li[data-task="i"]>input:checked,li[data-task="i"]>p>input:checked{--checkbox-marker-color:transparent;background-color:var(--color-blue);border-color:var(--color-blue);background-position:50%;background-size:100%;background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"%3E%3Cpath fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="40" d="M196 220h64v172"%2F%3E%3Cpath fill="none" stroke="white" stroke-linecap="round" stroke-miterlimit="10" stroke-width="40" d="M187 396h138"%2F%3E%3Cpath fill="white" d="M256 160a32 32 0 1 1 32-32a32 32 0 0 1-32 32Z"%2F%3E%3C%2Fsvg%3E')}.theme-dark input[data-task="i"]:checked,.theme-dark li[data-task="i"]>input:checked,.theme-dark li[data-task="i"]>p>input:checked{background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"%3E%3Cpath fill="none" stroke="black" stroke-opacity="0.8" stroke-linecap="round" stroke-linejoin="round" stroke-width="40" d="M196 220h64v172"%2F%3E%3Cpath fill="none" stroke="black" stroke-opacity="0.8" stroke-linecap="round" stroke-miterlimit="10" stroke-width="40" d="M187 396h138"%2F%3E%3Cpath fill="black" fill-opacity="0.8" d="M256 160a32 32 0 1 1 32-32a32 32 0 0 1-32 32Z"%2F%3E%3C%2Fsvg%3E')}input[data-task="S"]:checked,li[data-task="S"]>input:checked,li[data-task="S"]>p>input:checked{--checkbox-marker-color:transparent;border-color:var(--color-green);background-color:var(--color-green);background-size:100%;background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48"%3E%3Cpath fill="white" fill-rule="evenodd" d="M26 8a2 2 0 1 0-4 0v2a8 8 0 1 0 0 16v8a4.002 4.002 0 0 1-3.773-2.666a2 2 0 0 0-3.771 1.332A8.003 8.003 0 0 0 22 38v2a2 2 0 1 0 4 0v-2a8 8 0 1 0 0-16v-8a4.002 4.002 0 0 1 3.773 2.666a2 2 0 0 0 3.771-1.332A8.003 8.003 0 0 0 26 10V8Zm-4 6a4 4 0 0 0 0 8v-8Zm4 12v8a4 4 0 0 0 0-8Z" clip-rule="evenodd"%2F%3E%3C%2Fsvg%3E')}.theme-dark input[data-task="S"]:checked,.theme-dark li[data-task="S"]>input:checked,.theme-dark li[data-task="S"]>p>input:checked{background-image:url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48"%3E%3Cpath fill-opacity="0.8" fill="black" fill-rule="evenodd" d="M26 8a2 2 0 1 0-4 0v2a8 8 0 1 0 0 16v8a4.002 4.002 0 0 1-3.773-2.666a2 2 0 0 0-3.771 1.332A8.003 8.003 0 0 0 22 38v2a2 2 0 1 0 4 0v-2a8 8 0 1 0 0-16v-8a4.002 4.002 0 0 1 3.773 2.666a2 2 0 0 0 3.771-1.332A8.003 8.003 0 0 0 26 10V8Zm-4 6a4 4 0 0 0 0 8v-8Zm4 12v8a4 4 0 0 0 0-8Z" clip-rule="evenodd"%2F%3E%3C%2Fsvg%3E')}input[data-task="I"]:checked,li[data-task="I"]>input:checked,li[data-task="I"]>p>input:checked{color:var(--color-yellow);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M11 3a1 1 0 10-2 0v1a1 1 0 102 0V3zM15.657 5.757a1 1 0 00-1.414-1.414l-.707.707a1 1 0 001.414 1.414l.707-.707zM18 10a1 1 0 01-1 1h-1a1 1 0 110-2h1a1 1 0 011 1zM5.05 6.464A1 1 0 106.464 5.05l-.707-.707a1 1 0 00-1.414 1.414l.707.707zM5 10a1 1 0 01-1 1H3a1 1 0 110-2h1a1 1 0 011 1zM8 16v-1h4v1a2 2 0 11-4 0zM12 14c.015-.34.208-.646.477-.859a4 4 0 10-4.954 0c.27.213.462.519.476.859h4.002z' /%3E%3C/svg%3E")}input[data-task="f"]:checked,li[data-task="f"]>input:checked,li[data-task="f"]>p>input:checked{color:var(--color-red);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="k"]:checked,li[data-task="k"]>input:checked,li[data-task="k"]>p>input:checked{color:var(--color-yellow);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="u"]:checked,li[data-task="u"]>input:checked,li[data-task="u"]>p>input:checked{color:var(--color-green);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="d"]:checked,li[data-task="d"]>input:checked,li[data-task="d"]>p>input:checked{color:var(--color-red);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12 13a1 1 0 100 2h5a1 1 0 001-1V9a1 1 0 10-2 0v2.586l-4.293-4.293a1 1 0 00-1.414 0L8 9.586 3.707 5.293a1 1 0 00-1.414 1.414l5 5a1 1 0 001.414 0L11 9.414 14.586 13H12z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="w"]:checked,li[data-task="w"]>input:checked,li[data-task="w"]>p>input:checked{color:var(--color-purple);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M6 3a1 1 0 011-1h.01a1 1 0 010 2H7a1 1 0 01-1-1zm2 3a1 1 0 00-2 0v1a2 2 0 00-2 2v1a2 2 0 00-2 2v.683a3.7 3.7 0 011.055.485 1.704 1.704 0 001.89 0 3.704 3.704 0 014.11 0 1.704 1.704 0 001.89 0 3.704 3.704 0 014.11 0 1.704 1.704 0 001.89 0A3.7 3.7 0 0118 12.683V12a2 2 0 00-2-2V9a2 2 0 00-2-2V6a1 1 0 10-2 0v1h-1V6a1 1 0 10-2 0v1H8V6zm10 8.868a3.704 3.704 0 01-4.055-.036 1.704 1.704 0 00-1.89 0 3.704 3.704 0 01-4.11 0 1.704 1.704 0 00-1.89 0A3.704 3.704 0 012 14.868V17a1 1 0 001 1h14a1 1 0 001-1v-2.132zM9 3a1 1 0 011-1h.01a1 1 0 110 2H10a1 1 0 01-1-1zm3 0a1 1 0 011-1h.01a1 1 0 110 2H13a1 1 0 01-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E")}input[data-task="p"]:checked,li[data-task="p"]>input:checked,li[data-task="p"]>p>input:checked{color:var(--color-green);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z' /%3E%3C/svg%3E")}input[data-task="c"]:checked,li[data-task="c"]>input:checked,li[data-task="c"]>p>input:checked{color:var(--color-orange);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.105-1.79l-.05-.025A4 4 0 0011.055 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z' /%3E%3C/svg%3E")}input[data-task="b"]:checked,li[data-task="b"]>input:checked,li[data-task="b"]>p>input:checked{color:var(--color-orange);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z' /%3E%3C/svg%3E")}.colorful-active .nav-files-container{--nav-item-background-active:var(--interactive-accent);--nav-item-color-active:var(--text-on-accent)}.colorful-active #calendar-container .active,.colorful-active #calendar-container .active.today,.colorful-active #calendar-container .active:hover,.colorful-active #calendar-container .day:active{background-color:var(--interactive-accent);color:var(--text-on-accent)}.colorful-active #calendar-container .active .dot,.colorful-active #calendar-container .day:active .dot,.colorful-active #calendar-container .today.active .dot{fill:var(--text-on-accent)}body:not(.colorful-active) .horizontal-tab-nav-item.is-active,body:not(.colorful-active) .vertical-tab-nav-item.is-active{background-color:var(--background-modifier-hover);color:var(--text-normal)}body{--frame-background:hsl(var(--frame-background-h), var(--frame-background-s), var(--frame-background-l));--frame-icon-color:var(--frame-muted-color)}.theme-light{--frame-background-h:var(--accent-h);--frame-background-s:var(--accent-s);--frame-background-l:calc(var(--accent-l) + 30%);--frame-outline-color:hsla(var(--frame-background-h), var(--frame-background-s), calc(var(--frame-background-l) - 6.5%), 1);--frame-muted-color:hsl(var(--frame-background-h), calc(var(--frame-background-s) - 10%), calc(var(--frame-background-l) - 35%))}.theme-dark{--frame-background-h:var(--accent-h);--frame-background-s:var(--accent-s);--frame-background-l:calc(var(--accent-l) - 25%);--frame-outline-color:hsla(var(--frame-background-h), calc(var(--frame-background-s) - 2%), calc(var(--frame-background-l) + 6.5%), 1);--frame-muted-color:hsl(var(--frame-background-h), calc(var(--frame-background-s) - 10%), calc(var(--frame-background-l) + 25%))}.colorful-frame.theme-dark{--tab-outline-width:0px}.colorful-frame,.colorful-frame.is-focused{--frame-divider-color:var(--frame-outline-color);--titlebar-background:var(--frame-background);--titlebar-background-focused:var(--frame-background);--titlebar-text-color:var(--frame-muted-color);--minimal-tab-text-color:var(--frame-muted-color)}.colorful-frame .workspace-tabs:not(.mod-stacked),.colorful-frame.is-focused .workspace-tabs:not(.mod-stacked){--tab-text-color:var(--minimal-tab-text-color);--tab-text-color-focused:var(--minimal-tab-text-color)}.colorful-frame .mod-top .workspace-tab-header-container,.colorful-frame .titlebar,.colorful-frame .workspace-ribbon.mod-left:before,.colorful-frame.is-focused .mod-top .workspace-tab-header-container,.colorful-frame.is-focused .titlebar,.colorful-frame.is-focused .workspace-ribbon.mod-left:before{--tab-outline-color:var(--frame-outline-color);--tab-divider-color:var(--frame-outline-color)}.colorful-frame .mod-root .workspace-tab-header .workspace-tab-header-inner-icon,.colorful-frame.is-focused .mod-root .workspace-tab-header .workspace-tab-header-inner-icon{--icon-color:var(--minimal-tab-text-color-active);--icon-color-hover:var(--minimal-tab-text-color-active);--icon-color-active:var(--minimal-tab-text-color-active);--icon-color-focused:var(--minimal-tab-text-color-active)}.colorful-frame .mod-left-split .mod-top .workspace-tab-header,.colorful-frame .mod-right-split .mod-top .workspace-tab-header,.colorful-frame .sidebar-toggle-button,.colorful-frame .workspace-tab-header-new-tab,.colorful-frame .workspace-tab-header-tab-list,.colorful-frame .workspace-tab-header:not(.is-active),.colorful-frame.is-focused .mod-left-split .mod-top .workspace-tab-header,.colorful-frame.is-focused .mod-right-split .mod-top .workspace-tab-header,.colorful-frame.is-focused .sidebar-toggle-button,.colorful-frame.is-focused .workspace-tab-header-new-tab,.colorful-frame.is-focused .workspace-tab-header-tab-list,.colorful-frame.is-focused .workspace-tab-header:not(.is-active){--background-modifier-hover:var(--frame-outline-color);--icon-color:var(--frame-icon-color);--icon-color-hover:var(--frame-icon-color);--icon-color-active:var(--frame-icon-color);--icon-color-focused:var(--frame-icon-color);--icon-color-focus:var(--frame-icon-color)}.colorful-frame .mod-left-split .mod-top .workspace-tab-header.is-active .workspace-tab-header-inner-icon,.colorful-frame .mod-right-split .mod-top .workspace-tab-header.is-active .workspace-tab-header-inner-icon,.colorful-frame.is-focused .mod-left-split .mod-top .workspace-tab-header.is-active .workspace-tab-header-inner-icon,.colorful-frame.is-focused .mod-right-split .mod-top .workspace-tab-header.is-active .workspace-tab-header-inner-icon{color:var(--frame-icon-color)}.workspace-leaf-resize-handle{transition:none}.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-left-split>.workspace-leaf-resize-handle,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-right-split>.workspace-leaf-resize-handle,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-vertical>*>.workspace-leaf-resize-handle{-webkit-app-region:no-drag;border:0;z-index:15}.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-left-split>.workspace-leaf-resize-handle:after,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-right-split>.workspace-leaf-resize-handle:after,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-vertical>*>.workspace-leaf-resize-handle:after{content:"";height:100%;width:1px;background:linear-gradient(180deg,var(--frame-outline-color) var(--header-height),var(--divider-color) var(--header-height));top:0;position:absolute}.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-left-split>.workspace-leaf-resize-handle:hover:after,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-right-split>.workspace-leaf-resize-handle:hover:after,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-vertical>*>.workspace-leaf-resize-handle:hover:after{background:var(--divider-color-hover)}.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-right-split>.workspace-leaf-resize-handle:after{left:0}.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-left-split>.workspace-leaf-resize-handle:after,.colorful-frame.is-hidden-frameless:not(.minimal-focus-mode) .workspace-split.mod-vertical>*>.workspace-leaf-resize-handle:after{right:0}body.colorful-headings{--h1-color:var(--color-red);--h2-color:var(--color-orange);--h3-color:var(--color-yellow);--h4-color:var(--color-green);--h5-color:var(--color-blue);--h6-color:var(--color-purple)}body.colorful-headings .modal{--h1-color:var(--text-normal);--h2-color:var(--text-normal);--h3-color:var(--text-normal);--h4-color:var(--text-normal);--h5-color:var(--text-normal);--h6-color:var(--text-normal)}.is-mobile .tree-item-self .collapse-icon{width:20px}body:not(.minimal-icons-off) svg.calendar-day,body:not(.minimal-icons-off) svg.excalidraw-icon,body:not(.minimal-icons-off) svg.globe,body:not(.minimal-icons-off) svg.longform,body:not(.minimal-icons-off) svg.obsidian-leaflet-plugin-icon-map{background-color:currentColor}body:not(.minimal-icons-off) svg.excalidraw-icon path{display:none}body:not(.minimal-icons-off) svg.bar-graph{-webkit-mask-image:url('data:image/svg+xml;utf8,')}body:not(.minimal-icons-off) svg.excalidraw-icon{-webkit-mask-image:url('data:image/svg+xml;utf8,')}body:not(.minimal-icons-off) svg.longform{-webkit-mask-image:url('data:image/svg+xml;utf8,')}.workspace-ribbon.mod-left{border-left:0;transition:none}.minimal-focus-mode.is-translucent .workspace-ribbon.mod-left.is-collapsed,.minimal-focus-mode.is-translucent .workspace-ribbon.mod-left.is-collapsed:before{background-color:var(--background-primary)!important}.minimal-focus-mode .workspace-ribbon.mod-left{transition:background-color 0s linear 0s}.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed{border-color:transparent;background-color:var(--background-primary)}.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed:before{background-color:var(--background-primary);border-color:transparent}.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed .side-dock-actions,.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed .side-dock-settings{opacity:0;transition:opacity .1s ease-in-out .1s}.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed:hover .side-dock-actions,.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed:hover .side-dock-settings{opacity:1;transition:opacity .1s ease-in-out .1s}.minimal-focus-mode.borders-title .workspace-ribbon.mod-left.is-collapsed{border-right:none}.minimal-focus-mode .mod-top-right-space .sidebar-toggle-button.mod-right{opacity:0}.minimal-focus-mode:not(.minimal-status-off) .status-bar{opacity:0;transition:opacity .2s ease-in-out}.minimal-focus-mode .status-bar:hover{opacity:1;transition:opacity .2s ease-in-out}.minimal-focus-mode .mod-root .workspace-tabs{position:relative}.minimal-focus-mode .mod-root .workspace-tabs:before:hover{background-color:#00f}.minimal-focus-mode .mod-root .workspace-tab-header-container{height:0;transition:all .1s linear .6s;--tab-outline-width:0px}.minimal-focus-mode .mod-root .workspace-tab-header-container .workspace-tab-header-container-inner,.minimal-focus-mode .mod-root .workspace-tab-header-container .workspace-tab-header-new-tab,.minimal-focus-mode .mod-root .workspace-tab-header-container .workspace-tab-header-tab-list{opacity:0;transition:opacity .1s linear .6s}.minimal-focus-mode .mod-root .workspace-tab-header-container .workspace-tab-header-spacer:before{width:100%;content:" ";background-color:transparent;height:15px;position:absolute;z-index:100;top:0;left:0}.minimal-focus-mode .mod-root .workspace-tab-header-container:hover{height:var(--header-height);--tab-outline-width:1px;transition:all .1s linear 50ms}.minimal-focus-mode .mod-root .workspace-tab-header-container:hover .workspace-tab-header-container-inner,.minimal-focus-mode .mod-root .workspace-tab-header-container:hover .workspace-tab-header-new-tab,.minimal-focus-mode .mod-root .workspace-tab-header-container:hover .workspace-tab-header-tab-list{opacity:1;transition:opacity .1s linear 50ms}.minimal-focus-mode.mod-macos:not(.is-fullscreen) .workspace:not(.is-left-sidedock-open) .mod-root .workspace-tabs.mod-stacked .workspace-tab-container .workspace-tab-header-inner{padding-top:30px}body.show-view-header .app-container .workspace-split.mod-root>.workspace-leaf .view-header{transition:height .1s linear .1s}body.minimal-focus-mode.show-view-header .mod-root .workspace-leaf .view-header{height:0;transition:all .1s linear .5s}body.minimal-focus-mode.show-view-header .view-header::after{width:100%;content:" ";background-color:transparent;height:40px;position:absolute;z-index:-9;top:0}body.minimal-focus-mode.show-view-header .view-actions,body.minimal-focus-mode.show-view-header .view-header-nav-buttons,body.minimal-focus-mode.show-view-header .view-header-title-container{opacity:0;transition:all .1s linear .5s}body.minimal-focus-mode.show-view-header .mod-root .workspace-leaf .view-header:focus-within,body.minimal-focus-mode.show-view-header .mod-root .workspace-leaf .view-header:hover,body.minimal-focus-mode.show-view-header .mod-root .workspace-tab-header-container:hover~.workspace-tab-container .view-header{height:calc(var(--header-height) + 2px);transition:all .1s linear .1s}body.minimal-focus-mode.show-view-header .mod-root .workspace-tab-header-container:hover~.workspace-tab-container .view-header .view-actions,body.minimal-focus-mode.show-view-header .mod-root .workspace-tab-header-container:hover~.workspace-tab-container .view-header .view-header-nav-buttons,body.minimal-focus-mode.show-view-header .mod-root .workspace-tab-header-container:hover~.workspace-tab-container .view-header .view-header-title-container,body.minimal-focus-mode.show-view-header .view-header:focus-within .view-actions,body.minimal-focus-mode.show-view-header .view-header:focus-within .view-header-nav-buttons,body.minimal-focus-mode.show-view-header .view-header:focus-within .view-header-title-container,body.minimal-focus-mode.show-view-header .view-header:hover .view-actions,body.minimal-focus-mode.show-view-header .view-header:hover .view-header-nav-buttons,body.minimal-focus-mode.show-view-header .view-header:hover .view-header-title-container{opacity:1;transition:all .1s linear .1s}body.minimal-focus-mode.show-view-header .view-content{height:100%}.full-width-media{--iframe-width:100%}.full-width-media .markdown-preview-view .external-embed,.full-width-media .markdown-preview-view .image-embed img:not(.link-favicon):not(.emoji):not([width]),.full-width-media .markdown-preview-view audio,.full-width-media .markdown-preview-view img:not(.link-favicon):not(.emoji):not([width]),.full-width-media .markdown-preview-view p:has(.external-embed),.full-width-media .markdown-preview-view video,.full-width-media .markdown-source-view .external-embed,.full-width-media .markdown-source-view .image-embed img:not(.link-favicon):not(.emoji):not([width]),.full-width-media .markdown-source-view audio,.full-width-media .markdown-source-view img:not(.link-favicon):not(.emoji):not([width]),.full-width-media .markdown-source-view p:has(.external-embed),.full-width-media .markdown-source-view video{width:100%}.markdown-rendered img:not(.emoji),.markdown-rendered video,.markdown-source-view img:not(.emoji),.markdown-source-view video{border-radius:var(--image-radius)}.table-small table:not(.calendar){--table-text-size:85%}.table-tiny table:not(.calendar){--table-text-size:75%}.row-hover{--table-edge-cell-padding-first:10px}.row-alt{--table-row-alt-background:var(--background-table-rows);--table-edge-cell-padding-first:10px}.col-alt .markdown-rendered:not(.cards){--table-column-alt-background:var(--background-table-rows)}.table-tabular table:not(.calendar){font-variant-numeric:tabular-nums}.table-lines{--table-border-width:var(--border-width);--table-header-border-width:var(--border-width);--table-column-first-border-width:var(--border-width);--table-column-last-border-width:var(--border-width);--table-row-last-border-width:var(--border-width);--table-edge-cell-padding:10px}.table-nowrap{--table-white-space:nowrap}.table-nowrap .table-wrap,.trim-cols{--table-white-space:normal}.table-numbers table:not(.calendar){counter-reset:section}.table-numbers table:not(.calendar)>thead>tr>th:first-child::before{content:" ";padding-right:.5em;display:inline-block;min-width:2em}.table-numbers table:not(.calendar)>tbody>tr>td:first-child::before{counter-increment:section;content:counter(section) " ";text-align:center;padding-right:.5em;display:inline-block;min-width:2em;color:var(--text-faint);font-variant-numeric:tabular-nums}.row-lines-off .table-view-table>tbody>tr>td,.row-lines-off table:not(.calendar) tbody>tr:last-child>td,.row-lines-off table:not(.calendar) tbody>tr>td{border-bottom:none}.row-lines .table-view-table>tbody>tr>td,.row-lines table:not(.calendar) tbody>tr>td{border-bottom:var(--table-border-width) solid var(--table-border-color)}.row-lines table:not(.calendar) tbody>tr:last-child>td{border-bottom:none}.col-lines .table-view-table thead>tr>th:not(:last-child),.col-lines .table-view-table>tbody>tr>td:not(:last-child),.col-lines table:not(.calendar) tbody>tr>td:not(:last-child){border-right:var(--table-border-width) solid var(--background-modifier-border)}.row-hover{--table-row-background-hover:hsla(var(--accent-h), 50%, 80%, 20%)}.theme-dark .row-hover,.theme-dark.row-hover{--table-row-background-hover:hsla(var(--accent-h), 30%, 40%, 20%)}:root{--image-mix:normal}.image-blend-light{--image-mix:multiply}.theme-dark .markdown-preview-view img,.theme-dark .markdown-source-view img{opacity:var(--image-muted);transition:opacity .25s linear}@media print{body{--image-muted:1}}.theme-dark .markdown-preview-view img:hover,.theme-dark .markdown-source-view img:hover,.theme-dark .print-preview img{opacity:1;transition:opacity .25s linear}.theme-light img{mix-blend-mode:var(--image-mix)}div[src$="#invert"],div[src$="#multiply"]{background-color:var(--background-primary)}.theme-dark div[src$="#invert"] img,.theme-dark img[src$="#invert"],.theme-dark span[src$="#invert"] img{filter:invert(1) hue-rotate(180deg);mix-blend-mode:screen}.theme-dark div[src$="#multiply"] img,.theme-dark img[src$="#multiply"],.theme-dark span[src$="#multiply"] img{mix-blend-mode:screen}.theme-light div[src$="#multiply"] img,.theme-light img[src$="#multiply"],.theme-light span[src$="#multiply"] img{mix-blend-mode:multiply}.theme-light div[src$="#invertW"] img,.theme-light img[src$="#invertW"],.theme-light span[src$=invertW] img{filter:invert(1) hue-rotate(180deg)}img[src$="#circle"],span[src$="#circle"] img,span[src$="#round"] img{border-radius:50%;aspect-ratio:1/1}img[src$="#outline"],span[src$="#outline"] img{border:1px solid var(--ui1)}img[src$="#interface"],span[src$="#interface"] img{border:1px solid var(--ui1);box-shadow:0 .5px .9px rgba(0,0,0,.021),0 1.3px 2.5px rgba(0,0,0,.03),0 3px 6px rgba(0,0,0,.039),0 10px 20px rgba(0,0,0,.06);margin-top:10px;margin-bottom:15px;border-radius:var(--radius-m)}body{--image-grid-fit:cover;--image-grid-background:transparent;--img-grid-gap:0.5rem}@media (max-width:400pt){body{--img-grid-gap:0.25rem}}.img-grid-ratio{--image-grid-fit:contain}.img-grid .image-embed.is-loaded{line-height:0}.img-grid .image-embed.is-loaded img{background-color:var(--image-grid-background)}.img-grid .image-embed.is-loaded img:active{background-color:transparent}.img-grid .markdown-preview-section>div:has(.image-embed)>p{display:grid;margin-block-start:var(--img-grid-gap);margin-block-end:var(--img-grid-gap);grid-column-gap:var(--img-grid-gap);grid-row-gap:0;grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.img-grid .markdown-preview-section>div:has(.image-embed)>p>br{display:none}.img-grid .markdown-preview-section>div:has(.image-embed)>p>img{object-fit:var(--image-grid-fit);align-self:stretch}.img-grid .markdown-preview-section>div:has(.image-embed)>p>.internal-embed img{object-fit:var(--image-grid-fit);height:100%;align-self:center}body:not(.zoom-off) .view-content div:not(.canvas-node-content) img{max-width:100%;cursor:zoom-in}body:not(.zoom-off) .view-content img:active{cursor:zoom-out}body:not(.zoom-off) .view-content .markdown-preview-view img[referrerpolicy=no-referrer]:active{background-color:var(--background-primary);padding:10px}body:not(.zoom-off) .view-content .image-embed:not(.canvas-node-content):active,body:not(.zoom-off) .view-content .markdown-preview-view img[referrerpolicy=no-referrer]:active{--container-img-width:100%;--container-img-max-width:100%;aspect-ratio:unset;cursor:zoom-out;display:block;z-index:200;position:fixed;max-height:calc(100% + 1px);max-width:100%;height:calc(100% + 1px);width:100%;object-fit:contain;margin:-.5px auto 0!important;text-align:center;padding:0;left:0;right:0;bottom:0}body:not(.zoom-off) .view-content .image-embed:not(.canvas-node-content):active:after{background-color:var(--background-primary);opacity:.9;content:" ";height:calc(100% + 1px);width:100%;position:fixed;left:0;right:1px;z-index:0}body:not(.zoom-off) .view-content .image-embed:not(.canvas-node-content):active img{aspect-ratio:unset;top:50%;z-index:99;transform:translateY(-50%);padding:0;margin:0 auto;width:calc(100% - 20px);max-height:95vh;object-fit:contain;left:0;right:0;bottom:0;position:absolute;opacity:1}.labeled-nav.is-fullscreen:not(.colorful-frame),.labeled-nav.mod-windows{--labeled-nav-top-margin:0}.labeled-nav{--labeled-nav-top-margin:var(--header-height)}.labeled-nav.is-translucent .mod-left-split .mod-top .workspace-tab-header-container .workspace-tab-header-container-inner{background-color:transparent}.labeled-nav.is-hidden-frameless:not(.is-fullscreen) .mod-left-split .workspace-tabs.mod-top-left-space .workspace-tab-header-container{padding-left:0}.labeled-nav.mod-macos .mod-left-split .mod-top .workspace-tab-header-container:before,.labeled-nav.mod-macos.is-hidden-frameless:not(.is-fullscreen) .mod-left-split .mod-top .workspace-tab-header-container:before{-webkit-app-region:drag;position:absolute;width:calc(100% - var(--divider-width));height:calc(var(--header-height) - var(--tab-outline-width));border-bottom:0 solid var(--tab-outline-color)}.labeled-nav.mod-macos.is-hidden-frameless:not(.is-fullscreen) .workspace-ribbon.mod-left:not(.is-collapsed){border:none;--tab-outline-width:0px}.labeled-nav.colorful-frame.is-hidden-frameless:not(.is-fullscreen) .mod-left-split .mod-top .workspace-tab-header-container:before,.labeled-nav.mod-macos:not(.hider-ribbon) .mod-left-split .mod-top .workspace-tab-header-container:before,.labeled-nav:not(.is-hidden-frameless) .mod-left-split .mod-top .workspace-tab-header-container:before{border-bottom:var(--tab-outline-width) solid var(--tab-outline-color)}.labeled-nav.colorful-frame.is-hidden-frameless:not(.is-fullscreen) .workspace-ribbon.mod-left:not(.is-collapsed),.labeled-nav.mod-macos:not(.hider-ribbon) .workspace-ribbon.mod-left:not(.is-collapsed),.labeled-nav:not(.is-hidden-frameless) .workspace-ribbon.mod-left:not(.is-collapsed){--tab-outline-width:1px}.labeled-nav:not(.is-hidden-frameless) .mod-left-split .mod-top .workspace-tab-header-container:before{position:absolute;top:0;content:" "}.labeled-nav.hider-ribbon.mod-macos.is-hidden-frameless:not(.is-fullscreen):not(.is-popout-window) .mod-left-split:not(.is-sidedock-collapsed) .workspace-tabs.mod-top-left-space .workspace-tab-header-container{padding-left:0}.labeled-nav:not(.is-grabbing):not(.is-fullscreen).is-hidden-frameless .mod-top .workspace-tab-header-container{-webkit-app-region:no-drag}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-spacer{display:none}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-inner-title{display:inline-block;font-weight:500;font-size:var(--font-adaptive-smaller)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-container{position:relative;flex-direction:column-reverse!important;height:auto;width:100%}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-container .sidebar-toggle-button.mod-left{position:absolute;justify-content:flex-end;padding-right:var(--size-4-2);top:0;right:0}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-container .workspace-tab-header-container-inner{padding-top:var(--size-4-2);margin-top:var(--labeled-nav-top-margin);flex-direction:column!important;background-color:var(--background-secondary)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-container .workspace-tab-container-inner{flex-grow:1;gap:0;padding:var(--size-4-2) var(--size-4-3)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header{--icon-color:var(--text-muted);--tab-text-color:var(--text-muted);--tab-text-color-focused:var(--text-muted);padding:0;margin-bottom:2px;border:none;height:auto}.labeled-nav .mod-left-split .mod-top .workspace-tab-header.is-active:not(:hover){background-color:transparent}.labeled-nav .mod-left-split .mod-top .workspace-tab-header.is-active,.labeled-nav .mod-left-split .mod-top .workspace-tab-header:hover{opacity:1;--tab-text-color-active:var(--text-normal);--tab-text-color-focused:var(--text-normal);--tab-text-color-focused-active:var(--text-normal);--tab-text-color-focused-active-current:var(--text-normal);--icon-color:var(--text-normal)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header .workspace-tab-header-inner{gap:var(--size-2-3);padding:var(--size-4-1) var(--size-4-2);box-shadow:none;border:none}.labeled-nav .mod-left-split .mod-top .workspace-tab-header.has-active-menu:hover,.labeled-nav .mod-left-split .mod-top .workspace-tab-header.is-active:hover{background-color:transparent}.labeled-nav .mod-left-split .mod-top .workspace-tab-header.is-active:hover .workspace-tab-header-inner,.labeled-nav .mod-left-split .mod-top .workspace-tab-header:not(.is-active):hover .workspace-tab-header-inner{background-color:var(--nav-item-background-hover)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header.is-active .workspace-tab-header-inner-icon,.labeled-nav .mod-left-split .mod-top .workspace-tab-header:hover .workspace-tab-header-inner-icon{color:var(--icon-color-active)}.labeled-nav .mod-left-split .mod-top .workspace-tab-header-container{border:none;padding:0}body:not(.links-int-on){--link-decoration:none}body:not(.links-ext-on){--link-external-decoration:none}body:not(.sidebar-color) .mod-right-split{--background-secondary:var(--background-primary)}body:not(.sidebar-color) .mod-right-split :not(.mod-top) .workspace-tab-header-container{--tab-container-background:var(--background-primary)}body{--minimal-tab-text-color:var(--text-muted);--minimal-tab-text-color-active:var(--text-normal)}.workspace-tabs:not(.mod-stacked){--tab-text-color:var(--minimal-tab-text-color);--tab-text-color-focused:var(--minimal-tab-text-color);--tab-text-color-active:var(--minimal-tab-text-color-active);--tab-text-color-focused-active:var(--minimal-tab-text-color-active);--tab-text-color-focused-active-current:var(--minimal-tab-text-color-active)}.tabs-plain-square .mod-root{--tab-curve:0;--tab-radius:0;--tab-radius-active:0}.tabs-plain-square .mod-root .workspace-tab-header-container{padding-right:0}.tabs-plain-square .mod-root .workspace-tab-header-container-inner{margin-top:-1px;margin-left:-15px}.tabs-plain-square .mod-root .workspace-tab-header{padding:0}.tabs-plain-square .mod-root .workspace-tab-header-inner{padding:0 8px}.tabs-square .mod-root{--tab-curve:0;--tab-radius:0;--tab-radius-active:0}.tabs-underline .mod-root{--tab-curve:0;--tab-radius:0;--tab-radius-active:0;--tab-outline-width:0px;--tab-background-active:transparent}.tabs-underline .mod-root .workspace-tab-header-container{border-bottom:1px solid var(--divider-color)}.tabs-underline .mod-root .workspace-tab-header{border-bottom:2px solid transparent}.tabs-underline .mod-root .workspace-tab-header:hover{border-bottom:2px solid var(--ui2)}.tabs-underline .mod-root .workspace-tab-header:hover .workspace-tab-header-inner{background-color:transparent}.tabs-underline .mod-root .workspace-tab-header.is-active{border-bottom:2px solid var(--ax3)}.tabs-underline .mod-root .workspace-tab-header-inner:hover{background-color:transparent}body:not(.sidebar-tabs-underline):not(.sidebar-tabs-index):not(.sidebar-tabs-square) .workspace>.workspace-split:not(.mod-root) .workspace-tabs:not(.mod-top) .workspace-tab-header-container{--tab-outline-width:0}.tabs-modern.colorful-frame .mod-root .mod-top.workspace-tabs:not(.mod-stacked){--tab-background:var(--frame-outline-color);--tab-outline-width:1px}.tabs-modern.colorful-frame .mod-root .mod-top.workspace-tabs:not(.mod-stacked) .workspace-tab-header.is-active .workspace-tab-header-inner-close-button,.tabs-modern.colorful-frame .mod-root .mod-top.workspace-tabs:not(.mod-stacked) .workspace-tab-header:hover .workspace-tab-header-inner-close-button{color:var(--minimal-tab-text-color-active)}.tabs-modern.minimal-focus-mode .mod-root .workspace-tab-header-container:hover{--tab-outline-width:0px}.tabs-modern .mod-root{--tab-container-background:var(--background-primary)}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked){--tab-background:var(--background-modifier-hover);--tab-height:calc(var(--header-height) - 14px);--tab-outline-width:0px}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header-inner::after,.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header::after,.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header::before{display:none}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header-container-inner{align-items:center;margin:0;padding:2px var(--size-4-2) 0 var(--size-4-1)}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header-inner-title{text-overflow:ellipsis;-webkit-mask-image:none}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header{background:0 0;border-radius:5px;border:none;box-shadow:none;height:var(--tab-height);margin-left:var(--size-4-1);padding:0}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header.is-active .workspace-tab-header-inner-title{color:var(--tab-text-color-active)}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header.is-active.mod-active,.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header:hover{opacity:1;background-color:var(--tab-background)}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header-new-tab{margin-right:0}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header-inner{padding:0 var(--size-4-1) 0 var(--size-4-2);border:1px solid transparent}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header:not(.is-active):hover .workspace-tab-header-inner{background-color:transparent}.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header.is-active:not(.mod-active) .workspace-tab-header-inner,.tabs-modern .mod-root .workspace-tabs:not(.mod-stacked) .workspace-tab-header:not(:hover):not(.mod-active) .workspace-tab-header-inner{border:1px solid var(--tab-outline-color)}.tabs-modern.sidebar-tabs-default .mod-right-split,.tabs-modern.sidebar-tabs-wide .mod-right-split{--tab-outline-width:0}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-spacer,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-spacer,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-spacer{display:none}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container{padding-right:0}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container-inner,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container-inner,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container-inner{padding:0;margin:0;flex-grow:1;gap:0}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header{flex-grow:1;border-radius:0;max-width:100px}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header.is-active,.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header:hover,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header.is-active,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header:hover,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header.is-active,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header:hover{background-color:transparent}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner,.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header:hover .workspace-tab-header-inner,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header:hover .workspace-tab-header-inner,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header:hover .workspace-tab-header-inner{background-color:transparent}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner{border-bottom:2px solid transparent;border-radius:0}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner:hover,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner:hover,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner:hover{border-color:var(--ui2)}.sidebar-tabs-underline .mod-right-split .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner,.sidebar-tabs-underline.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner,.sidebar-tabs-underline:not(.labeled-nav) .mod-left-split .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner{border-color:var(--ax3);padding-top:1px}.sidebar-tabs-square .mod-left-split,.sidebar-tabs-square .mod-right-split{--tab-radius:0px}.sidebar-tabs-index.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top),.sidebar-tabs-index:not(.labeled-nav) .mod-left-split,.sidebar-tabs-square.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top),.sidebar-tabs-square:not(.labeled-nav) .mod-left-split{--tab-background-active:var(--background-secondary)}.sidebar-tabs-index .mod-right-split .workspace-tab-header-container-inner,.sidebar-tabs-index.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container-inner,.sidebar-tabs-index:not(.labeled-nav) .mod-left-split .workspace-tab-header-container-inner,.sidebar-tabs-square .mod-right-split .workspace-tab-header-container-inner,.sidebar-tabs-square.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container-inner,.sidebar-tabs-square:not(.labeled-nav) .mod-left-split .workspace-tab-header-container-inner{padding:1px var(--size-4-2) 0;margin:6px 0 calc(var(--tab-outline-width) * -1);flex-grow:1}.sidebar-tabs-index .mod-right-split .workspace-tab-header,.sidebar-tabs-index.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header,.sidebar-tabs-index:not(.labeled-nav) .mod-left-split .workspace-tab-header,.sidebar-tabs-square .mod-right-split .workspace-tab-header,.sidebar-tabs-square.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header,.sidebar-tabs-square:not(.labeled-nav) .mod-left-split .workspace-tab-header{flex-grow:1;max-width:100px;border-radius:var(--tab-radius) var(--tab-radius) 0 0}.sidebar-tabs-index .mod-right-split .workspace-tab-header.is-active,.sidebar-tabs-index.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header.is-active,.sidebar-tabs-index:not(.labeled-nav) .mod-left-split .workspace-tab-header.is-active,.sidebar-tabs-square .mod-right-split .workspace-tab-header.is-active,.sidebar-tabs-square.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header.is-active,.sidebar-tabs-square:not(.labeled-nav) .mod-left-split .workspace-tab-header.is-active{box-shadow:0 0 0 var(--tab-outline-width) var(--tab-outline-color);color:var(--tab-text-color-active);background-color:var(--tab-background-active)}.sidebar-tabs-wide .mod-right-split .workspace-tab-header-container-inner,.sidebar-tabs-wide.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container-inner,.sidebar-tabs-wide:not(.labeled-nav) .mod-left-split .workspace-tab-header-container-inner{flex-grow:1;border:1px solid var(--tab-outline-color);padding:3px;margin:6px 8px 6px;border-radius:4px}.sidebar-tabs-wide .mod-right-split .workspace-tab-header,.sidebar-tabs-wide.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header,.sidebar-tabs-wide:not(.labeled-nav) .mod-left-split .workspace-tab-header{flex-grow:1}.sidebar-tabs-wide .mod-right-split .workspace-tab-header.is-active,.sidebar-tabs-wide.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header.is-active,.sidebar-tabs-wide:not(.labeled-nav) .mod-left-split .workspace-tab-header.is-active{border-color:transparent}.sidebar-tabs-wide .mod-right-split .workspace-tab-header-container,.sidebar-tabs-wide.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-container,.sidebar-tabs-wide:not(.labeled-nav) .mod-left-split .workspace-tab-header-container{padding-right:0}.sidebar-tabs-wide .mod-right-split .workspace-tab-header-spacer,.sidebar-tabs-wide.labeled-nav .mod-left-split .workspace-tabs:not(.mod-top) .workspace-tab-header-spacer,.sidebar-tabs-wide:not(.labeled-nav) .mod-left-split .workspace-tab-header-spacer{display:none}.full-file-names{--nav-item-white-space:normal}body:not(.full-file-names){--nav-item-white-space:nowrap}body:not(.full-file-names) .tree-item-self{white-space:nowrap}body:not(.full-file-names) .tree-item-inner{text-overflow:ellipsis;overflow:hidden}.theme-dark,.theme-light{--h1l:var(--ui1);--h2l:var(--ui1);--h3l:var(--ui1);--h4l:var(--ui1);--h5l:var(--ui1);--h6l:var(--ui1)}.h1-l .markdown-reading-view h1:not(.embedded-note-title),.h1-l .mod-cm6 .cm-editor .HyperMD-header-1{border-bottom:1px solid var(--h1l);padding-bottom:.4em;margin-block-end:0.6em}.h2-l .markdown-reading-view h2,.h2-l .mod-cm6 .cm-editor .HyperMD-header-2{border-bottom:1px solid var(--h2l);padding-bottom:.4em;margin-block-end:0.6em}.h3-l .markdown-reading-view h3,.h3-l .mod-cm6 .cm-editor .HyperMD-header-3{border-bottom:1px solid var(--h3l);padding-bottom:.4em;margin-block-end:0.6em}.h4-l .markdown-reading-view h4,.h4-l .mod-cm6 .cm-editor .HyperMD-header-4{border-bottom:1px solid var(--h4l);padding-bottom:.4em;margin-block-end:0.6em}.h5-l .markdown-reading-view h5,.h5-l .mod-cm6 .cm-editor .HyperMD-header-5{border-bottom:1px solid var(--h5l);padding-bottom:.4em;margin-block-end:0.6em}.h6-l .markdown-reading-view h6,.h6-l .mod-cm6 .cm-editor .HyperMD-header-6{border-bottom:1px solid var(--h6l);padding-bottom:.4em;margin-block-end:0.6em}.is-tablet .workspace-drawer{padding-top:0}.is-tablet .workspace-drawer:not(.is-pinned){margin:30px 16px 0;height:calc(100vh - 48px);border-radius:15px;border:none}.is-tablet .workspace-drawer-ribbon{background-color:var(--background-primary);border-right:1px solid var(--background-modifier-border)}.is-tablet .workspace-drawer-header,.is-tablet .workspace-drawer.is-pinned .workspace-drawer-header{padding-top:var(--size-4-4)}.is-mobile{--font-bold:600;--font-ui-medium:var(--font-adaptive-small);--interactive-normal:var(--background-secondary);--background-modifier-form-field:var(--background-secondary);--background-modifier-form-field-highlighted:var(--background-secondary)}.is-mobile .markdown-source-view.mod-cm6 .cm-gutters{margin-left:0}.is-mobile .workspace-drawer.mod-left.is-pinned{max-width:var(--mobile-left-sidebar-width);min-width:150pt}.is-mobile .workspace-drawer.mod-right.is-pinned{max-width:var(--mobile-right-sidebar-width);min-width:150pt}.backlink-pane>.tree-item-self,.backlink-pane>.tree-item-self:hover,.outgoing-link-pane>.tree-item-self,.outgoing-link-pane>.tree-item-self:hover{color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;font-size:var(--font-adaptive-smallest);font-weight:500}body{--canvas-dot-pattern:var(--background-modifier-border-hover)}.canvas-node-label{font-size:var(--font-adaptive-small)}.canvas-edges :not(.is-themed) path.canvas-display-path{stroke:var(--background-modifier-border-focus)}.canvas-edges :not(.is-themed) polyline.canvas-path-end{stroke:var(--background-modifier-border-focus);fill:var(--background-modifier-border-focus)}.canvas-node-container{border:1.5px solid var(--background-modifier-border-focus)}.node-insert-event.mod-inside-iframe{--max-width:100%;--folding-offset:0px}.node-insert-event.mod-inside-iframe .cm-editor .cm-content{padding-top:0}.is-mobile .nav-folder.mod-root>.nav-folder-title .nav-folder-title-content{display:none}body:not(.is-mobile) .nav-folder.mod-root>.nav-folder-title .nav-folder-title-content{font-weight:500;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-size:var(--font-adaptive-smallest)}.nav-buttons-container{justify-content:flex-start}.nav-file-tag{padding-top:.2em;background-color:transparent;color:var(--text-faint)}.nav-file .is-active .nav-file-tag,.nav-file:hover .nav-file-tag{color:var(--text-muted)}input.prompt-input,input.prompt-input:focus,input.prompt-input:focus-visible,input.prompt-input:hover{border-color:rgba(var(--mono-rgb-100),.05)}.is-mobile .mod-publish .modal-content{display:unset;padding:10px 10px 10px;margin-bottom:120px;overflow-x:hidden}.is-mobile .mod-publish .button-container,.is-mobile .modal.mod-publish .modal-button-container{padding:10px 15px 30px;margin-left:0;left:0}.is-mobile .modal.mod-publish .modal-title{padding:10px 20px;margin:0 -10px;border-bottom:1px solid var(--background-modifier-border)}.is-mobile .publish-site-settings-container{margin-right:0;padding:0}.is-mobile .modal.mod-publish .modal-content .publish-sections-container{margin-right:0;padding-right:0}@media (max-width:400pt){.is-mobile .publish-changes-info,.is-mobile .publish-section-header{flex-wrap:wrap;border:none}.is-mobile .publish-changes-info .publish-changes-add-linked-btn{flex-basis:100%;margin-top:10px}.is-mobile .publish-section-header-text{flex-basis:100%;margin-bottom:10px;margin-left:20px;margin-top:-8px}.is-mobile .publish-section{background:var(--background-secondary);border-radius:10px;padding:12px 12px 1px}.is-mobile .publish-changes-switch-site{flex-grow:0;margin-right:10px}}.release-notes-view .cm-scroller.is-readable-line-width{width:var(--line-width);max-width:var(--max-width);margin:0 auto}.search-results-info{border-bottom:none}@media (max-width:400pt){.is-mobile .modal.mod-sync-log{width:100vw;height:100vh;max-height:calc(100vh - 32px);box-shadow:0 -32px 0 32px var(--background-primary);bottom:0;padding-bottom:10px}}.obsidian-banner.solid{border-bottom:var(--divider-width) solid var(--divider-color)}.contextual-typography .markdown-preview-view div.has-banner-icon.obsidian-banner-wrapper{overflow:visible}.theme-dark .markdown-preview-view img.emoji{opacity:1}body.theme-dark .button-default,body.theme-light .button-default{border:none;box-shadow:none;height:var(--input-height);background:var(--background-modifier-hover);color:var(--text-normal);font-size:revert;font-weight:500;transform:none;transition:all .1s linear;padding:0 20px}body.theme-dark .button-default:hover,body.theme-light .button-default:hover{border:none;background:var(--background-modifier-border-hover);box-shadow:none;transform:none;transition:all .1s linear}body.theme-dark .button-default:active,body.theme-dark .button-default:focus,body.theme-light .button-default:active,body.theme-light .button-default:focus{box-shadow:none}body .button-default.blue{background-color:var(--color-blue)!important}.button-default.red{background-color:var(--color-red)!important}.button-default.green{background-color:var(--color-green)!important}.button-default.yellow{background-color:var(--color-yellow)!important}.button-default.purple{background-color:var(--color-purple)!important}.workspace-leaf-content[data-type=calendar] .view-content{padding:5px 0 0 0}.mod-root #calendar-container{width:var(--line-width);max-width:var(--max-width);margin:0 auto;padding:0}body{--calendar-dot-active:var(--text-faint);--calendar-dot-today:var(--text-accent)}#calendar-container{padding:0 var(--size-4-4) var(--size-4-1);--color-background-day-empty:var(--background-secondary-alt);--color-background-day-active:var(--background-modifier-hover);--color-background-day-hover:var(--background-modifier-hover);--color-dot:var(--text-faint);--calendar-text-active:inherit;--color-text-title:var(--text-normal);--color-text-heading:var(--text-muted);--color-text-day:var(--text-normal);--color-text-today:var(--text-normal);--color-arrow:var(--text-faint);--color-background-day-empty:transparent}#calendar-container .table{border-collapse:separate;table-layout:fixed}#calendar-container h2{font-weight:400;font-size:var(--h2)}#calendar-container .arrow{cursor:var(--cursor);width:22px;border-radius:4px;padding:3px 7px}#calendar-container .arrow svg{width:12px;height:12px;color:var(--text-faint);opacity:.7}#calendar-container .arrow:hover{fill:var(--text-muted);color:var(--text-muted);background-color:var(--background-modifier-hover)}#calendar-container .arrow:hover svg{color:var(--text-muted);opacity:1}#calendar-container tr th{padding:2px 0 4px;font-weight:500;letter-spacing:.1em;font-size:var(--font-adaptive-smallest)}#calendar-container tr th:first-child{padding-left:0!important}#calendar-container tr td{padding:2px 0 0 0;border-radius:var(--radius-m);cursor:var(--cursor);border:1px solid transparent;transition:none}#calendar-container tr td:first-child{padding-left:0!important}#calendar-container .nav{padding:0;margin:var(--size-4-2) var(--size-4-1)}#calendar-container .dot{margin:0}#calendar-container .month,#calendar-container .title,#calendar-container .year{font-size:calc(var(--font-adaptive-small) + 2px);font-weight:400;color:var(--text-normal)}#calendar-container .today,#calendar-container .today.active{color:var(--text-accent);font-weight:600}#calendar-container .today .dot,#calendar-container .today.active .dot{fill:var(--calendar-dot-today)}#calendar-container .active .task{stroke:var(--text-faint)}#calendar-container .active{color:var(--text-normal)}#calendar-container .reset-button{text-transform:none;letter-spacing:0;font-size:var(--font-adaptive-smaller);font-weight:500;color:var(--text-muted);border-radius:4px;margin:0;padding:2px 8px}#calendar-container .reset-button:hover{color:var(--text-normal);background-color:var(--background-modifier-hover)}#calendar-container .day,#calendar-container .reset-button,#calendar-container .week-num{cursor:var(--cursor)}#calendar-container .day.adjacent-month{color:var(--text-faint);opacity:1}#calendar-container .day{padding:2px 4px 4px}#calendar-container .day,#calendar-container .week-num{font-size:calc(var(--font-adaptive-smaller) + 5%)}#calendar-container .active,#calendar-container .active.today,#calendar-container .day:hover,#calendar-container .week-num:hover{background-color:var(--color-background-day-active);color:var(--calendar-text-active)}#calendar-container .active .dot{fill:var(--calendar-dot-active)}#calendar-container .active .task{stroke:var(--text-faint)}.block-language-chart canvas,.block-language-dataviewjs canvas{margin:1em 0}.theme-dark,.theme-light{--chart-color-1:var(--color-blue);--chart-color-2:var(--color-red);--chart-color-3:var(--color-yellow);--chart-color-4:var(--color-green);--chart-color-5:var(--color-orange);--chart-color-6:var(--color-purple);--chart-color-7:var(--color-cyan);--chart-color-8:var(--color-pink)}.checklist-plugin-main .group .classic,.checklist-plugin-main .group .compact,.checklist-plugin-main .group .page,.checklist-plugin-main .group svg{cursor:var(--cursor)}.workspace .view-content .checklist-plugin-main{padding:10px 10px 15px 15px;--todoList-togglePadding--compact:2px;--todoList-listItemMargin--compact:2px}.checklist-plugin-main .title{font-weight:400;color:var(--text-muted);font-size:var(--font-adaptive-small)}.checklist-plugin-main .group svg{fill:var(--text-faint)}.checklist-plugin-main .group svg:hover{fill:var(--text-normal)}.checklist-plugin-main .group .title:hover{color:var(--text-normal)}.checklist-plugin-main .group:not(:last-child){border-bottom:1px solid var(--background-modifier-border)}.checklist-plugin-main .group{padding:0 0 2px 0}.checklist-plugin-main .group .classic:last-child,.checklist-plugin-main .group .compact:last-child{margin-bottom:10px}.checklist-plugin-main .group .classic,.checklist-plugin-main .group .compact{font-size:var(--font-adaptive-small)}.checklist-plugin-main .group .classic,.checklist-plugin-main .group .compact{background:0 0;border-radius:0;margin:1px auto;padding:0}.checklist-plugin-main .group .classic .content{padding:0}.checklist-plugin-main .group .classic:hover,.checklist-plugin-main .group .compact:hover{background:0 0}.markdown-preview-view.checklist-plugin-main ul>li:not(.task-list-item)::before{display:none}.checklist-plugin-main .group .compact>.toggle .checked{background:var(--text-accent);top:-1px;left:-1px;height:18px;width:18px}.checklist-plugin-main .compact .toggle:hover{opacity:1!important}.checklist-plugin-main .group .count{font-size:var(--font-adaptive-smaller);padding:0;background:0 0;font-weight:400;color:var(--text-faint)}.checklist-plugin-main .group .group-header:hover .count{color:var(--text-muted)}.checklist-plugin-main .group .checkbox{border:1px solid var(--background-modifier-border-hover);min-height:18px;min-width:18px;height:18px;width:18px}.checklist-plugin-main .group .checkbox:hover{border:1px solid var(--background-modifier-border-focus)}.checklist-plugin-main button:active,.checklist-plugin-main button:focus,.checklist-plugin-main button:hover{box-shadow:none!important}.checklist-plugin-main button.collapse{padding:0}body:not(.is-mobile) .checklist-plugin-main button.collapse svg{width:18px;height:18px}.is-mobile .checklist-plugin-main .group-header .title{flex-grow:1;flex-shrink:0}.is-mobile .checklist-plugin-main button{width:auto}.is-mobile .checklist-plugin-main.markdown-preview-view ul{padding-inline-start:0}.is-mobile .workspace .view-content .checklist-plugin-main{padding-bottom:50px}body #cMenuModalBar{box-shadow:0 2px 20px var(--shadow-color)}body #cMenuModalBar .cMenuCommandItem{cursor:var(--cursor)}body #cMenuModalBar button.cMenuCommandItem:hover{background-color:var(--background-modifier-hover)}.MiniSettings-statusbar-button{padding-top:0;padding-bottom:0}.dataview-inline-lists .markdown-preview-view .dataview-ul,.dataview-inline-lists .markdown-source-view .dataview-ul{--list-spacing:0}.dataview-inline-lists .markdown-preview-view .dataview-ul li:not(:last-child):after,.dataview-inline-lists .markdown-source-view .dataview-ul li:not(:last-child):after{content:", "}.dataview-inline-lists .markdown-preview-view ul.dataview-ul>li::before,.dataview-inline-lists .markdown-source-view ul.dataview-ul>li::before{display:none}.dataview-inline-lists .markdown-preview-view .dataview-ul li,.dataview-inline-lists .markdown-source-view .dataview-ul li{display:inline-block;padding-right:.25em}.markdown-preview-view .table-view-table>thead>tr>th,body .table-view-table>thead>tr>th{font-weight:400;font-size:var(--table-text-size);color:var(--text-muted);border-bottom:var(--table-border-width) solid var(--table-border-color);cursor:var(--cursor)}table.dataview ul.dataview-ul{list-style:none;padding-inline-start:0;margin-block-start:0em!important;margin-block-end:0em!important}.markdown-preview-view:not(.cards) .table-view-table>tbody>tr>td,.markdown-source-view.mod-cm6:not(.cards) .table-view-table>tbody>tr>td{max-width:var(--max-col-width)}body .dataview.small-text{color:var(--text-faint)}body:not(.row-hover) .dataview.task-list-basic-item:hover,body:not(.row-hover) .dataview.task-list-item:hover,body:not(.row-hover) .table-view-table>tbody>tr:hover{background-color:transparent!important;box-shadow:none}body.row-hover .dataview.task-list-basic-item:hover,body.row-hover .dataview.task-list-item:hover,body.row-hover .table-view-table>tbody>tr:hover{background-color:var(--table-row-background-hover)!important}body .dataview-error{background-color:transparent}.dataview.dataview-error,.markdown-source-view.mod-cm6 .cm-content .dataview.dataview-error{color:var(--text-muted)}body div.dataview-error-box{min-height:0;border:none;background-color:transparent;font-size:var(--table-text-size);border-radius:var(--radius-m);padding:15px 0;justify-content:flex-start}body div.dataview-error-box p{margin-block-start:0;margin-block-end:0;color:var(--text-faint)}.block-language-dataviewjs:has(.dataview-error-box) table.dataview{display:none}.trim-cols .markdown-preview-view .table-view-table>tbody>tr>td,.trim-cols .markdown-source-view.mod-cm6 .table-view-table>tbody>tr>td,.trim-cols .markdown-source-view.mod-cm6 .table-view-table>thead>tr>th{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}ul .dataview .task-list-basic-item:hover,ul .dataview .task-list-item:hover{background-color:transparent;box-shadow:none}body .dataview.result-group{padding-left:0}body .dataview .inline-field-standalone-value,body .dataview.inline-field-key,body .dataview.inline-field-value{font-family:var(--font-text);font-size:calc(var(--font-adaptive-normal) - 2px);background:0 0;color:var(--text-muted)}body .dataview.inline-field-key{padding:0}body .dataview .inline-field-standalone-value{padding:0}body .dataview.inline-field-key::after{margin-left:3px;content:"|";color:var(--background-modifier-border)}body .dataview.inline-field-value{padding:0 1px 0 3px}.markdown-preview-view .block-language-dataview table.calendar th{border:none;cursor:default;background-image:none}.markdown-preview-view .block-language-dataview table.calendar .day{font-size:var(--font-adaptive-small)}.database-plugin__navbar,.database-plugin__scroll-container,.database-plugin__table{width:100%}.dbfolder-table-container{--font-adaptive-normal:var(--table-text-size);--font-size-text:12px}.database-plugin__cell_size_wide .database-plugin__td{padding:.15rem}.database-plugin__table{border-spacing:0!important}.MuiAppBar-root{background-color:transparent!important}.workspace-leaf-content .view-content.dictionary-view-content{padding:0}div[data-type=dictionary-view] .contents{padding-bottom:2rem}div[data-type=dictionary-view] .results>.container{background-color:transparent;margin-top:0;max-width:none;padding:0 10px}div[data-type=dictionary-view] .error,div[data-type=dictionary-view] .errorDescription{text-align:left;font-size:var(--font-adaptive-small);padding:10px 12px 0;margin:0}div[data-type=dictionary-view] .results>.container h3{text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-size:var(--font-adaptive-smallest);font-weight:500;padding:5px 7px 0 2px;margin-bottom:6px}div[data-type=dictionary-view] .container .main{border-radius:0;background-color:transparent;font-size:var(--font-adaptive-smaller);line-height:1.3;color:var(--text-muted);padding:5px 0 0}div[data-type=dictionary-view] .main .definition{padding:10px;border:1px solid var(--background-modifier-border);border-radius:5px;margin:10px 0 5px;background-color:var(--background-primary)}div[data-type=dictionary-view] .main .definition:last-child{border:1px solid var(--background-modifier-border)}div[data-type=dictionary-view] .main .synonyms{padding:10px 0 0}div[data-type=dictionary-view] .main .synonyms p{margin:0}div[data-type=dictionary-view] .main .definition>blockquote{margin:0}div[data-type=dictionary-view] .main .label{color:var(--text-normal);margin-bottom:2px;font-size:var(--font-adaptive-smaller);font-weight:500}div[data-type=dictionary-view] .main .mark{color:var(--text-normal);background-color:var(--text-selection);box-shadow:none}div[data-type=dictionary-view] .main>.opener{font-size:var(--font-adaptive-small);color:var(--text-normal);padding-left:5px}body .excalidraw,body .excalidraw.theme--dark{--color-primary-light:var(--text-selection);--color-primary:var(--interactive-accent);--color-primary-darker:var(--interactive-accent-hover);--color-primary-darkest:var(--interactive-accent-hover);--ui-font:var(--font-interface);--island-bg-color:var(--background-secondary);--icon-fill-color:var(--text-normal);--button-hover:var(--background-modifier-hover);--button-gray-1:var(--background-modifier-hover);--button-gray-2:var(--background-modifier-hover);--focus-highlight-color:var(--background-modifier-border-focus);--default-bg-color:var(--background-primary);--default-border-color:var(--background-modifier-border);--input-border-color:var(--background-modifier-border);--link-color:var(--text-accent);--overlay-bg-color:rgba(255, 255, 255, 0.88);--text-primary-color:var(--text-normal)}.git-view-body .opener{text-transform:uppercase;letter-spacing:.05em;font-size:var(--font-adaptive-smallest);font-weight:500;padding:5px 7px 5px 10px;margin-bottom:6px}.git-view-body .file-view .opener{text-transform:none;letter-spacing:normal;font-size:var(--font-adaptive-smallest);font-weight:400;padding:initial;margin-bottom:0}.git-view-body .file-view .opener .collapse-icon{display:flex!important;margin-left:-7px}.git-view-body{margin-top:6px}.git-view-body .file-view{margin-left:4px}.git-view-body .file-view main:hover{color:var(--text-normal)}.git-view-body .file-view .tools .type{display:none!important}.git-view-body .file-view .tools{opacity:0;transition:opacity .1s}.git-view-body .file-view main:hover>.tools{opacity:1}.git-view-body .staged{margin-bottom:12px}.git-view-body .opener.open{color:var(--text-normal)}div[data-type=git-view] .search-input-container{margin-left:0;width:100%}.git-view-body .opener .collapse-icon{display:none!important}.git-view-body main{background-color:var(--background-primary)!important;width:initial!important}.git-view-body .file-view>main:not(.topLevel){margin-left:7px}div[data-type=git-view] .commit-msg{min-height:2.5em!important;height:2.5em!important;padding:6.5px 8px!important}div[data-type=git-view] .search-input-clear-button{bottom:5.5px}.mod-macos.hider-frameless .workspace-ribbon{border:none}.is-tablet.hider-ribbon{--ribbon-width:0px}.is-tablet.hider-ribbon .side-dock-ribbon{display:none}.hider-ribbon .workspace-ribbon{padding:0}:root{--hider-ribbon-display:none}.ribbon-bottom-left-hover:not(.is-mobile){--hider-ribbon-display:flex}.hider-ribbon .workspace-ribbon-collapse-btn{display:none}.hider-ribbon .workspace-ribbon.mod-right{pointer-events:none}.hider-ribbon .workspace-ribbon.mod-left{position:absolute;border-right:0px;margin:0;height:var(--header-height);overflow:visible;flex-basis:0;bottom:0;top:auto;display:var(--hider-ribbon-display)!important;flex-direction:row;z-index:17;opacity:0;transition:opacity .25s ease-in-out;filter:drop-shadow(2px 10px 30px rgba(0, 0, 0, .2));gap:0}.hider-ribbon .side-dock-actions,.hider-ribbon .side-dock-settings{flex-direction:row;display:var(--hider-ribbon-display);border-top:var(--border-width) solid var(--background-modifier-border);background:var(--background-secondary);margin:0;position:relative;gap:var(--size-2-2)}.hider-ribbon .side-dock-actions{padding-left:8px}.hider-ribbon .side-dock-settings{border-right:var(--border-width) solid var(--background-modifier-border);border-top-right-radius:var(--radius-m);padding:0 var(--size-2-2)}.hider-ribbon .workspace-ribbon.mod-left .side-dock-ribbon-action{display:var(--hider-ribbon-display);margin:7px 0 8px}.hider-ribbon .workspace-ribbon.mod-left:hover{opacity:1;transition:opacity .25s ease-in-out}.hider-ribbon .workspace-ribbon.mod-left .workspace-ribbon-collapse-btn{opacity:0}.hider-ribbon .workspace-split.mod-left-split{margin:0}.hider-ribbon .workspace-leaf-content .item-list{padding-bottom:40px}.popover.hover-editor{--folding-offset:10px}.theme-dark,.theme-light{--he-title-bar-inactive-bg:var(--background-secondary);--he-title-bar-inactive-pinned-bg:var(--background-secondary);--he-title-bar-active-pinned-bg:var(--background-secondary);--he-title-bar-active-bg:var(--background-secondary);--he-title-bar-inactive-fg:var(--text-muted);--he-title-bar-active-fg:var(--text-normal);--he-title-bar-font-size:14px}.theme-light{--popover-shadow:0px 2.7px 3.1px rgba(0, 0, 0, 0.032),0px 5.9px 8.7px rgba(0, 0, 0, 0.052),0px 10.4px 18.1px rgba(0, 0, 0, 0.071),0px 20px 40px rgba(0, 0, 0, 0.11)}.theme-dark{--popover-shadow:0px 2.7px 3.1px rgba(0, 0, 0, 0.081),0px 5.9px 8.7px rgba(0, 0, 0, 0.131),0px 10.4px 18.1px rgba(0, 0, 0, 0.18),0px 20px 40px rgba(0, 0, 0, 0.28)}.popover.hover-editor:not(.snap-to-viewport){--max-width:92%}.popover.hover-editor:not(.snap-to-viewport) .markdown-preview-view,.popover.hover-editor:not(.snap-to-viewport) .markdown-source-view .cm-content{font-size:90%}body .popover.hover-editor:not(.is-loaded){box-shadow:var(--popover-shadow)}body .popover.hover-editor:not(.is-loaded) .markdown-preview-view{padding:15px 0 0 0}body .popover.hover-editor:not(.is-loaded) .view-content{height:100%;background-color:var(--background-primary)}body .popover.hover-editor:not(.is-loaded) .view-actions{height:auto}body .popover.hover-editor:not(.is-loaded) .popover-content{border:1px solid var(--background-modifier-border-hover)}body .popover.hover-editor:not(.is-loaded) .popover-titlebar{padding:0 4px}body .popover.hover-editor:not(.is-loaded) .popover-titlebar .popover-title{padding-left:4px;letter-spacing:-.02em;font-weight:var(--title-weight)}body .popover.hover-editor:not(.is-loaded) .markdown-embed{height:auto;font-size:unset;line-height:unset}body .popover.hover-editor:not(.is-loaded) .markdown-embed .markdown-preview-view{padding:0}body .popover.hover-editor:not(.is-loaded).show-navbar .popover-titlebar{border-bottom:var(--border-width) solid var(--background-modifier-border)}body .popover.hover-editor:not(.is-loaded) .popover-action,body .popover.hover-editor:not(.is-loaded) .popover-header-icon{cursor:var(--cursor);margin:4px 0;padding:4px 3px;border-radius:var(--radius-m);color:var(--icon-color)}body .popover.hover-editor:not(.is-loaded) .popover-action.mod-pin-popover,body .popover.hover-editor:not(.is-loaded) .popover-header-icon.mod-pin-popover{padding:4px 2px}body .popover.hover-editor:not(.is-loaded) .popover-action svg,body .popover.hover-editor:not(.is-loaded) .popover-header-icon svg{opacity:var(--icon-muted)}body .popover.hover-editor:not(.is-loaded) .popover-action:hover,body .popover.hover-editor:not(.is-loaded) .popover-header-icon:hover{background-color:var(--background-modifier-hover);color:var(--icon-color-hover)}body .popover.hover-editor:not(.is-loaded) .popover-action:hover svg,body .popover.hover-editor:not(.is-loaded) .popover-header-icon:hover svg{opacity:1;transition:opacity .1s ease-in-out}body .popover.hover-editor:not(.is-loaded) .popover-action.is-active,body .popover.hover-editor:not(.is-loaded) .popover-header-icon.is-active{color:var(--icon-color)}body.minimal-dark-black.theme-dark,body.minimal-dark-tonal.theme-dark,body.minimal-light-tonal.theme-light,body.minimal-light-white.theme-light,body.theme-dark{--kanban-border:0px}body:not(.is-mobile) .kanban-plugin__grow-wrap>textarea:focus{box-shadow:none}body:not(.minimal-icons-off) .kanban-plugin svg.cross{height:14px;width:14px}body .kanban-plugin__icon>svg,body .kanban-plugin__lane-settings-button svg{width:18px;height:18px}body .kanban-plugin{--kanban-border:var(--border-width);--interactive-accent:var(--text-selection);--interactive-accent-hover:var(--background-modifier-hover);--text-on-accent:var(--text-normal);background-color:var(--background-primary)}body .kanban-plugin__markdown-preview-view{font-family:var(--font-text)}body .kanban-plugin__board>div{margin:0 auto}body .kanban-plugin__checkbox-label{color:var(--text-muted)}body .kanban-plugin__item-markdown ul{margin:0}body .kanban-plugin__item-content-wrapper{box-shadow:none}body .kanban-plugin__grow-wrap::after,body .kanban-plugin__grow-wrap>textarea{padding:0;border:0;border-radius:0}body .kanban-plugin__grow-wrap::after,body .kanban-plugin__grow-wrap>textarea,body .kanban-plugin__item-title p,body .kanban-plugin__markdown-preview-view{font-size:var(--font-ui-medium);line-height:1.3}body .kanban-plugin__item{background-color:var(--background-primary)}body .kanban-plugin__item-title-wrapper{align-items:center}body .kanban-plugin__lane-form-wrapper{border:1px solid var(--background-modifier-border)}body .kanban-plugin__lane-header-wrapper{border-bottom:0}body .kanban-plugin__lane-header-wrapper .kanban-plugin__grow-wrap>textarea,body .kanban-plugin__lane-input-wrapper .kanban-plugin__grow-wrap>textarea,body .kanban-plugin__lane-title p{background:0 0;color:var(--text-normal);font-size:var(--font-ui-medium);font-weight:500}body .kanban-plugin__item-input-wrapper .kanban-plugin__grow-wrap>textarea{padding:0;border-radius:0;height:auto}body .kanban-plugin__item-form .kanban-plugin__grow-wrap{background-color:var(--background-primary)}body .kanban-plugin__item-input-wrapper .kanban-plugin__grow-wrap>textarea::placeholder{color:var(--text-faint)}body .kanban-plugin__item .kanban-plugin__item-edit-archive-button,body .kanban-plugin__item button.kanban-plugin__item-edit-button,body .kanban-plugin__item-settings-actions>button,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled,body .kanban-plugin__lane-action-wrapper>button{background:0 0;transition:color .1s ease-in-out}body .kanban-plugin__item .kanban-plugin__item-edit-archive-button:hover,body .kanban-plugin__item button.kanban-plugin__item-edit-button.is-enabled,body .kanban-plugin__item button.kanban-plugin__item-edit-button:hover,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button:hover{color:var(--text-normal);transition:color .1s ease-in-out;background:0 0}body .kanban-plugin__new-lane-button-wrapper{position:fixed;bottom:30px}body .kanban-plugin__lane-items>.kanban-plugin__placeholder:only-child{border:1px dashed var(--background-modifier-border);height:2em}body .kanban-plugin__item-postfix-button-wrapper{align-self:flex-start}body .kanban-plugin__item button.kanban-plugin__item-postfix-button.is-enabled,body .kanban-plugin__item button.kanban-plugin__item-prefix-button.is-enabled,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled{color:var(--text-muted)}body .kanban-plugin button{box-shadow:none;cursor:var(--cursor);height:auto}body .kanban-plugin__item button.kanban-plugin__item-postfix-button:hover,body .kanban-plugin__item button.kanban-plugin__item-prefix-button:hover,body .kanban-plugin__lane button.kanban-plugin__lane-settings-button:hover{background-color:var(--background-modifier-hover)}body .kanban-plugin__item-button-wrapper>button{color:var(--text-muted);font-weight:400;background:0 0;min-height:calc(var(--input-height) + 8px)}body .kanban-plugin__item-button-wrapper>button:hover{color:var(--text-normal);background:var(--background-modifier-hover)}body .kanban-plugin__item-button-wrapper>button:focus{box-shadow:none}body .kanban-plugin__item-button-wrapper{padding:1px 6px 5px;border-top:none}body .kanban-plugin__lane-setting-wrapper>div:last-child{border:none;margin:0}body .kanban-plugin.something-is-dragging{cursor:grabbing;cursor:-webkit-grabbing}body .kanban-plugin__item.is-dragging{box-shadow:0 5px 30px rgba(0,0,0,.15),0 0 0 2px var(--text-selection)}body .kanban-plugin__lane-items{border:var(--kanban-border) solid var(--background-modifier-border);padding:0 4px;margin:0;background-color:var(--background-secondary)}body .kanban-plugin__lane{background:0 0;padding:0;border:var(--border-width) solid transparent}body .kanban-plugin__lane.is-dragging{box-shadow:0 5px 30px rgba(0,0,0,.15);border:1px solid var(--background-modifier-border)}body .kanban-plugin__lane .kanban-plugin__item-button-wrapper{border-top-left-radius:8px;border-top-right-radius:8px;border-top:1px solid var(--background-modifier-border);border-bottom-width:0;padding:4px 4px 0 4px}body .kanban-plugin__lane.will-prepend .kanban-plugin__lane-items{border-radius:8px}body .kanban-plugin__lane.will-prepend .kanban-plugin__item-form{border-top:1px solid var(--background-modifier-border);border-radius:8px 8px 0 0;padding:4px 4px 0;border-bottom-width:0}body .kanban-plugin__lane.will-prepend .kanban-plugin__item-form+.kanban-plugin__lane-items{border-top-width:0;border-radius:0 0 8px 8px}body .kanban-plugin__lane.will-prepend .kanban-plugin__item-button-wrapper+.kanban-plugin__lane-items{border-top-width:0;border-radius:0 0 8px 8px}body .kanban-plugin__lane:not(.will-prepend) .kanban-plugin__item-button-wrapper,body .kanban-plugin__lane:not(.will-prepend) .kanban-plugin__item-form{border-top:none;border-radius:0 0 8px 8px}body .kanban-plugin__lane:not(.will-prepend) .kanban-plugin__item-button-wrapper{padding:0 4px 4px 4px;border-bottom-width:1px}body .kanban-plugin__lane:not(.will-prepend) .kanban-plugin__lane-items{border-bottom:none;border-top-width:1px;border-radius:8px 8px 0 0}body .kanban-plugin__item-form .kanban-plugin__item-input-wrapper{min-height:calc(var(--input-height) + 8px);display:flex;justify-content:center}body .kanban-plugin__item-button-wrapper,body .kanban-plugin__item-form{background-color:var(--background-secondary);border:var(--kanban-border) solid var(--background-modifier-border)}body .kanban-plugin__item-form{padding:0 4px 5px}body .kanban-plugin__markdown-preview-view ol,body .kanban-plugin__markdown-preview-view ol.contains-task-list .contains-task-list,body .kanban-plugin__markdown-preview-view ul,body .kanban-plugin__markdown-preview-view ul.contains-task-list .contains-task-list{padding-inline-start:1.8em!important}@media (max-width:400pt){.kanban-plugin__board{flex-direction:column!important}.kanban-plugin__lane{width:100%!important;margin-bottom:1rem!important}}body .cm-heading-marker{cursor:var(--cursor);padding-left:10px}.theme-light{--leaflet-buttons:var(--bg1);--leaflet-borders:rgba(0, 0, 0, 0.1)}.theme-dark{--leaflet-buttons:var(--bg2);--leaflet-borders:rgba(255, 255, 255, 0.1)}.leaflet-top{transition:top .1s linear}.mod-macos.minimal-focus-mode .mod-root .map-100 .markdown-preview-sizer.markdown-preview-section .el-lang-leaflet:nth-child(3) .leaflet-top{top:calc(18px + var(--ewt-traffic-light-y));transition:top .1s linear}body .leaflet-container{background-color:var(--background-secondary);font-family:var(--font-interface)}.map-100 .markdown-preview-sizer.markdown-preview-section .el-lang-leaflet:nth-child(3){margin-top:-16px}.leaflet-control-attribution{display:none}.leaflet-popup-content{margin:10px}.block-language-leaflet{border-radius:var(--radius-m);overflow:hidden;border:var(--border-width) solid var(--background-modifier-border)}.map-wide .block-language-leaflet{border-radius:var(--radius-l)}.map-max .block-language-leaflet{border-radius:var(--radius-xl)}.workspace-leaf-content[data-type=obsidian-leaflet-map-view] .block-language-leaflet{border-radius:0;border:none}.map-100 .block-language-leaflet{border-radius:0;border-left:none;border-right:none}.block-language-leaflet .leaflet-control-expandable-list .input-container .input-item>input{appearance:none}body .block-language-leaflet .leaflet-bar.disabled>a{background-color:transparent;opacity:.3}body .leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}body .leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}body .leaflet-control-layers-toggle{border-radius:4px}body .block-language-leaflet .leaflet-control-expandable,body .block-language-leaflet .leaflet-control-has-actions .control-actions.expanded,body .block-language-leaflet .leaflet-distance-control,body .leaflet-bar,body .leaflet-bar a,body .leaflet-control-layers-expanded,body .leaflet-control-layers-toggle{background-color:var(--leaflet-buttons);color:var(--text-muted);border:none;user-select:none}body .leaflet-bar a.leaflet-disabled,body .leaflet-bar a.leaflet-disabled:hover{background-color:var(--leaflet-buttons);color:var(--text-faint);opacity:.6;cursor:not-allowed}body .leaflet-control a{cursor:var(--cursor);color:var(--text-normal)}body .leaflet-bar a:hover{background-color:var(--background-modifier-hover);color:var(--text-normal);border:none}body .leaflet-touch .leaflet-control-layers{background-color:var(--leaflet-buttons)}body .leaflet-touch .leaflet-bar,body .leaflet-touch .leaflet-control-layers{border-radius:5px;box-shadow:2px 0 8px 0 rgba(0,0,0,.1);border:1px solid var(--ui1)}body .block-language-leaflet .leaflet-control-has-actions .control-actions{box-shadow:0;border:1px solid var(--ui1)}body .leaflet-control-expandable-list .leaflet-bar{box-shadow:none;border-radius:0}body .block-language-leaflet .leaflet-distance-control{padding:4px 10px;height:auto;cursor:var(--cursor)!important}body .block-language-leaflet .leaflet-marker-link-popup>.leaflet-popup-content-wrapper>*{font-size:var(--font-adaptive-small);font-family:var(--font-interface)}body .block-language-leaflet .leaflet-marker-link-popup>.leaflet-popup-content-wrapper{padding:4px 10px!important}.leaflet-marker-icon svg path{stroke:var(--background-primary);stroke-width:18px}.map-view-marker-name{font-weight:400}.workspace-leaf-content[data-type=map] .graph-controls{background-color:var(--background-primary)}body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf-content[data-type=map] .view-header{position:fixed;background:0 0!important;width:100%;z-index:99}body:not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type=map] .view-header-title{display:none}body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type=map] .view-actions{background:0 0}body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type=map] .view-content{height:100%}body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type=map] .leaflet-top.leaflet-right{top:var(--header-height)}.obsidian-metatable{--metatable-font-size:calc(var(--font-adaptive-normal) - 2px);--metatable-font-family:var(--font-interface);--metatable-background:transparent;--metatable-foreground:var(--text-faint);--metatable-key-background:transparent;--metatable-key-border-width:0;--metatable-key-border-color:transparent;--metatable-value-background:transparent;padding-bottom:.5rem}.obsidian-metatable::part(key),.obsidian-metatable::part(value){border-bottom:0 solid var(--background-modifier-border);padding:.1rem 0;text-overflow:ellipsis;overflow:hidden}.obsidian-metatable::part(key){font-weight:400;color:var(--tx3);font-size:calc(var(--font-adaptive-normal) - 2px)}.obsidian-metatable::part(value){font-size:calc(var(--font-adaptive-normal) - 2px);color:var(--tx1)}body .NLT__header-menu-header-container{font-size:85%}body .NLT__button{background:0 0;box-shadow:none;color:var(--text-muted)}body .NLT__button:active,body .NLT__button:focus,body .NLT__button:hover{background:0 0;color:var(--text-normal);box-shadow:none}.NLT__app .NLT__button{background:0 0;border:1px solid var(--background-modifier-border);box-shadow:0 .5px 1px 0 var(--btn-shadow-color);color:var(--text-muted);padding:2px 8px}.NLT__app .NLT__button:active,.NLT__app .NLT__button:focus,.NLT__app .NLT__button:hover{background:0 0;border-color:var(--background-modifier-border-hover);color:var(--text-normal);box-shadow:0 .5px 1px 0 var(--btn-shadow-color)}.NLT__td:nth-last-child(2),.NLT__th:nth-last-child(2){border-right:0}.NLT__app .NLT__td:last-child,.NLT__app .NLT__th:last-child{padding-right:0}.NLT__app .NLT__th{background-image:none!important}.NLT__app th.NLT__selectable:hover{background-color:transparent;cursor:var(--cursor)}.NLT__menu .NLT__menu-container{background-color:var(--background-secondary)}.NLT__menu .NLT__header-menu-item{font-size:var(--font-adaptive-small)}.NLT__menu .NLT__header-menu{padding:6px 4px}.NLT__menu .NLT__drag-menu{font-size:var(--font-adaptive-small);padding:6px 4px}.NLT__menu svg{color:var(--text-faint);margin-right:6px}.NLT__menu .NLT__selectable:hover,.NLT__menu .NLT__selected{background:0 0}.NLT__menu .NLT__selected>.NLT__selectable{background-color:var(--background-modifier-hover)}.NLT__menu .NLT__selectable{cursor:var(--cursor)}.NLT__menu div.NLT__selectable{min-width:110px;border-radius:var(--radius-m);padding:3px 8px 3px 4px;margin:1px 2px 1px;cursor:var(--cursor);height:auto;line-height:20px}.NLT__menu div.NLT__selectable:hover{background-color:var(--background-modifier-hover)}.NLT__menu .NLT__textarea{font-size:var(--table-text-size)}.NLT__tfoot tr:hover td{background-color:transparent}.modal .quickAddPrompt>h1,.modal .quickAddYesNoPrompt h1{margin-top:0;text-align:left!important;font-size:var(--h1);font-weight:600}.modal .quickAddYesNoPrompt p{text-align:left!important}.modal .quickAddYesNoPrompt button{font-size:var(--font-ui-small)}.modal .yesNoPromptButtonContainer{font-size:var(--font-ui-small);justify-content:flex-end}.quickAddModal .modal-content{padding:20px 2px 5px}div#quick-explorer{display:flex}div#quick-explorer span.explorable{align-items:center;color:var(--text-muted);display:flex;font-size:var(--font-adaptive-smaller);line-height:16px}div#quick-explorer span.explorable:last-of-type{font-size:var(--font-adaptive-smaller)}div#quick-explorer span.explorable.selected,div#quick-explorer span.explorable:hover{background-color:unset!important}div#quick-explorer span.explorable.selected .explorable-name,div#quick-explorer span.explorable:hover .explorable-name{color:var(--text-normal)}div#quick-explorer span.explorable.selected .explorable-separator,div#quick-explorer span.explorable:hover .explorable-separator{color:var(--text-normal)}div#quick-explorer .explorable-name{padding:0 4px;border-radius:4px}div#quick-explorer .explorable-separator::before{content:"\00a0›"!important;font-size:1.3em;font-weight:400;margin:0}body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label).selected,body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label):hover{background-color:var(--background-modifier-hover);color:var(--text-normal)}body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label).selected .menu-item-icon,body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label):hover .menu-item-icon{color:var(--text-normal)}.workspace-leaf-content[data-type=recent-files] .view-content{padding-top:10px}.mod-root .workspace-leaf-content[data-type=reminder-list] main{max-width:var(--max-width);margin:0 auto;padding:0}.modal .reminder-actions .later-select{font-size:var(--font-settings-small);vertical-align:bottom;margin-left:3px}.modal .reminder-actions .icon{line-height:1}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main{margin:0 auto;padding:15px}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main .group-name{font-weight:500;color:var(--text-muted);font-size:var(--font-adaptive-small);padding-bottom:.5em;border-bottom:1px solid var(--background-modifier-border)}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main .reminder-group .reminder-list-item{line-height:1.3;font-size:var(--font-adaptive-small)}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main .reminder-group .no-reminders{color:var(--text-faint)}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main .reminder-group .reminder-time{font-family:var(--font-text);font-size:var(--font-adaptive-small)}:not(.mod-root) .workspace-leaf-content[data-type=reminder-list] main .reminder-group .reminder-file{color:var(--text-faint)}body .modal .dtchooser{background-color:transparent}body .modal .dtchooser .reminder-calendar .year-month{font-weight:400;font-size:var(--font-adaptive-normal);padding-bottom:10px}body .modal .dtchooser .reminder-calendar .year-month .month,body .modal .dtchooser .reminder-calendar .year-month .year{color:var(--text-normal)}body .modal .dtchooser .reminder-calendar .year-month .month-nav:first-child{background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E")}body .modal .dtchooser .reminder-calendar .year-month .month-nav:last-child{background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E")}body .modal .dtchooser .reminder-calendar .year-month .month-nav{-webkit-mask-size:20px 20px;-webkit-mask-repeat:no-repeat;-webkit-mask-position:50% 50%;color:var(--text-faint);cursor:var(--cursor);border-radius:var(--radius-m);padding:0;width:30px;display:inline-block}body .modal .dtchooser .reminder-calendar .year-month .month-nav:hover{color:var(--text-muted)}body .modal .dtchooser .reminder-calendar th{padding:.5em 0;font-size:var(--font-adaptive-smallest);font-weight:500;text-transform:uppercase;letter-spacing:.1em}body .modal .dtchooser .reminder-calendar .calendar-date{transition:background-color .1s ease-in;padding:.3em 0;border-radius:var(--radius-m)}body .modal .dtchooser .reminder-calendar .calendar-date.is-selected,body .modal .dtchooser .reminder-calendar .calendar-date:hover{transition:background-color .1s ease-in;background-color:var(--background-modifier-hover)!important}body .modal .dtchooser .reminder-calendar .calendar-date.is-selected{font-weight:var(--bold-weight);color:var(--text-accent)!important}body .markdown-preview-view th,body .markdown-source-view.mod-cm6 .dataview.table-view-table thead.table-view-thead tr th,body .table-view-table>thead>tr>th{cursor:var(--cursor);background-image:none}.markdown-source-view.mod-cm6 th{background-repeat:no-repeat;background-position:right}.style-settings-container[data-level="2"]{background:var(--background-secondary);border:1px solid var(--ui1);border-radius:5px;padding:10px 20px;margin:2px 0 2px -20px}.workspace-leaf-content[data-type=style-settings] div[data-id=instructions] .setting-item-name{display:none}.workspace-leaf-content[data-type=style-settings] div[data-id=instructions] .setting-item-description{color:var(--text-normal);font-size:var(--font-adaptive-smaller);padding-bottom:.5em}.workspace-leaf-content[data-type=style-settings] .view-content{padding:var(--size-4-4) 0}.workspace-leaf-content[data-type=style-settings] .view-content>div{width:var(--line-width);max-width:var(--max-width);margin:0 auto}.workspace-leaf-content[data-type=style-settings] .style-settings-heading[data-level="0"] .setting-item-name{padding-left:17px}.workspace-leaf-content[data-type=style-settings] .setting-item{max-width:100%;margin:0 auto}.workspace-leaf-content[data-type=style-settings] .setting-item-name{position:relative}.workspace-leaf-content[data-type=style-settings] .style-settings-collapse-indicator{position:absolute;left:0}.setting-item-heading.style-settings-heading,.style-settings-container .style-settings-heading{cursor:var(--cursor)}.modal.mod-settings .setting-item .pickr button.pcr-button{box-shadow:none;border-radius:40px;height:24px;width:24px}.setting-item .pickr .pcr-button:after,.setting-item .pickr .pcr-button:before{border-radius:40px;box-shadow:none;border:none}.setting-item.setting-item-heading.style-settings-heading.is-collapsed{border-bottom:1px solid var(--background-modifier-border)}.setting-item.setting-item-heading.style-settings-heading{border:0;padding:10px 0 5px;margin-bottom:0}.setting-item .style-settings-export,.setting-item .style-settings-import{text-decoration:none;font-size:var(--font-ui-small);font-weight:500;color:var(--text-muted);margin:0;padding:2px 8px;border-radius:5px;cursor:var(--cursor)}.setting-item .style-settings-export:hover,.setting-item .style-settings-import:hover{background-color:var(--background-modifier-hover);color:var(--text-normal);cursor:var(--cursor)}.mod-root .workspace-leaf-content[data-type=style-settings] .style-settings-container .setting-item:not(.setting-item-heading){flex-direction:row;align-items:center;padding:.5em 0}.workspace-split:not(.mod-root) .workspace-leaf-content[data-type=style-settings] .setting-item-name{font-size:var(--font-adaptive-smaller)}.themed-color-wrapper>div+div{margin-top:0;margin-left:6px}.theme-light .themed-color-wrapper>.theme-light{background-color:transparent}.theme-light .themed-color-wrapper>.theme-dark{background-color:rgba(0,0,0,.8)}.theme-dark .themed-color-wrapper>.theme-dark{background-color:transparent}@media (max-width:400pt){.workspace-leaf-content[data-type=style-settings] .setting-item-name{font-size:var(--font-adaptive-small)}.workspace-leaf-content[data-type=style-settings] .setting-item-info:has(.search-input-container){width:100%;margin-right:0}}body .todoist-query-title{display:inline;font-size:var(--h4);font-variant:var(--h4-variant);letter-spacing:.02em;color:var(--h4-color);font-weight:var(--h4-weight);font-style:var(--h4-style)}body .is-live-preview .block-language-todoist{padding-left:0}ul.todoist-task-list>li.task-list-item .task-list-item-checkbox{margin:0}body .todoist-refresh-button{display:inline;float:right;background:0 0;padding:5px 6px 0;margin-right:0}body .is-live-preview .todoist-refresh-button{margin-right:30px}body .todoist-refresh-button:hover{box-shadow:none;background-color:var(--background-modifier-hover)}.todoist-refresh-button svg{width:15px;height:15px;opacity:var(--icon-muted)}ul.todoist-task-list{margin-left:-.25em}.is-live-preview ul.todoist-task-list{padding-left:0;margin-left:.5em;margin-block-start:0;margin-block-end:0}.contains-task-list.todoist-task-list .task-metadata{font-size:var(--font-adaptive-small);display:flex;color:var(--text-muted);justify-content:space-between;margin-left:.1em;margin-bottom:.25rem}.is-live-preview .contains-task-list.todoist-task-list .task-metadata{padding-left:calc(var(--checkbox-size) + .6em)}.todoist-task-list .task-date.task-overdue{color:var(--color-orange)}body .todoist-p1>input[type=checkbox]{border:1px solid var(--color-red)}body .todoist-p1>input[type=checkbox]:hover{opacity:.8}body .todoist-p2>input[type=checkbox]{border:1px solid var(--color-yellow)}body .todoist-p2>input[type=checkbox]:hover{opacity:.8}body .todoist-p3>input[type=checkbox]{border:1px solid var(--color-blue)}body .todoist-p3>input[type=checkbox]:hover{opacity:.8}body.theme-light{--color-axis-label:var(--tx1);--color-tick-label:var(--tx2);--color-dot-fill:var(--ax1);--color-line:var(--ui1)}.tracker-axis-label{font-family:var(--font-interface)}.tracker-axis{color:var(--ui2)}.tabs-manager .chat-view{--assistant-message-color:var(--background-primary);--padding-md:var(--size-4-2) var(--size-4-3);--padding-lg:var(--size-4-3) var(--size-4-3);--chat-box-color:var(--background-primary)}.tabs-manager .chat-view .ow-dialogue-timeline{padding:var(--size-4-4) var(--size-4-3) var(--size-4-8)}.tabs-manager .chat-view .ow-dialogue-timeline .ow-message-bubble .ow-content-wrapper{box-shadow:none;border-color:var(--background-modifier-border);border-radius:var(--radius-m)}.tabs-manager .chat-view .ow-dialogue-timeline .ow-message-bubble.ow-user-bubble .ow-content-wrapper{border-width:0;background-color:var(--interactive-accent)}.tabs-manager .chat-view .input-area .input-form .chat-box{border-radius:0;box-shadow:none;grid-row:1;grid-column:1/3;height:100px;border:none;padding:var(--size-4-3) var(--size-4-4) var(--size-4-2)}.tabs-manager .chat-view .input-area .input-form .chat-box:hover{height:100px}.tabs-manager .chat-view .input-area{padding:0;gap:0}.tabs-manager .chat-view .header{border-bottom:1px solid var(--background-modifier-border)}.tabs-manager .chat-view .input-form{border-top:1px solid var(--background-modifier-border)}.tabs-manager .chat-view .input-area .input-form .chat-box .info-bar span{color:var(--text-faint)}.tabs-manager .chat-view .input-area .input-form .btn-new-chat{display:none}.zoom-plugin-header{--link-color:var(--text-normal);--link-decoration:none;font-size:var(--font-ui-small);padding:0;justify-content:center;margin:var(--size-4-2) auto;max-width:var(--max-width)}.zoom-plugin-header>.zoom-plugin-title{text-decoration:none;max-width:15em;overflow:hidden}.zoom-plugin-header>.zoom-plugin-delimiter{color:var(--text-faint);padding:0 var(--size-4-1)}.theme-dark.minimal-atom-dark{--color-red-rgb:225,109,118;--color-orange-rgb:209,154,102;--color-yellow-rgb:206,193,103;--color-green-rgb:152,195,121;--color-cyan-rgb:88,182,194;--color-blue-rgb:98,175,239;--color-purple-rgb:198,120,222;--color-pink-rgb:225,109,118;--color-red:#e16d76;--color-orange:#d19a66;--color-yellow:#cec167;--color-green:#98c379;--color-cyan:#58b6c2;--color-blue:#62afef;--color-purple:#c678de;--color-pink:#e16d76}.theme-light.minimal-atom-light{--color-red-rgb:228,87,73;--color-orange-rgb:183,107,2;--color-yellow-rgb:193,131,2;--color-green-rgb:80,161,80;--color-cyan-rgb:13,151,179;--color-blue-rgb:98,175,239;--color-purple-rgb:166,38,164;--color-pink-rgb:228,87,73;--color-red:#e45749;--color-orange:#b76b02;--color-yellow:#c18302;--color-green:#50a150;--color-cyan:#0d97b3;--color-blue:#62afef;--color-purple:#a626a4;--color-pink:#e45749}.theme-light.minimal-atom-light{--base-h:106;--base-s:0%;--base-l:98%;--accent-h:231;--accent-s:76%;--accent-l:62%;--bg1:#fafafa;--bg2:#eaeaeb;--bg3:rgba(0, 0, 0, .1);--ui1:#dbdbdc;--ui2:#d8d8d9;--tx1:#232324;--tx2:#8e8e90;--tx3:#a0a1a8;--hl1:rgba(180, 180, 183, 0.3);--hl2:rgba(209, 154, 102, 0.35)}.theme-light.minimal-atom-light.minimal-light-white{--bg3:#eaeaeb}.theme-dark.minimal-atom-dark,.theme-light.minimal-atom-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-atom-light.minimal-light-contrast .titlebar,.theme-light.minimal-atom-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-atom-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-atom-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:220;--base-s:12%;--base-l:18%;--accent-h:220;--accent-s:86%;--accent-l:65%;--bg1:#282c34;--bg2:#21252c;--bg3:#3a3f4b;--divider-color:#181a1f;--tab-outline-color:#181a1f;--tx1:#d8dae1;--tx2:#898f9d;--tx3:#5d6370;--hl1:rgba(114, 123, 141, 0.3);--hl2:rgba(209, 154, 102, 0.3);--sp1:#fff}.theme-dark.minimal-atom-dark.minimal-dark-black{--base-d:5%;--bg3:#282c34;--divider-color:#282c34;--tab-outline-color:#282c34}.theme-light.minimal-ayu-light{--color-red-rgb:230,80,80;--color-orange-rgb:250,141,62;--color-yellow-rgb:242,174,73;--color-green-rgb:108,191,67;--color-cyan-rgb:76,191,153;--color-blue-rgb:57,158,230;--color-purple-rgb:163,122,204;--color-pink-rgb:255,115,131;--color-red:#e65050;--color-orange:#fa8d3e;--color-yellow:#f2ae49;--color-green:#6CBF43;--color-cyan:#4cbf99;--color-blue:#399ee6;--color-purple:#a37acc;--color-pink:#ff7383}.theme-dark.minimal-ayu-dark{--color-red-rgb:255,102,102;--color-orange-rgb:250,173,102;--color-yellow-rgb:255,209,55;--color-green-rgb:135,217,108;--color-cyan-rgb:149,230,203;--color-blue-rgb:115,208,255;--color-purple-rgb:223,191,255;--color-pink-rgb:242,121,131;--color-red:#ff6666;--color-orange:#ffad66;--color-yellow:#ffd137;--color-green:#87D96C;--color-cyan:#95e6cb;--color-blue:#73d0ff;--color-purple:#dfbfff;--color-pink:#f27983}.theme-light.minimal-ayu-light{--base-h:210;--base-s:17%;--base-l:98%;--accent-h:36;--accent-s:100%;--accent-l:50%;--bg1:#fff;--bg2:#f8f9fa;--bg3:rgba(209, 218, 224, 0.5);--ui1:#E6EAED;--tx1:#5C6165;--tx2:#8A9199;--tx3:#AAAEB0;--hl1:rgba(3, 91, 214, 0.15)}.theme-dark.minimal-ayu-dark,.theme-light.minimal-ayu-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-ayu-light.minimal-light-contrast .titlebar,.theme-light.minimal-ayu-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-ayu-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-ayu-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:222;--base-s:22%;--base-l:15%;--accent-h:35;--accent-s:100%;--accent-l:60%;--bg1:#232937;--bg2:#1E2431;--bg3:rgba(51, 61, 80, 0.5);--ui1:#333C4A;--ui2:#333C4A;--ui3:#333C4A;--tx1:#cccac2;--tx2:#707A8C;--tx3:#495063;--hl1:rgba(64, 159, 255, 0.25)}.theme-dark.minimal-ayu-dark.minimal-dark-black{--accent-h:40;--accent-s:75%;--accent-l:61%;--bg3:#0E1017;--tx1:#BFBDB6;--divider-color:#11151C;--tab-outline-color:#11151C}.theme-light.minimal-catppuccin-light{--color-red-rgb:230,69,83;--color-orange-rgb:254,100,12;--color-yellow-rgb:223,142,29;--color-green-rgb:64,160,43;--color-cyan-rgb:23,146,154;--color-blue-rgb:33,102,246;--color-purple-rgb:137,56,239;--color-pink-rgb:234,119,203;--color-red:#E64553;--color-orange:#FE640C;--color-yellow:#DF8E1D;--color-green:#40A02B;--color-cyan:#17929A;--color-blue:#2166F6;--color-purple:#8938EF;--color-pink:#EA77CB}.theme-dark.minimal-catppuccin-dark{--color-red-rgb:235,153,156;--color-orange-rgb:239,160,118;--color-yellow-rgb:229,200,144;--color-green-rgb:166,209,138;--color-cyan-rgb:129,200,190;--color-blue-rgb:140,170,238;--color-purple-rgb:202,158,230;--color-pink-rgb:244,185,229;--color-red:#EB999C;--color-orange:#EFA076;--color-yellow:#E5C890;--color-green:#A6D18A;--color-cyan:#81C8BE;--color-blue:#8CAAEE;--color-purple:#CA9EE6;--color-pink:#F4B9E5}.theme-light.minimal-catppuccin-light{--base-h:228;--base-s:20%;--base-l:95%;--accent-h:11;--accent-s:59%;--accent-l:67%;--bg1:#F0F1F5;--bg2:#DCE0E8;--bg3:hsla(228, 11%, 65%, .25);--ui1:#CCD0DA;--ui2:#BCC0CC;--ui3:#ACB0BE;--tx1:#4D4F69;--tx2:#5D5F77;--tx3:#8D8FA2;--hl1:rgba(172, 176, 190, .3);--hl2:rgba(223, 142, 29, .3)}.theme-light.minimal-catppuccin-light.minimal-light-tonal{--bg2:#DCE0E8}.theme-light.minimal-catppuccin-light.minimal-light-white{--bg3:#F0F1F5;--ui1:#DCE0E8}.theme-dark.minimal-catppuccin-dark,.theme-light.minimal-catppuccin-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-catppuccin-light.minimal-light-contrast .titlebar,.theme-light.minimal-catppuccin-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-catppuccin-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-catppuccin-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:229;--base-s:19%;--base-l:23%;--accent-h:10;--accent-s:57%;--accent-l:88%;--bg1:#303446;--bg2:#242634;--bg3:hsla(229, 13%, 52%, 0.25);--ui1:#41455A;--ui2:#51576D;--ui3:#626880;--tx1:#C6D0F5;--tx2:#A6ADCE;--tx3:#848BA7;--sp1:#242634;--hl1:rgba(98, 104, 128, .5);--hl2:rgba(223, 142, 29, .4)}.theme-dark.minimal-catppuccin-dark.minimal-dark-black{--ui1:#303446;--hl2:rgba(223, 142, 29, .5)}.theme-dark.minimal-dracula-dark{--color-red-rgb:255,85,85;--color-orange-rgb:255,184,108;--color-yellow-rgb:241,250,140;--color-green-rgb:80,250,123;--color-cyan-rgb:139,233,253;--color-blue-rgb:98,114,164;--color-purple-rgb:189,147,249;--color-pink-rgb:255,121,198;--color-red:#ff5555;--color-orange:#ffb86c;--color-yellow:#f1fa8c;--color-green:#50fa7b;--color-cyan:#8be9fd;--color-blue:#6272a4;--color-purple:#bd93f9;--color-pink:#ff79c6}.theme-dark.minimal-dracula-dark,.theme-light.minimal-dracula-light.minimal-light-contrast .titlebar,.theme-light.minimal-dracula-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-dracula-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:232;--base-s:16%;--base-l:19%;--accent-h:265;--accent-s:89%;--accent-l:78%;--bg1:#282a37;--bg2:#21222c;--ui2:#44475a;--ui3:#6272a4;--tx1:#f8f8f2;--tx2:#949FBE;--tx3:#6272a4;--hl1:rgba(134, 140, 170, 0.3);--hl2:rgba(189, 147, 249, 0.35)}.theme-dark.minimal-dracula-dark.minimal-dark-black{--ui1:#282a36}.theme-dark.minimal-eink-dark,.theme-light.minimal-eink-light{--collapse-icon-color:var(--text-normal);--icon-color-active:var(--bg1);--icon-color-hover:var(--bg1);--icon-color-focused:var(--bg1);--icon-opacity:1;--indentation-guide-color:var(--tx1);--indentation-guide-color-active:var(--tx1);--indentation-guide-width-active:3px;--interactive-normal:var(--bg1);--input-shadow:0 0 0 1px var(--tx1);--link-unresolved-opacity:1;--link-unresolved-decoration-style:dashed;--link-unresolved-decoration-color:var(--tx1);--metadata-label-background-active:var(--bg1);--metadata-input-background-active:var(--bg1);--modal-border-color:var(--tx1);--modal-border-width:2px;--nav-item-color-hover:var(--bg1);--nav-item-color-active:var(--bg1);--prompt-border-color:var(--tx1);--prompt-border-width:2px;--calendar-dot-active:var(--bg1);--calendar-dot-today:var(--bg1);--calendar-text-active:var(--bg1);--tag-border-width:1.25px;--tag-background:transparent;--tag-background-hover:transparent;--tag-border-color:var(--tx1);--tag-border-color-hover:var(--tx1);--text-on-accent:var(--bg1);--text-on-accent-inverted:var(--bg1)}.theme-dark.minimal-eink-dark.tabs-modern,.theme-light.minimal-eink-light.tabs-modern{--minimal-tab-text-color-active:var(--bg1);--tab-text-color-focused-active-current:var(--bg1)}.theme-dark.minimal-eink-dark .suggestion-container,.theme-light.minimal-eink-light .suggestion-container{border-width:3px}.theme-dark.minimal-eink-dark .cm-s-obsidian span.cm-inline-code,.theme-dark.minimal-eink-dark .markdown-rendered code,.theme-light.minimal-eink-light .cm-s-obsidian span.cm-inline-code,.theme-light.minimal-eink-light .markdown-rendered code{font-weight:600}.theme-dark.minimal-eink-dark .metadata-property-icon,.theme-light.minimal-eink-light .metadata-property-icon{--icon-color-focused:var(--tx1)}.theme-dark.minimal-eink-dark .checkbox-container,.theme-light.minimal-eink-light .checkbox-container{background-color:var(--bg1);box-shadow:0 0 0 1px var(--tx1);--toggle-thumb-color:var(--tx1)}.theme-dark.minimal-eink-dark .checkbox-container.is-enabled,.theme-light.minimal-eink-light .checkbox-container.is-enabled{background-color:var(--tx1);--toggle-thumb-color:var(--bg1)}.theme-dark.minimal-eink-dark .cm-s-obsidian span.cm-formatting-highlight,.theme-dark.minimal-eink-dark .cm-s-obsidian span.cm-highlight,.theme-dark.minimal-eink-dark .community-item .suggestion-highlight,.theme-dark.minimal-eink-dark .dropdown:hover,.theme-dark.minimal-eink-dark .horizontal-tab-nav-item:hover,.theme-dark.minimal-eink-dark .markdown-rendered mark,.theme-dark.minimal-eink-dark .status-bar .plugin-sync:hover .sync-status-icon.mod-success,.theme-dark.minimal-eink-dark .status-bar .plugin-sync:hover .sync-status-icon.mod-working,.theme-dark.minimal-eink-dark .status-bar-item.mod-clickable:hover,.theme-dark.minimal-eink-dark .suggestion-item.is-selected,.theme-dark.minimal-eink-dark .text-icon-button:hover,.theme-dark.minimal-eink-dark .vertical-tab-nav-item:hover,.theme-dark.minimal-eink-dark button,.theme-dark.minimal-eink-dark select:hover,.theme-dark.minimal-eink-dark:not(.colorful-active) .horizontal-tab-nav-item.is-active,.theme-dark.minimal-eink-dark:not(.colorful-active) .vertical-tab-nav-item.is-active,.theme-light.minimal-eink-light .cm-s-obsidian span.cm-formatting-highlight,.theme-light.minimal-eink-light .cm-s-obsidian span.cm-highlight,.theme-light.minimal-eink-light .community-item .suggestion-highlight,.theme-light.minimal-eink-light .dropdown:hover,.theme-light.minimal-eink-light .horizontal-tab-nav-item:hover,.theme-light.minimal-eink-light .markdown-rendered mark,.theme-light.minimal-eink-light .status-bar .plugin-sync:hover .sync-status-icon.mod-success,.theme-light.minimal-eink-light .status-bar .plugin-sync:hover .sync-status-icon.mod-working,.theme-light.minimal-eink-light .status-bar-item.mod-clickable:hover,.theme-light.minimal-eink-light .suggestion-item.is-selected,.theme-light.minimal-eink-light .text-icon-button:hover,.theme-light.minimal-eink-light .vertical-tab-nav-item:hover,.theme-light.minimal-eink-light button,.theme-light.minimal-eink-light select:hover,.theme-light.minimal-eink-light:not(.colorful-active) .horizontal-tab-nav-item.is-active,.theme-light.minimal-eink-light:not(.colorful-active) .vertical-tab-nav-item.is-active{color:var(--bg1)}.theme-light.minimal-eink-light{--base-h:0;--base-s:0%;--base-l:100%;--accent-h:0;--accent-s:0%;--accent-l:0%;--ax3:#000;--bg1:#fff;--bg2:#fff;--bg3:#000;--ui1:#000;--ui2:#000;--ui3:#000;--tx1:#000;--tx2:#000;--tx3:#000;--hl1:#000;--hl2:#000;--sp1:#fff;--text-on-accent:#fff;--background-modifier-cover:rgba(235, 235, 235, 1)}.theme-light.minimal-eink-light.minimal-light-white{--bg3:#fff}.theme-dark.minimal-eink-dark,.theme-light.minimal-eink-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-eink-light.minimal-light-contrast .titlebar,.theme-light.minimal-eink-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-eink-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-eink-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:0;--base-s:0%;--base-l:0%;--accent-h:0;--accent-s:0%;--accent-l:100%;--ax3:#fff;--bg1:#000;--bg2:#000;--bg3:#fff;--ui1:#fff;--ui2:#fff;--ui3:#fff;--tx1:#fff;--tx2:#fff;--tx3:#fff;--hl1:#fff;--hl2:#fff;--sp1:#000;--background-modifier-cover:rgba(20, 20, 20, 1)}.theme-light.minimal-eink-light.minimal-light-tonal{--bg3:#bbb;--ui1:#bbb;--modal-border-color:var(--ui1);--prompt-border-color:var(--ui1)}.theme-dark.minimal-eink-dark.minimal-dark-tonal{--bg3:#444;--ui1:#444;--modal-border-color:var(--ui1);--prompt-border-color:var(--ui1)}.theme-light.minimal-everforest-light{--color-red-rgb:248,85,82;--color-orange-rgb:245,125,38;--color-yellow-rgb:223,160,0;--color-green-rgb:141,161,1;--color-cyan-rgb:53,167,124;--color-blue-rgb:56,148,196;--color-purple-rgb:223,105,186;--color-pink-rgb:223,105,186;--color-red:#f85552;--color-orange:#f57d26;--color-yellow:#dfa000;--color-green:#8da101;--color-cyan:#35a77c;--color-blue:#3795C5;--color-purple:#df69ba;--color-pink:#df69ba}.theme-dark.minimal-everforest-dark{--color-red-rgb:230,126,128;--color-orange-rgb:230,152,117;--color-yellow-rgb:219,188,127;--color-green-rgb:167,192,128;--color-cyan-rgb:131,192,146;--color-blue-rgb:127,187,179;--color-purple-rgb:223,105,186;--color-pink-rgb:223,105,186;--color-red:#e67e80;--color-orange:#e69875;--color-yellow:#dbbc7f;--color-green:#a7c080;--color-cyan:#83c092;--color-blue:#7fbbb3;--color-purple:#d699b6;--color-pink:#d699b6}.theme-light.minimal-everforest-light{--base-h:44;--base-s:87%;--base-l:94%;--accent-h:83;--accent-s:36%;--accent-l:53%;--bg1:#fdf6e3;--bg2:#efebd4;--bg3:rgba(226, 222, 198, .5);--ui1:#e0dcc7;--ui2:#bec5b2;--ui3:#bec5b2;--tx1:#5C6A72;--tx2:#829181;--tx3:#a6b0a0;--hl1:rgba(198, 214, 152, .4);--hl2:rgba(222, 179, 51, .3)}.theme-light.minimal-everforest-light.minimal-light-tonal{--bg2:#fdf6e3}.theme-light.minimal-everforest-light.minimal-light-white{--bg3:#f3efda;--ui1:#edead5}.theme-dark.minimal-everforest-dark,.theme-light.minimal-everforest-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-everforest-light.minimal-light-contrast .titlebar,.theme-light.minimal-everforest-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-everforest-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-everforest-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:203;--base-s:15%;--base-l:23%;--accent-h:81;--accent-s:34%;--accent-l:63%;--bg1:#2d353b;--bg2:#232a2e;--bg3:rgba(71, 82, 88, 0.5);--ui1:#475258;--ui2:#4f585e;--ui3:#525c62;--tx1:#d3c6aa;--tx2:#9da9a0;--tx3:#7a8478;--hl1:rgba(134, 70, 93, .5);--hl2:rgba(147, 185, 96, .3)}.theme-dark.minimal-everforest-dark.minimal-dark-black{--hl1:rgba(134, 70, 93, .4);--ui1:#2b3339}.theme-light.minimal-flexoki-light{--color-red-rgb:175,48,41;--color-orange-rgb:188,82,21;--color-yellow-rgb:173,131,1;--color-green-rgb:102,128,11;--color-cyan-rgb:36,131,123;--color-blue-rgb:32,94,166;--color-purple-rgb:94,64,157;--color-pink-rgb:160,47,111;--color-red:#AF3029;--color-orange:#BC5215;--color-yellow:#AD8301;--color-green:#66800B;--color-cyan:#24837B;--color-blue:#205EA6;--color-purple:#5E409D;--color-pink:#A02F6F}.theme-dark.minimal-flexoki-dark{--color-red-rgb:209,77,65;--color-orange-rgb:218,112,44;--color-yellow-rgb:208,162,21;--color-green-rgb:135,154,57;--color-cyan-rgb:58,169,159;--color-blue-rgb:67,133,190;--color-purple-rgb:139,126,200;--color-pink-rgb:206,93,151;--color-red:#D14D41;--color-orange:#DA702C;--color-yellow:#D0A215;--color-green:#879A39;--color-cyan:#3AA99F;--color-blue:#4385BE;--color-purple:#8B7EC8;--color-pink:#CE5D97}.theme-light.minimal-flexoki-light{--base-h:48;--base-s:100%;--base-l:97%;--accent-h:175;--accent-s:57%;--accent-l:33%;--bg1:#FFFCF0;--bg2:#F2F0E5;--bg3:rgba(16, 15, 15, 0.05);--ui1:#E6E4D9;--ui2:#DAD8CE;--ui3:#CECDC3;--tx1:#100F0F;--tx2:#6F6E69;--tx3:#B7B5AC;--hl1:rgba(187, 220, 206, 0.3);--hl2:rgba(247, 209, 61, 0.3)}.theme-light.minimal-flexoki-light.minimal-light-tonal{--bg2:#FFFCF0}.theme-dark.minimal-flexoki-dark,.theme-light.minimal-flexoki-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-flexoki-light.minimal-light-contrast .titlebar,.theme-light.minimal-flexoki-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-flexoki-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-flexoki-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:360;--base-s:3%;--base-l:6%;--accent-h:175;--accent-s:49%;--accent-l:45%;--bg1:#100F0F;--bg2:#1C1B1A;--bg3:rgba(254, 252, 240, 0.05);--ui1:#282726;--ui2:#343331;--ui3:#403E3C;--tx1:#CECDC3;--tx2:#878580;--tx3:#575653;--hl1:rgba(30, 95, 91, 0.3);--hl2:rgba(213, 159, 17, 0.3)}.theme-dark.minimal-flexoki-dark.minimal-dark-black{--ui1:#1C1B1A}.theme-dark.minimal-gruvbox-dark,.theme-light.minimal-gruvbox-light{--color-red-rgb:204,36,29;--color-orange-rgb:214,93,14;--color-yellow-rgb:215,153,33;--color-green-rgb:152,151,26;--color-cyan-rgb:104,157,106;--color-blue-rgb:69,133,136;--color-purple-rgb:177,98,134;--color-pink-rgb:177,98,134;--color-red:#cc241d;--color-orange:#d65d0e;--color-yellow:#d79921;--color-green:#98971a;--color-cyan:#689d6a;--color-blue:#458588;--color-purple:#b16286;--color-pink:#b16286}.theme-light.minimal-gruvbox-light{--base-h:49;--base-s:92%;--base-l:89%;--accent-h:24;--accent-s:88%;--accent-l:45%;--bg1:#fcf2c7;--bg2:#f2e6bd;--bg3:#ebd9b3;--ui1:#ebdbb2;--ui2:#d5c4a1;--ui3:#bdae93;--tx1:#282828;--tx2:#7c7065;--tx3:#a89a85;--hl1:rgba(192, 165, 125, .3);--hl2:rgba(215, 153, 33, .4)}.theme-light.minimal-gruvbox-light.minimal-light-tonal{--bg2:#fcf2c7}.theme-light.minimal-gruvbox-light.minimal-light-white{--bg3:#faf5d7;--ui1:#f2e6bd}.theme-dark.minimal-gruvbox-dark,.theme-light.minimal-gruvbox-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-gruvbox-light.minimal-light-contrast .titlebar,.theme-light.minimal-gruvbox-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-gruvbox-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-gruvbox-light.minimal-light-contrast.minimal-status-off .status-bar{--accent-h:24;--accent-s:88%;--accent-l:45%;--bg1:#282828;--bg2:#1e2021;--bg3:#3d3836;--bg3:rgba(62, 57, 55, 0.5);--ui1:#3c3836;--ui2:#504945;--ui3:#665c54;--tx1:#fbf1c7;--tx2:#bdae93;--tx3:#7c6f64;--hl1:rgba(173, 149, 139, 0.3);--hl2:rgba(215, 153, 33, .4)}.theme-dark.minimal-gruvbox-dark.minimal-dark-black{--hl1:rgba(173, 149, 139, 0.4);--ui1:#282828}.theme-dark.minimal-macos-dark,.theme-light.minimal-macos-light{--color-red-rgb:255,59,49;--color-orange-rgb:255,149,2;--color-yellow-rgb:255,204,0;--color-green-rgb:42,205,65;--color-cyan-rgb:2,199,190;--color-blue-rgb:2,122,255;--color-purple-rgb:176,81,222;--color-pink-rgb:255,46,85;--color-red:#ff3b31;--color-orange:#ff9502;--color-yellow:#ffcc00;--color-green:#2acd41;--color-cyan:#02c7be;--color-blue:#027aff;--color-purple:#b051de;--color-pink:#ff2e55}.theme-light.minimal-macos-light{--base-h:106;--base-s:0%;--base-l:94%;--accent-h:212;--accent-s:100%;--accent-l:50%;--bg1:#fff;--bg2:#f0f0f0;--bg3:rgba(0, 0, 0, .1);--ui1:#e7e7e7;--tx1:#454545;--tx2:#808080;--tx3:#b0b0b0;--hl1:#b3d7ff}.theme-light.minimal-macos-light.minimal-light-tonal{--bg1:#f0f0f0;--bg2:#f0f0f0}.theme-dark.minimal-macos-dark,.theme-light.minimal-macos-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-macos-light.minimal-light-contrast .titlebar,.theme-light.minimal-macos-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-macos-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-macos-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:106;--base-s:0%;--base-l:12%;--accent-h:212;--accent-s:100%;--accent-l:50%;--bg1:#1e1e1e;--bg2:#282828;--bg3:rgba(255, 255, 255, 0.11);--divider-color:#000;--tab-outline-color:#000;--ui1:#373737;--ui2:#515151;--ui3:#595959;--tx1:#dcdcdc;--tx2:#8c8c8c;--tx3:#686868;--hl1:rgba(98, 169, 252, 0.5);--sp1:#fff}.theme-dark.minimal-macos-dark.minimal-dark-black{--divider-color:#1e1e1e;--tab-outline-color:#1e1e1e}.theme-dark.minimal-nord-dark,.theme-light.minimal-nord-light{--color-red-rgb:191,97,106;--color-orange-rgb:208,138,112;--color-yellow-rgb:235,203,139;--color-green-rgb:163,190,140;--color-cyan-rgb:136,192,208;--color-blue-rgb:129,161,193;--color-purple-rgb:180,142,173;--color-pink-rgb:180,142,173;--color-red:#BF616A;--color-orange:#D08770;--color-yellow:#EBCB8B;--color-green:#A3BE8C;--color-cyan:#88C0D0;--color-blue:#81A1C1;--color-purple:#B48EAD;--color-pink:#B48EAD}.theme-light.minimal-nord-light{--base-h:221;--base-s:27%;--base-l:94%;--accent-h:213;--accent-s:32%;--accent-l:52%;--bg1:#fff;--bg2:#eceff4;--bg3:rgba(157, 174, 206, 0.25);--ui1:#d8dee9;--ui2:#BBCADC;--ui3:#81a1c1;--tx1:#2e3440;--tx2:#7D8697;--tx3:#ADB1B8;--hl2:rgba(208, 135, 112, 0.35)}.theme-dark.minimal-nord-dark,.theme-light.minimal-nord-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-nord-light.minimal-light-contrast .titlebar,.theme-light.minimal-nord-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-nord-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-nord-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:220;--base-s:16%;--base-l:22%;--accent-h:213;--accent-s:32%;--accent-l:52%;--bg1:#2e3440;--bg2:#3b4252;--bg3:rgba(135, 152, 190, 0.15);--ui1:#434c5e;--ui2:#58647b;--ui3:#58647b;--tx1:#d8dee9;--tx2:#9eafcc;--tx3:#4c566a;--hl1:rgba(129, 142, 180, 0.3);--hl2:rgba(208, 135, 112, 0.35)}.theme-dark.minimal-nord-dark.minimal-dark-black{--ui1:#2e3440}.theme-light.minimal-notion-light{--base-h:39;--base-s:18%;--base-d:96%;--accent-h:197;--accent-s:71%;--accent-l:52%;--bg2:#f7f6f4;--bg3:#e8e7e4;--ui1:#ededec;--ui2:#dbdbda;--ui3:#aaa9a5;--tx1:#37352f;--tx2:#72706c;--tx3:#aaa9a5;--hl1:rgba(131, 201, 229, 0.3);--link-weight:500}.theme-dark.minimal-notion-dark,.theme-light.minimal-notion-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-notion-light.minimal-light-contrast .titlebar,.theme-light.minimal-notion-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-notion-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-notion-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:203;--base-s:8%;--base-d:20%;--accent-h:197;--accent-s:71%;--accent-l:52%;--bg1:#2f3437;--bg2:#373c3f;--bg3:#4b5053;--ui1:#3e4245;--ui2:#585d5f;--ui3:#585d5f;--tx1:#ebebeb;--tx2:#909295;--tx3:#585d5f;--hl1:rgba(57, 134, 164, 0.3);--link-weight:500}.theme-dark.minimal-notion-dark.minimal-dark-black{--base-d:5%;--bg3:#232729;--ui1:#2f3437}.theme-light.minimal-rose-pine-light{--color-red-rgb:180,99,122;--color-orange-rgb:215,130,125;--color-yellow-rgb:234,157,53;--color-green-rgb:40,105,131;--color-cyan-rgb:87,147,159;--color-blue-rgb:87,147,159;--color-purple-rgb:144,122,169;--color-pink-rgb:144,122,169;--color-red:#b4637a;--color-orange:#d7827e;--color-yellow:#ea9d34;--color-green:#286983;--color-cyan:#56949f;--color-blue:#56949f;--color-purple:#907aa9;--color-pink:#907aa9}.theme-dark.minimal-rose-pine-dark{--color-red-rgb:234,111,146;--color-orange-rgb:233,155,151;--color-yellow-rgb:246,193,119;--color-green-rgb:47,116,143;--color-cyan-rgb:157,207,215;--color-blue-rgb:157,207,215;--color-purple-rgb:196,167,231;--color-pink-rgb:196,167,231;--color-red:#eb6f92;--color-orange:#ea9a97;--color-yellow:#f6c177;--color-green:#31748f;--color-cyan:#9ccfd8;--color-blue:#9ccfd8;--color-purple:#c4a7e7;--color-pink:#c4a7e7}.theme-light.minimal-rose-pine-light{--base-h:32;--base-s:57%;--base-l:95%;--accent-h:3;--accent-s:53%;--accent-l:67%;--bg1:#fffaf3;--bg2:#faf4ed;--bg3:rgba(233, 223, 218, 0.5);--ui1:#EAE3E1;--ui2:#dfdad9;--ui3:#cecacd;--tx1:#575279;--tx2:#797593;--tx3:#9893a5;--hl1:#EAE3E1}.theme-dark.minimal-rose-pine-dark,.theme-light.minimal-rose-pine-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-rose-pine-light.minimal-light-contrast .titlebar,.theme-light.minimal-rose-pine-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-rose-pine-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-rose-pine-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:247;--base-s:23%;--base-l:15%;--accent-h:2;--accent-s:55%;--accent-l:83%;--bg1:#1f1d2e;--bg2:#191724;--bg3:rgba(68, 66, 86, 0.5);--ui1:#312F41;--ui2:#403d52;--ui3:#524f67;--tx1:#e0def4;--tx2:#908caa;--tx3:#6e6a86;--hl1:#403d52}.theme-dark.minimal-rose-pine-dark.minimal-dark-black{--ui1:#21202e}.theme-dark.minimal-solarized-dark,.theme-light.minimal-solarized-light{--color-red-rgb:220,50,47;--color-orange-rgb:203,77,22;--color-yellow-rgb:181,137,0;--color-green-rgb:133,153,0;--color-cyan-rgb:42,161,152;--color-blue-rgb:38,139,210;--color-purple-rgb:108,113,196;--color-pink-rgb:211,54,130;--color-red:#dc322f;--color-orange:#cb4b16;--color-yellow:#b58900;--color-green:#859900;--color-cyan:#2aa198;--color-blue:#268bd2;--color-purple:#6c71c4;--color-pink:#d33682}.theme-light.minimal-solarized-light{--base-h:44;--base-s:87%;--base-l:94%;--accent-h:205;--accent-s:70%;--accent-l:48%;--bg1:#fdf6e3;--bg2:#eee8d5;--bg3:rgba(0, 0, 0, 0.062);--ui1:#e9e1c8;--ui2:#d0cab8;--ui3:#d0cab8;--tx1:#073642;--tx2:#586e75;--tx3:#ABB2AC;--tx4:#586e75;--hl1:rgba(202, 197, 182, 0.3);--hl2:rgba(203, 75, 22, 0.3)}.theme-light.minimal-solarized-light.minimal-light-tonal{--bg2:#fdf6e3}.theme-dark.minimal-solarized-dark,.theme-light.minimal-solarized-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-solarized-light.minimal-light-contrast .titlebar,.theme-light.minimal-solarized-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-solarized-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-solarized-light.minimal-light-contrast.minimal-status-off .status-bar{--accent-h:205;--accent-s:70%;--accent-l:48%;--base-h:193;--base-s:98%;--base-l:11%;--bg1:#002b36;--bg2:#073642;--bg3:rgba(255, 255, 255, 0.062);--ui1:#19414B;--ui2:#274850;--ui3:#31535B;--tx1:#93a1a1;--tx2:#657b83;--tx3:#31535B;--tx4:#657b83;--hl1:rgba(15, 81, 98, 0.3);--hl2:rgba(203, 75, 22, 0.35)}.theme-dark.minimal-solarized-dark.minimal-dark-black{--hl1:rgba(15, 81, 98, 0.55);--ui1:#002b36}.theme-dark.minimal-things-dark,.theme-light.minimal-things-light{--color-red-rgb:255,48,108;--color-orange-rgb:255,149,2;--color-yellow-rgb:255,213,0;--color-green-rgb:75,191,94;--color-cyan-rgb:73,174,164;--color-purple-rgb:176,81,222;--color-pink-rgb:255,46,85;--color-red:#FF306C;--color-orange:#ff9502;--color-yellow:#FFD500;--color-green:#4BBF5E;--color-cyan:#49AEA4;--color-purple:#b051de;--color-pink:#ff2e55}.theme-light.minimal-things-light{--color-blue-rgb:27,97,194;--color-blue:#1b61c2}.theme-dark.minimal-things-dark{--color-blue-rgb:77,149,247;--color-blue:#4d95f7}.theme-light.minimal-things-light{--accent-h:215;--accent-s:76%;--accent-l:43%;--bg1:white;--bg2:#f5f6f8;--bg3:rgba(162, 177, 187, 0.25);--ui1:#eef0f4;--ui2:#D8DADD;--ui3:#c1c3c6;--tx1:#26272b;--tx2:#7D7F84;--tx3:#a9abb0;--hl1:#cae2ff}.theme-light.minimal-things-light.minimal-light-tonal{--ui1:#e6e8ec}.theme-light.minimal-things-light.minimal-light-white{--bg3:#f5f6f8}.theme-dark.minimal-things-dark,.theme-light.minimal-things-light.minimal-light-contrast .mod-left-split,.theme-light.minimal-things-light.minimal-light-contrast .titlebar,.theme-light.minimal-things-light.minimal-light-contrast .workspace-drawer.mod-left,.theme-light.minimal-things-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed),.theme-light.minimal-things-light.minimal-light-contrast.minimal-status-off .status-bar{--base-h:218;--base-s:9%;--base-l:15%;--accent-h:215;--accent-s:91%;--accent-l:64%;--bg1:#24262a;--bg2:#202225;--bg3:#3d3f41;--divider-color:#17191c;--tab-outline-color:#17191c;--ui1:#3A3B3F;--ui2:#45464a;--ui3:#6c6e70;--tx1:#fbfbfb;--tx2:#CBCCCD;--tx3:#6c6e70;--hl1:rgba(40, 119, 236, 0.35);--sp1:#fff}.theme-dark.minimal-things-dark.minimal-dark-black{--base-d:5%;--bg3:#24262a;--divider-color:#24262a;--tab-outline-color:#24262a} \ No newline at end of file diff --git a/docs/migration-from-math-booster-version-1.html b/docs/migration-from-math-booster-version-1.html new file mode 100644 index 0000000..0774e5e --- /dev/null +++ b/docs/migration-from-math-booster-version-1.html @@ -0,0 +1 @@ +Migration from Math Booster version 1

Migration from Math Booster version 1
...

Among many improvements that LaTeX-like Theorem & Equation Referencer/Math Booster version 2 introduces is a new format for theorem callouts, which is much cleaner, more intuitive, more keyboard-friendly, and less plugin-dependent than the old format used in version 1.

To fully enjoy version 2, run the command Migrate from version 1 to convert the old format to the new one.

Warning

MAKE SURE YOU HAVE A BACKUP OF YOUR VAULT BEFORE CONVERSION. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM THIS OPERATION.

What's new in version 2
...

No longer supported
...

\ No newline at end of file diff --git a/docs/proof-environment.html b/docs/proof-environment.html new file mode 100644 index 0000000..e26886a --- /dev/null +++ b/docs/proof-environment.html @@ -0,0 +1,12 @@ +Proof environment

Proof environment
...

Remark 1.
  • This is still an experimental feature.
  • See also this post on the forum for more information.

This plugin supports -like proof environments.

`\begin{proof}`
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...
+`\end{proof}`
+

Proof.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...

Custom texts
...

Use the following syntax to print custom text.
Any inline markdown syntax can be used, but inline formulas will render with flickering in the live preview.

`\begin{proof}[Solution.]`
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...
+`\end{proof}`
+

Solution.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...

Linked proofs
...

Suppose that you have a theorem like below and it has a block ID 123456.

Theorem 2 (Title).

Content

The following will be printed as "Proof of Theorem 2 (Title)." by default.

`\begin{proof}`@[[#^123456]]
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...
+`\end{proof}`
+

Proof of Theorem callouts > Theorem 1 (Title).
Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...

Remark 3.

Alternatively, you can just write:

\begin{proof}[Proof of [[#^123456]].]
+

For now, I prefer this due to its potentially higher potability.
The strength of the @ syntax is it's rendered dynamically depending on the current profile.

Settings & styling
...

Don't like \begin{proof}? You can use any string you like instead. Go to the plugin setting tab, and modify the Beginning of proof and Ending of proof fields. Also take a look at the Proofs section in the Profiles editing menu.

Beginning/ending of proofs are given the following CSS classes.

  • .math-booster-begin-proof/.math-booster-end-proof
  • .math-booster-begin-proof-{tag}/.math-booster-end-proof-{tag}: {tag} is each tag associated with the profile applying to the note.

Latex Suite snippet
...

Using the following Latex Suite snippet, you can quickly insert a proof by just typing proof+Tab.

    { trigger: "proof", replacement: "`\\begin{proof}`\n$0\n`\\end{proof}`", options: "t" }
+
\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp b/docs/search-&-link-auto-completion/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp new file mode 100644 index 0000000000000000000000000000000000000000..64319f0a18c5696c8dca1aaed828a873bfa02d4c GIT binary patch literal 8664 zcma)f1ymi&vi1PMf^1wuaMz6lcL?t84#8c6ySoIp;1-;qAp{7pakt+e}z{Y`gGbyb(ljIxxt_!ubwXo!g@sw;A7z61b(wstN~$`T?Znp)Z<@T&mS^YuqFGI4hJ6aA0$nd@@qPu@w!|B>VWoxA`;vCRL(G{3R&pP27Ac6V`bdFE04jh$4LMW3Xai0QejN0C)?3&r#$704Nv$nnwSg`}<5BjGT=A5clGlLceCrG z!lA)u-})Bf{c7dWc%Q_3jmzA=PHVxR*Wi=wdtyZc$Wz8s!3pmZs4>d$Noxo8zBXW%&?}FsqD}n0mI^>UgThb;Fe9ohmON zQ$kER8JD=*H{i1I|G7|{nuc11z!ot*^R+fIQTg$I6BAtL8d7#Kta592N07N1`pd=Y zGF-c@L))K| zLTGH|!%2s@4M{DYdVAs{odcS4bKzFR6CX;qkXo6oI@@=4nSL2J#cV9QxTLzV=!l=p zPg(tKE9GQ_VyjA;vhGRre=r{L@W`}waeUV@_?J5J3TX53E`d!dLH|$MZ>Bc9ssfLz zY(mOz+Ne7oGP8Y3lRr;ZbVjpiR2o;x-2}VAp6INgu&p+7fiv#?_=MhEuPs4eds0%l z$zKN!rdxN5G#LF)CwL(Lb-JZ)4-2$@e>zJ}P?W*cn14xj&5y`srO1_3>i0^i`_`0K zYsbgbvwfii{6D#HFe0%d)Gv*E zDlap1dE~AX->q;Mc091S6CgFj$xf`*f|#j0h`>TLo;isy->WONAAfM&yq!C|Pl@Bt zxX6}esX*eQ^eKGgw{SXe+Lf!97+gdBMN8;A}^59$+cz=YKcM01#kiV_6gA9k?7L}Hc@f_K04(VV)i=z7*ru@aL zKgKMd<^<7S9JYx<_A|;qh1K`bkmqZkE&c7KW4zy-&uO4|aM$pUI8N|I{xcW2kqgED z%9UeO{`~B_ee+w`0%{0|S`un@zHoL6} zj!#*?{2wCq&)DL+{VP9HaA#{R3id2JVgqnSG0O6V*|BadP|G5mGN@=x8JUvv)Fqth z#h81T@c?HxUryt0!MnzT-qO?7YDk&B2*x1;U;Z>a_2x#iXHfyb_=>lf) z_)VfDw|D|8yk_y_m1;^ux@5_Z4Sia&! znx;REd1E$*NWW(q)<(wU_{GH-n*`kU)bL|2DA)}&^mMhq8N#;WlFIF1 z46vUHa?HGrC53aD;>WO_-o;<#025cxQ`t?RVD-Cf##Mh^k@mc?4x*Y7d1NwC?r!ad zc2I01rDOr+l=A0czTpgLMIi)w+z2-$DB0~3+V?yGY6n=Ixs;RH`&cmO_d6EW_F`!- zl^=app6l%@XdwL-d;?Qn=~yI^Vp(MDS^hh5H(SGO-Zl0~#m1|Tw|kAmCLVsLw^-)i zmiR{zsdju5v{eR3H}t_{_tCc zHp7tUG>!i#R)0nCdTa8(3g~+E^&c7c-;?3b`U(J#Pc&bj->xuhCkayttMT>LF*fg% zM#zs*u`^JL)1!|vY;OB;72Y3&5u&CCbtHB-LbnPLR%`}Xq7w*14ieC^y|5jBF?|J= zLH;7&IMDKwJoM1K<$Z6RgM)X%r^P^LC+Pa7!S*%vm{tCV>gt6;mVEkJbRJ>9$ox8a z*IRY0zIgv28U1E|SnCproIj`~N$#iS^-E`>b!8iySf0alWU@r08qf1!Y9&ZYY?2Wq zTN!GYXy(kL>p+5}Orm5`cn0Rw-A5}$;j#W8nF4NgN*+`DB5zPyr$1T-*>_PAj(qDS zD)^FJ`z9iQ3*f;I*$0-Ga9~~C7=d(;>&~JW(H(-_FDt%Tfp$ej2=P~rF6r-^6o{ic zYxBrmmkb`##A*)kL|>JOqny@D(mt)}%bl_9`jySplC=rC_j9DCg4d}-Eb`Nkwh!m; zt`AB=?>Nc!z?(b6Urp2dc>M`{C3z!B;>p&1@Z9YqCL=C>V7+-?RpDWbgREX+*t6Jy zsr4g&_O)NGDIOD|L%t`J)fquJmbI>dXeUl={;@lmg|wjxCb_Esk6Cc|_au&V$G}`*3)cIr+j8cSMOfF~J{XzCCWe z{QA_Ri@PUwM6>5IW;UOaxdG0lm|N;p3sDjN3xUBbEp|UO3Sn+q`fxo*wlcziA7Kw; zw6R)F(_-IjsHWfdl~2b;Tw2$nqQzEFfazB;^cwXw4I+_TsxQrM zvt!pJbt`pMG+Nnk$8PU@S5aVpe&})Zml5{~d0J}q<`TA+i3&m114)q^nY`7RBMrAo zZ&nEK?=>6fuEZrFhg}t-3`c9FmBaMS2F-b zAs^iGmDdqhhQ0`~4%-9!%qt=>Cv>Yw@h01~6c=BhjLf4j%PF|}-D)u^RM!jBf1T~K zMue^(%oy9MO2-51qj?744CTnD&3ozmWOTGZ_|gW}V(vTU$;{q}%@dy?2jtE%P!BS6 zbO~fLS>^`&Vr~KdjVJ;gxX`{!{}!`32W96)q2GCa-X?ehS;sGFS#Xup@#u?wIq%%K z>b6f4_SrAktAl>xfXbdFMIsp|(hzxfM6Mr?i6G))eMX-0D)_rRc{aX?sXh3{>uR1r zN&KELiXqxHh(0a(7G@!q>u0HSCM8zJozo2VTHlW(d*6R4OyRCKR3y&HrKVovlH?eG z-o9HFNM%sL=4~bDUUo36DQ#$p6hkWD%B%uM9o=-8i_aS6ghDa-@Une56xs9K7{EGP zg+w`>wvT#isMeV{N6zTGpWmbXzz?qWp{*%J*Zyi3H<6(EGZAyLxz~_?9Dtiu%n+ph zsK8NV<25NWJqZ>FbF)^&-#o_>-`f)PtKZ{PIA>gA@zl3?pA1;{bCIChtTNU@x{p|dxNbGub1#QM!w?g&NSni{Z)5F{8 zC`zGRj&?>Y!rnNeBAxeY@BwpiE?Aue5piS%vh|_369g!D^WwcWb)sll+w($YylGr% z%ARsI_%yd&5WB!zHSvPXn@y}H&gPIpXiE(6STv4{PrkjVIpIx2wp}a@$lE~^X1IGb zOxq{(oimKXra?W|pCEc*x8L&g&})$5K? z&O!dyFCc08t$&*pClt&$m-}kXv>Xx7hDY%lbOlZii+n6^3-{-%`dOq zP!?dd>R)=D@DZo!Wrs!3&r;jLUQicpnTuTNCDt4&+%4n?bX)lUD_B1~-Vl|C)E_s<+AAGga z=QQ(iXLh47xaMl9P=+l(j>>o;dJ0W`$1mB|=E&wu2BeARpc$IbFZOfv>q5np_1F$E z2I&|$HQ4;q15a3?mw?n95K5qUP`pCpev331G{86fr&k3+pT2iYFqezD+W~NJM?qVeO*(s)938ywD})K%^O2y?UpKxmA0XeQI_;) zR_^j!%6_@lJu-^<4(nWCDb;wIyw{PDiC6yGT4m|+glmv%0P*Q`D^Xe&iB_=O?m2$d%(*#&3dRNWEr z@QwBxt{TW~A^p?nDI%*T;$=STChAP#=<5003tO;lbx!g5mse9>D{r_|NL`pm6$#5L z$G7K*#4&5Itnmm@5iw;3lQu?n5u+5V;YwgjUF?|ki{Sg%F0?&vS8OAe{-2BOml3w`^B5pTF1E~85YOhI5yjNl z{7bY=q2czT&ngWl?L^m}(;79QMYPW!pF}JG)E(YO2>|~m0Kl!^n3XWSFs~Fi@*j|r z@mFqThGg9GD@>nA5z+Q*(Oi-}N9&2C^4EZD2iw4^d#Pk9xI!!#*^vIUK9Lsc@q$#P z(@GLTp7p*RqEDV~hY)3foO@J@8CG%W6*U^YyS^*8)>`l`bCb7k%tIlanb^#`&0)Gl zA2t>SFK8qzvNB_@L!|+?L$*&Jz!UZgSqS#b9R4>>d##C!r}0x8;OhW?vlhutZoY$i zaWaQJ97rAQFltl#v48o;bjmwV1*#ZpH~yxbwUH6DU)241!K=%VKy!^j>%D8z%yKzt zEJ0O1Vh-xJqFD;yZYE8Y@Jpd93@iT@_WOWd_oZt1!ed3@S86p^#h5yzl#|M3`Q#iO zudeJWyAhFkk~nck`o${WQrl{ywiJqqV$RsDsR+yVj6IOgqsVRdpJ&Fn=pX-4F8 zv+glq?8WBkx(~h|8<}Q%h*x>%YbMN(-b=p_D1IyO10xYwn{aKf86;YsEi0-^ zGrwz8HdD-)T*SL3<&GeI6#epuGR;=HKNRxTd?u=rleo&cYYm;V#+WjIrT2w|trfB^ z&KulVL*m>J{OUol5UH-#jVdj5?isPatU4ODstQGCN_u*hw=;8mZhp)i^&`v38E+f| zEV(2lyCY`VZ2w%IZ=~Pn!c&WxXz#7OX@Af9&Mk*lA1}eCdsre=x|uerF!k&P!R-N^ zGkmG`tE4J6TdRPzkXy#}(t+$RaM<4!u}3i7>K&Be)OyF>^y}VGMEt0HM4ICELJM~b zX#MGM->+!XHVbCip$l3Xwu6JsUjh_rCKD_p!f*mqHf66R~ zdQf1<4th5bl}s9ypTXf96h%JDC_HrdxTOSnr!asA%LOA5zUqq(#zXr9^hPF?IS~el~h$&zL@KrdxeZfV(eszL;+O z&csv7HNR|I(G8h~xm0*+NRHP$OXNP$t>4aDX&H0w7g?Fj(qmdkLR#URUQkwDpxlLl=n;2Q zf=BCE=dreNi9VQRA0p2nSNAbds8~E63ugz+ke#@!C3Zncz`wznxv`QMnTqCmL`b5S zdq-b>27k|&h-S^iQXNShn{CKSLtyPX5(gI9CQ$F(lkabj25rwG!Sb-oXX^o#bWcq|7epEiC=c zlp0ePx>i8sl`YU_V2px#UC{mC~VLwoaalqss=P#SB=Kg3j6coLaSco z+f+L&2i!yTLn?QN8KpP zpCt$4P@71KKfCst%9MO}^NYKN>Nng2O!HRxSYPMFz%tPsBE*O)aXU}RHqVy%I^$qb z&^T+m2CfPjw2fK(?27QlNnkuD=?fO|I!p0Wm*?zOTo@Vdt)QFr--tIU5!=CR-Mh#? zr`I0}6ntvBsX%UgYuK@BylL)O82;!PR5_^eq4t_8+`6Od;)4Xm1p^^jzu%ed;&#+# z7uM3W%A3%eKqPAqxKf8)a}LZmsifq_^>Yn8Ih1*{XIsl}XIJ_Y9+{%15)sJk6rkS^ zW?7<_jdTl)K(cmNOX1v#P240@19Zz%j4`MV1t$##MDwgKXqk=U?r;8_@Z|1wi?8|$LtEhD`I!6AO_nT zA6dMo(gE_;MlLYv zAb}R#i)IeUS-9J=;uL?`ZcRNT;q9O~gkF;!6M4y?p75I5QtlF|UduM}>aZC7%4;kY63Nhe7`ri7j zBm{{gxEGP`cJef15u2kGbwMGF#IE(d_OHPVn3IH1PD7fcnO?mH3Yp;XnV*3-X2FPN zSf4$x3&tNt!$>q8iqkR)OP=@^>+8*_u)3v*uIZceTQR=V4Pzwk3GU1@hW^k$=efr9 zn?i1{3(1dW*%$c8kS+YlDs|OzJm+Da=Ar-Yh~)=p3tlil)YNm|`L2Co{2Gd?IVw%l%bS z*=;jc!QzN;*6@-x75l|8+tRF6X--e}!b=kP0vkCZg@c{1wz64D?{OhYgLbCy%b>_k zvWjxp#TPY*lM{;zd+l9MAc}}Cemub>5lj*3^DF6`*QjRZ?_YLMT;b-3D3bv#Y4*I4npi2%L~=0`rHsLC~q4O zFR}Hjg)Zc?FE?VfNw}!w1xAhK`10C1M3`1=??=GgrTkDDj2_4G2eOJQLRDrp{&Xvl zI~;tK=vW%S?tnw;YpXFd>`)MWh1`lbM&*|e@rdeR(j^p}AB(1eJ78J&Mv)$846HAH zj0@HTIx^I(D`Wb%pS6YAmhowm*d!AZ*0f$jS?z0{GY(5_Nc;dcyaLKHTbL2c^03i; z>_0}?Tg&n!Y|wOaUUi<+Ani8C6BR`5)`J;qUd)ii!nBBU82 zN!#xwSKgm%c6IWF|63yiE5pq?9FCi`%1|Oq$CqywDrXf6M_M^TqYgDp94?J%#s*er z{n}iG-;eo@iN5&~%kk34VxC^XlDvJS)H9kzg!Ox%(SQ%#nSrTHzi7R6V8a<-BqO3pKD94E;_9;}>-jovw`||9 z%+~KosFRspNKs`uy5sPr*{z}jZ<@%N?<+uQFhgriaL;&=X~EU-PQ)0I$lw$t7fO{( zpO+Le{1Y~`u{*&tTP56b?hHTN6@qMMj2%9ESyZNKUXPAMtIDLao0G|wZnAH>r&3^# zeR~n_t&Q+*7)4Jda}*Wx^^-rHXw0lu3q9FmVpt!4@)3%JPIP)H?$m5s5jEAavj!4G ztnO~iS>WZiKHKhqP$+#Gt&f{S{ufXo-(h6cFYdGDm^X{lNW^T5*27;XQJN#05Ttq{ zNN-%Tv1W^DZ&%6nodzffrDl)ttIjRu)vTk5X-S1jL z8z24mzoiq8VKTTEBn)pVbe;LERLFGXk07hmt4dWtR6jt(r*X*cs}-|^N0F2=l;j6= znkVQ=b1a)o($r6!T&3q2{g*#67OQ_rf~QL6w=K-I5P zyjcr*LC2+2BK|bMf*W+-)F5C<0KdbIAuto-completion

Editor auto-completion
...

Math Booster lets you insert links to theorems or equations with ease.

In the editor, type \ref (by default; you can change it as you like in the plugin settings). Then, live suggestions for all theorem callouts & equation blocks in the entire vault show up.

Suggestions show up

Press Enter to insert a link to the selected item.

Suggestion inserted

Or you can jump to the selected item by pressing Cmd + Enter on Mac / Ctrl + Enter on Windows.

Jump to suggestion

Use \tref or \eqref (by default) instead of \ref to suggest only theorems or only equations.

Suggest only theorems

Suggest only equations

Available search keys
...

Theorem suggestion
...

KeyExample
Environment typedefinition, theorem, ...
Formatted titleDefinition 1.1 (Continuity)
Formatted label[1]def:continuity
Note pathfolder/note.md

Theorem suggestion example

Equation suggestion
...

KeyExample
Equation number (if any)(1)
LaTeX source code\lim_{n \to \infty} \int f_n \, d\mu = \int f \, d\mu
Note pathfolder/note.md

Equation suggestion example

Remark
...

This feature inserts wikilinks (i.e. [[]]) even if you are turning off Use \[\[Wikilinks\]\] in the app settings because markdown links are not suitable for dynamically updating the displayed text.

Search modal
...

Query type
...

Choose among:

  • search both theorems & equations
  • search theorem callouts only
  • search equations only

Search range
...

Specify the range of files to be searched. Available options are:

  • Vault: the entire vault
  • Recent notes: recently opened notes
  • Active note: the note currently edited
  • Dataview query: filter notes by powerful Dataview queries. Only available if Dataview is enabled.

Dataview queries
...

Although most parts of Math Booster don't require Dataview, I recommend installing it together with Math Booster because it brings a tremendous filtering capability to this theorem & equation searching functionality.

Note that only LIST queries without GROUP BY are supported.

Example
  • Filtering by a folder
    LIST FROM "Papers"
    +
  • Filtering by a tag
    LIST FROM #statistics
    +
  • Filtering by dates
    LIST WHERE file.cday >= date("2023-11-01")
    +

  1. Requires the Include theorem callout label for search target option to be turned on.↩︎
\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/custom-link-autocomplete.html b/docs/search-&-link-auto-completion/custom-link-autocomplete.html new file mode 100644 index 0000000..9819c9f --- /dev/null +++ b/docs/search-&-link-auto-completion/custom-link-autocomplete.html @@ -0,0 +1 @@ +Custom link autocomplete

Custom link autocomplete
...

In the editor, type \ref (by default). Then, live suggestions for all theorem callouts & equation blocks in the entire vault show up.

  • Enter: insert a link to the selected item.
  • Shift+Enter: insert a link to the note containing the selected item.
  • Cmd+Enter on Mac/Ctrl+Enter on Windows: jump to the selected item by pressing.

Use \tref or \eqref (by default) instead of \ref to suggest only theorems or only equations.

Remark 1 (the Better Link Autocompletion plugin).

I also wrote another plugin that renders equation inside Obsidian's built-in link autocompletion.
Check it out here: https://github.com/RyotaUshio/obsidian-better-link-autocompletion

Filter files to be searched
...

  • \rer/\trer/\eqrer (by default): search only within recently opened notes
  • \rea/\trea/\eqrea (by default): search only within active note
Remark 2.

You can change the trigger strings (e.g. \ref) to whatever you like in the plugin settings.

Remark 3.

There are variants of custom autocomplete. It is recommended to turn off unnecessary ones in the plugin setting to improve performance.

Available search keys
...

Theorem suggestion
...

KeyExample
Environment typedefinition, theorem, ...
Formatted titleDefinition 1.1 (Continuity)
Formatted label[1]def:continuity
Note pathfolder/note.md

Equation suggestion
...

KeyExample
Equation number (if any)(1)
LaTeX source code\lim_{n \to \infty} \int f_n \, d\mu = \int f \, d\mu
Note pathfolder/note.md
Remark 4 (Wikilinks vs. markdown links).

This feature inserts wikilinks (i.e. [[]]) even if you are turning off Use [[Wikilinks]] in the app settings because markdown links are not suitable for dynamically updating the displayed text.


  1. Requires the Include theorem callout label for search target option to be turned on.↩︎
\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/custom-link-completion.html b/docs/search-&-link-auto-completion/custom-link-completion.html new file mode 100644 index 0000000..b3aabc9 --- /dev/null +++ b/docs/search-&-link-auto-completion/custom-link-completion.html @@ -0,0 +1 @@ +Custom link completion

Custom link completion
...

Math Booster lets you insert links to theorems or equations with ease.

In the editor, type \ref (by default). Then, live suggestions for all theorem callouts & equation blocks in the entire vault show up.

  • Enter: insert a link to the selected item.
  • Shift+Enter: insert a link to the note containing the selected item.
  • Cmd+Enter on Mac/Ctrl+Enter on Windows: jump to the selected item by pressing.

Use \tref or \eqref (by default) instead of \ref to suggest only theorems or only equations.

Remark 1 (the Better Link Autocompletion plugin).

I also wrote another plugin that renders equation inside Obsidian's built-in link autocompletion.
Check it out here: https://github.com/RyotaUshio/obsidian-better-link-autocompletion

Filter files to be searched
...

  • \rer/\trer/\eqrer (by default): search only within recently opened notes
  • \rea/\trea/\eqrea (by default): search only within active note
Remark 2.

You can change the trigger strings (e.g. \ref) to whatever you like in the plugin settings.

Remark 3.

There are variants of link auto-completion. It is recommended to turn off unnecessary ones in the plugin setting to improve performance.

Available search keys
...

Theorem suggestion
...

KeyExample
Environment typedefinition, theorem, ...
Formatted titleDefinition 1.1 (Continuity)
Formatted label[1]def:continuity
Note pathfolder/note.md

Equation suggestion
...

KeyExample
Equation number (if any)(1)
LaTeX source code\lim_{n \to \infty} \int f_n \, d\mu = \int f \, d\mu
Note pathfolder/note.md
Remark 4 (Wikilinks vs. markdown links).

This feature inserts wikilinks (i.e. [[]]) even if you are turning off Use [[Wikilinks]] in the app settings because markdown links are not suitable for dynamically updating the displayed text.


  1. Requires the Include theorem callout label for search target option to be turned on.↩︎
\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/editor-auto-completion.html b/docs/search-&-link-auto-completion/editor-auto-completion.html new file mode 100644 index 0000000..c3f5c74 --- /dev/null +++ b/docs/search-&-link-auto-completion/editor-auto-completion.html @@ -0,0 +1 @@ +Editor auto-completion

Editor auto-completion
...

Math Booster lets you insert links to theorems or equations with ease.

In the editor, type \ref (by default). Then, live suggestions for all theorem callouts & equation blocks in the entire vault show up.

  • Enter: insert a link to the selected item.
  • Shift+Enter: insert a link to the note containing the selected item.
  • Cmd+Enter on Mac/Ctrl+Enter on Windows: jump to the selected item by pressing.

Use \tref or \eqref (by default) instead of \ref to suggest only theorems or only equations.

Remark 1 (the Better Link Autocompletion plugin).

I also wrote another plugin that renders equation inside Obsidian's built-in link autocompletion.
Check it out here: https://github.com/RyotaUshio/obsidian-better-link-autocompletion

Filter files to be searched
...

  • \rer/\trer/\eqrer (by default): search only within recently opened notes
  • \rea/\trea/\eqrea (by default): search only within active note
Remark 2.

You can change the trigger strings (e.g. \ref) to whatever you like in the plugin settings.

Remark 3.

There are variants of link auto-completion. It is recommended to turn off unnecessary ones in the plugin setting to improve performance.

Available search keys
...

Theorem suggestion
...

KeyExample
Environment typedefinition, theorem, ...
Formatted titleDefinition 1.1 (Continuity)
Formatted label[1]def:continuity
Note pathfolder/note.md

Equation suggestion
...

KeyExample
Equation number (if any)(1)
LaTeX source code\lim_{n \to \infty} \int f_n \, d\mu = \int f \, d\mu
Note pathfolder/note.md
Remark 4 (Wikilinks vs. markdown links).

This feature inserts wikilinks (i.e. [[]]) even if you are turning off Use [[Wikilinks]] in the app settings because markdown links are not suitable for dynamically updating the displayed text.


  1. Requires the Include theorem callout label for search target option to be turned on.↩︎
\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-autocomplete.html b/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-autocomplete.html new file mode 100644 index 0000000..7f367f4 --- /dev/null +++ b/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-autocomplete.html @@ -0,0 +1 @@ +Enhancing Obsidian's built-in link autocomplete

Enhancing Obsidian's built-in link autocomplete
...

Math Booster enhances Obsidian's built-in link autocomplete (the one that is triggered when you type [[ in the editor) by displaying formatted theorem titles and rendered equations.

Enhancing Obsidian's built-in link autocomplete-20231130210116619.webp

\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-completion.html b/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-completion.html new file mode 100644 index 0000000..49d1046 --- /dev/null +++ b/docs/search-&-link-auto-completion/enhancing-obsidian's-built-in-link-completion.html @@ -0,0 +1 @@ +Enhancing Obsidian's built-in link completion

Enhancing Obsidian's built-in link completion
...

Math Booster enhances Obsidian's built-in link completion (the one that is triggered when you type [[ in the editor) by displaying formatted theorem titles and rendered equations.

This built-in link completion works in a top-down manner: you select a note first, and then choose a theorem or an equation contained in it.
This is useful when you know exactly which note the theorem/equation that you want to reference lives in.

However, it might be also the case you're not very sure where the target theorem/equation is. In that case, you can use either of the following features Math Booster offers:

Both of them work in a bottom-up manner as opposed to the built-in completion. Now you have different tools that you can use for different situations.

\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/search-&-link-auto-completion.html b/docs/search-&-link-auto-completion/search-&-link-auto-completion.html new file mode 100644 index 0000000..8f40ca4 --- /dev/null +++ b/docs/search-&-link-auto-completion/search-&-link-auto-completion.html @@ -0,0 +1 @@ +Search & link auto-completion

Search & link auto-completion
...

Math Booster provides three different tools for quickly search or insert links to your theorems and equations:

\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/search-&-link-autocomplete.html b/docs/search-&-link-auto-completion/search-&-link-autocomplete.html new file mode 100644 index 0000000..120edf6 --- /dev/null +++ b/docs/search-&-link-auto-completion/search-&-link-autocomplete.html @@ -0,0 +1 @@ +Search & link autocomplete

Search & link autocomplete
...

Math Booster provides three different tools for quickly search or insert links to your theorems and equations:

Overview
...

Math Booster enhances Obsidian's built-in link autocomplete (the one that is triggered when you type [[ in the editor) by displaying formatted theorem titles and rendered equations.

This built-in autocomplete works in a top-down manner: you select a note first, and then choose a theorem or an equation contained in it.
This is useful when you know exactly which note the theorem/equation that you want to reference lives in.

However, it might be also the case you're not very sure where the target theorem/equation is. In that case, you can use either of the following features Math Booster offers:

Both of them work in a bottom-up manner as opposed to the built-in completion. Now you have different tools that you can use for different situations.

\ No newline at end of file diff --git a/docs/search-&-link-auto-completion/search-modal.html b/docs/search-&-link-auto-completion/search-modal.html new file mode 100644 index 0000000..20f324c --- /dev/null +++ b/docs/search-&-link-auto-completion/search-modal.html @@ -0,0 +1,4 @@ +Search modal

Search modal
...

Run the Search command to open a search modal.

This is similar to Custom link autocomplete, but available as a search modal like Quick Switcher along with further search control functionalities.

Search modal can be controled by keyboard alone without mouse

Use the Tab/Shift+Tab to move back and forth between the search window and two drop-down menus (Query type & Search range).

Also, you can use Shift+ArrowUp/Shift+ArrowDown to move around in each dropdown menu.

Query type
...

Choose among:

  • search both theorems & equations
  • search theorem callouts only
  • search equations only

Search range
...

Specify the range of files to be searched. Available options are:

  • Vault: the entire vault
  • Recent notes: recently opened notes
  • Active note: the note currently edited
  • Dataview query: filter notes by powerful Dataview queries. Only available if Dataview is enabled.

Dataview queries
...

Although most parts of Math Booster don't require Dataview, I recommend installing it together with Math Booster because it brings a tremendous filtering capability to this theorem & equation searching functionality.

Note that only LIST queries without GROUP BY are supported.

Example
  • Filtering by a folder
    LIST FROM "Papers"
    +
  • Filtering by a tag
    LIST FROM #statistics
    +
  • Filtering by dates
    LIST WHERE file.cday >= date("2023-11-01")
    +
\ No newline at end of file diff --git a/docs/search-&-link-autocomplete/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp b/docs/search-&-link-autocomplete/assets/enhancing-obsidian's-built-in-link-autocomplete-20231130210116619.webp new file mode 100644 index 0000000000000000000000000000000000000000..64319f0a18c5696c8dca1aaed828a873bfa02d4c GIT binary patch literal 8664 zcma)f1ymi&vi1PMf^1wuaMz6lcL?t84#8c6ySoIp;1-;qAp{7pakt+e}z{Y`gGbyb(ljIxxt_!ubwXo!g@sw;A7z61b(wstN~$`T?Znp)Z<@T&mS^YuqFGI4hJ6aA0$nd@@qPu@w!|B>VWoxA`;vCRL(G{3R&pP27Ac6V`bdFE04jh$4LMW3Xai0QejN0C)?3&r#$704Nv$nnwSg`}<5BjGT=A5clGlLceCrG z!lA)u-})Bf{c7dWc%Q_3jmzA=PHVxR*Wi=wdtyZc$Wz8s!3pmZs4>d$Noxo8zBXW%&?}FsqD}n0mI^>UgThb;Fe9ohmON zQ$kER8JD=*H{i1I|G7|{nuc11z!ot*^R+fIQTg$I6BAtL8d7#Kta592N07N1`pd=Y zGF-c@L))K| zLTGH|!%2s@4M{DYdVAs{odcS4bKzFR6CX;qkXo6oI@@=4nSL2J#cV9QxTLzV=!l=p zPg(tKE9GQ_VyjA;vhGRre=r{L@W`}waeUV@_?J5J3TX53E`d!dLH|$MZ>Bc9ssfLz zY(mOz+Ne7oGP8Y3lRr;ZbVjpiR2o;x-2}VAp6INgu&p+7fiv#?_=MhEuPs4eds0%l z$zKN!rdxN5G#LF)CwL(Lb-JZ)4-2$@e>zJ}P?W*cn14xj&5y`srO1_3>i0^i`_`0K zYsbgbvwfii{6D#HFe0%d)Gv*E zDlap1dE~AX->q;Mc091S6CgFj$xf`*f|#j0h`>TLo;isy->WONAAfM&yq!C|Pl@Bt zxX6}esX*eQ^eKGgw{SXe+Lf!97+gdBMN8;A}^59$+cz=YKcM01#kiV_6gA9k?7L}Hc@f_K04(VV)i=z7*ru@aL zKgKMd<^<7S9JYx<_A|;qh1K`bkmqZkE&c7KW4zy-&uO4|aM$pUI8N|I{xcW2kqgED z%9UeO{`~B_ee+w`0%{0|S`un@zHoL6} zj!#*?{2wCq&)DL+{VP9HaA#{R3id2JVgqnSG0O6V*|BadP|G5mGN@=x8JUvv)Fqth z#h81T@c?HxUryt0!MnzT-qO?7YDk&B2*x1;U;Z>a_2x#iXHfyb_=>lf) z_)VfDw|D|8yk_y_m1;^ux@5_Z4Sia&! znx;REd1E$*NWW(q)<(wU_{GH-n*`kU)bL|2DA)}&^mMhq8N#;WlFIF1 z46vUHa?HGrC53aD;>WO_-o;<#025cxQ`t?RVD-Cf##Mh^k@mc?4x*Y7d1NwC?r!ad zc2I01rDOr+l=A0czTpgLMIi)w+z2-$DB0~3+V?yGY6n=Ixs;RH`&cmO_d6EW_F`!- zl^=app6l%@XdwL-d;?Qn=~yI^Vp(MDS^hh5H(SGO-Zl0~#m1|Tw|kAmCLVsLw^-)i zmiR{zsdju5v{eR3H}t_{_tCc zHp7tUG>!i#R)0nCdTa8(3g~+E^&c7c-;?3b`U(J#Pc&bj->xuhCkayttMT>LF*fg% zM#zs*u`^JL)1!|vY;OB;72Y3&5u&CCbtHB-LbnPLR%`}Xq7w*14ieC^y|5jBF?|J= zLH;7&IMDKwJoM1K<$Z6RgM)X%r^P^LC+Pa7!S*%vm{tCV>gt6;mVEkJbRJ>9$ox8a z*IRY0zIgv28U1E|SnCproIj`~N$#iS^-E`>b!8iySf0alWU@r08qf1!Y9&ZYY?2Wq zTN!GYXy(kL>p+5}Orm5`cn0Rw-A5}$;j#W8nF4NgN*+`DB5zPyr$1T-*>_PAj(qDS zD)^FJ`z9iQ3*f;I*$0-Ga9~~C7=d(;>&~JW(H(-_FDt%Tfp$ej2=P~rF6r-^6o{ic zYxBrmmkb`##A*)kL|>JOqny@D(mt)}%bl_9`jySplC=rC_j9DCg4d}-Eb`Nkwh!m; zt`AB=?>Nc!z?(b6Urp2dc>M`{C3z!B;>p&1@Z9YqCL=C>V7+-?RpDWbgREX+*t6Jy zsr4g&_O)NGDIOD|L%t`J)fquJmbI>dXeUl={;@lmg|wjxCb_Esk6Cc|_au&V$G}`*3)cIr+j8cSMOfF~J{XzCCWe z{QA_Ri@PUwM6>5IW;UOaxdG0lm|N;p3sDjN3xUBbEp|UO3Sn+q`fxo*wlcziA7Kw; zw6R)F(_-IjsHWfdl~2b;Tw2$nqQzEFfazB;^cwXw4I+_TsxQrM zvt!pJbt`pMG+Nnk$8PU@S5aVpe&})Zml5{~d0J}q<`TA+i3&m114)q^nY`7RBMrAo zZ&nEK?=>6fuEZrFhg}t-3`c9FmBaMS2F-b zAs^iGmDdqhhQ0`~4%-9!%qt=>Cv>Yw@h01~6c=BhjLf4j%PF|}-D)u^RM!jBf1T~K zMue^(%oy9MO2-51qj?744CTnD&3ozmWOTGZ_|gW}V(vTU$;{q}%@dy?2jtE%P!BS6 zbO~fLS>^`&Vr~KdjVJ;gxX`{!{}!`32W96)q2GCa-X?ehS;sGFS#Xup@#u?wIq%%K z>b6f4_SrAktAl>xfXbdFMIsp|(hzxfM6Mr?i6G))eMX-0D)_rRc{aX?sXh3{>uR1r zN&KELiXqxHh(0a(7G@!q>u0HSCM8zJozo2VTHlW(d*6R4OyRCKR3y&HrKVovlH?eG z-o9HFNM%sL=4~bDUUo36DQ#$p6hkWD%B%uM9o=-8i_aS6ghDa-@Une56xs9K7{EGP zg+w`>wvT#isMeV{N6zTGpWmbXzz?qWp{*%J*Zyi3H<6(EGZAyLxz~_?9Dtiu%n+ph zsK8NV<25NWJqZ>FbF)^&-#o_>-`f)PtKZ{PIA>gA@zl3?pA1;{bCIChtTNU@x{p|dxNbGub1#QM!w?g&NSni{Z)5F{8 zC`zGRj&?>Y!rnNeBAxeY@BwpiE?Aue5piS%vh|_369g!D^WwcWb)sll+w($YylGr% z%ARsI_%yd&5WB!zHSvPXn@y}H&gPIpXiE(6STv4{PrkjVIpIx2wp}a@$lE~^X1IGb zOxq{(oimKXra?W|pCEc*x8L&g&})$5K? z&O!dyFCc08t$&*pClt&$m-}kXv>Xx7hDY%lbOlZii+n6^3-{-%`dOq zP!?dd>R)=D@DZo!Wrs!3&r;jLUQicpnTuTNCDt4&+%4n?bX)lUD_B1~-Vl|C)E_s<+AAGga z=QQ(iXLh47xaMl9P=+l(j>>o;dJ0W`$1mB|=E&wu2BeARpc$IbFZOfv>q5np_1F$E z2I&|$HQ4;q15a3?mw?n95K5qUP`pCpev331G{86fr&k3+pT2iYFqezD+W~NJM?qVeO*(s)938ywD})K%^O2y?UpKxmA0XeQI_;) zR_^j!%6_@lJu-^<4(nWCDb;wIyw{PDiC6yGT4m|+glmv%0P*Q`D^Xe&iB_=O?m2$d%(*#&3dRNWEr z@QwBxt{TW~A^p?nDI%*T;$=STChAP#=<5003tO;lbx!g5mse9>D{r_|NL`pm6$#5L z$G7K*#4&5Itnmm@5iw;3lQu?n5u+5V;YwgjUF?|ki{Sg%F0?&vS8OAe{-2BOml3w`^B5pTF1E~85YOhI5yjNl z{7bY=q2czT&ngWl?L^m}(;79QMYPW!pF}JG)E(YO2>|~m0Kl!^n3XWSFs~Fi@*j|r z@mFqThGg9GD@>nA5z+Q*(Oi-}N9&2C^4EZD2iw4^d#Pk9xI!!#*^vIUK9Lsc@q$#P z(@GLTp7p*RqEDV~hY)3foO@J@8CG%W6*U^YyS^*8)>`l`bCb7k%tIlanb^#`&0)Gl zA2t>SFK8qzvNB_@L!|+?L$*&Jz!UZgSqS#b9R4>>d##C!r}0x8;OhW?vlhutZoY$i zaWaQJ97rAQFltl#v48o;bjmwV1*#ZpH~yxbwUH6DU)241!K=%VKy!^j>%D8z%yKzt zEJ0O1Vh-xJqFD;yZYE8Y@Jpd93@iT@_WOWd_oZt1!ed3@S86p^#h5yzl#|M3`Q#iO zudeJWyAhFkk~nck`o${WQrl{ywiJqqV$RsDsR+yVj6IOgqsVRdpJ&Fn=pX-4F8 zv+glq?8WBkx(~h|8<}Q%h*x>%YbMN(-b=p_D1IyO10xYwn{aKf86;YsEi0-^ zGrwz8HdD-)T*SL3<&GeI6#epuGR;=HKNRxTd?u=rleo&cYYm;V#+WjIrT2w|trfB^ z&KulVL*m>J{OUol5UH-#jVdj5?isPatU4ODstQGCN_u*hw=;8mZhp)i^&`v38E+f| zEV(2lyCY`VZ2w%IZ=~Pn!c&WxXz#7OX@Af9&Mk*lA1}eCdsre=x|uerF!k&P!R-N^ zGkmG`tE4J6TdRPzkXy#}(t+$RaM<4!u}3i7>K&Be)OyF>^y}VGMEt0HM4ICELJM~b zX#MGM->+!XHVbCip$l3Xwu6JsUjh_rCKD_p!f*mqHf66R~ zdQf1<4th5bl}s9ypTXf96h%JDC_HrdxTOSnr!asA%LOA5zUqq(#zXr9^hPF?IS~el~h$&zL@KrdxeZfV(eszL;+O z&csv7HNR|I(G8h~xm0*+NRHP$OXNP$t>4aDX&H0w7g?Fj(qmdkLR#URUQkwDpxlLl=n;2Q zf=BCE=dreNi9VQRA0p2nSNAbds8~E63ugz+ke#@!C3Zncz`wznxv`QMnTqCmL`b5S zdq-b>27k|&h-S^iQXNShn{CKSLtyPX5(gI9CQ$F(lkabj25rwG!Sb-oXX^o#bWcq|7epEiC=c zlp0ePx>i8sl`YU_V2px#UC{mC~VLwoaalqss=P#SB=Kg3j6coLaSco z+f+L&2i!yTLn?QN8KpP zpCt$4P@71KKfCst%9MO}^NYKN>Nng2O!HRxSYPMFz%tPsBE*O)aXU}RHqVy%I^$qb z&^T+m2CfPjw2fK(?27QlNnkuD=?fO|I!p0Wm*?zOTo@Vdt)QFr--tIU5!=CR-Mh#? zr`I0}6ntvBsX%UgYuK@BylL)O82;!PR5_^eq4t_8+`6Od;)4Xm1p^^jzu%ed;&#+# z7uM3W%A3%eKqPAqxKf8)a}LZmsifq_^>Yn8Ih1*{XIsl}XIJ_Y9+{%15)sJk6rkS^ zW?7<_jdTl)K(cmNOX1v#P240@19Zz%j4`MV1t$##MDwgKXqk=U?r;8_@Z|1wi?8|$LtEhD`I!6AO_nT zA6dMo(gE_;MlLYv zAb}R#i)IeUS-9J=;uL?`ZcRNT;q9O~gkF;!6M4y?p75I5QtlF|UduM}>aZC7%4;kY63Nhe7`ri7j zBm{{gxEGP`cJef15u2kGbwMGF#IE(d_OHPVn3IH1PD7fcnO?mH3Yp;XnV*3-X2FPN zSf4$x3&tNt!$>q8iqkR)OP=@^>+8*_u)3v*uIZceTQR=V4Pzwk3GU1@hW^k$=efr9 zn?i1{3(1dW*%$c8kS+YlDs|OzJm+Da=Ar-Yh~)=p3tlil)YNm|`L2Co{2Gd?IVw%l%bS z*=;jc!QzN;*6@-x75l|8+tRF6X--e}!b=kP0vkCZg@c{1wz64D?{OhYgLbCy%b>_k zvWjxp#TPY*lM{;zd+l9MAc}}Cemub>5lj*3^DF6`*QjRZ?_YLMT;b-3D3bv#Y4*I4npi2%L~=0`rHsLC~q4O zFR}Hjg)Zc?FE?VfNw}!w1xAhK`10C1M3`1=??=GgrTkDDj2_4G2eOJQLRDrp{&Xvl zI~;tK=vW%S?tnw;YpXFd>`)MWh1`lbM&*|e@rdeR(j^p}AB(1eJ78J&Mv)$846HAH zj0@HTIx^I(D`Wb%pS6YAmhowm*d!AZ*0f$jS?z0{GY(5_Nc;dcyaLKHTbL2c^03i; z>_0}?Tg&n!Y|wOaUUi<+Ani8C6BR`5)`J;qUd)ii!nBBU82 zN!#xwSKgm%c6IWF|63yiE5pq?9FCi`%1|Oq$CqywDrXf6M_M^TqYgDp94?J%#s*er z{n}iG-;eo@iN5&~%kk34VxC^XlDvJS)H9kzg!Ox%(SQ%#nSrTHzi7R6V8a<-BqO3pKD94E;_9;}>-jovw`||9 z%+~KosFRspNKs`uy5sPr*{z}jZ<@%N?<+uQFhgriaL;&=X~EU-PQ)0I$lw$t7fO{( zpO+Le{1Y~`u{*&tTP56b?hHTN6@qMMj2%9ESyZNKUXPAMtIDLao0G|wZnAH>r&3^# zeR~n_t&Q+*7)4Jda}*Wx^^-rHXw0lu3q9FmVpt!4@)3%JPIP)H?$m5s5jEAavj!4G ztnO~iS>WZiKHKhqP$+#Gt&f{S{ufXo-(h6cFYdGDm^X{lNW^T5*27;XQJN#05Ttq{ zNN-%Tv1W^DZ&%6nodzffrDl)ttIjRu)vTk5X-S1jL z8z24mzoiq8VKTTEBn)pVbe;LERLFGXk07hmt4dWtR6jt(r*X*cs}-|^N0F2=l;j6= znkVQ=b1a)o($r6!T&3q2{g*#67OQ_rf~QL6w=K-I5P zyjcr*LC2+2BK|bMf*W+-)F5C<0KdbICustom link autocomplete

Custom link autocomplete
...

New in version 2.2.0

If you have the Quick Preview plugin installed, holding down Alt/Option (by default) will trigger a quick preview of the selected suggestion with the context around it.

In the editor, type \ref (by default). Then, live suggestions for all theorem callouts & equation blocks in the entire vault show up.

  • Enter: insert a link to the selected item.
  • Shift+Enter: insert a link to the note containing the selected item.
  • Cmd+Enter on Mac/Ctrl+Enter on Windows: jump to the selected item by pressing.

Use \tref or \eqref (by default) instead of \ref to suggest only theorems or only equations.

Filter files to be searched
...

  • \ref:r/\tref:r/\eqref:r (by default): search only within recently opened notes
  • \ref:a/\tref:a/\eqref:a (by default): search only within active note
Remark 1.

You can change the trigger strings (e.g. \ref) to whatever you like in the plugin settings.

Remark 2.

There are variants of custom autocomplete. It is recommended to turn off unnecessary ones in the plugin setting to improve performance.

Available search keys
...

Theorem suggestion
...

KeyExample
Environment typedefinition, theorem, ...
Formatted titleDefinition 1.1 (Continuity)
Formatted label[1]def:continuity
Note pathfolder/note.md

Equation suggestion
...

KeyExample
Equation number (if any)(1)
LaTeX source code\lim_{n \to \infty} \int f_n \, d\mu = \int f \, d\mu
Note pathfolder/note.md
Remark 3 (Wikilinks vs. markdown links).

This feature inserts wikilinks (i.e. [[]]) even if you are turning off Use [[Wikilinks]] in the app settings because markdown links are not suitable for dynamically updating the displayed text.


  1. Requires the Include theorem callout label for search target option to be turned on.↩︎
\ No newline at end of file diff --git a/docs/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html b/docs/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html new file mode 100644 index 0000000..73cb261 --- /dev/null +++ b/docs/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html @@ -0,0 +1 @@ +Enhancing Obsidian's built-in link autocomplete

Enhancing Obsidian's built-in link autocomplete
...

Important

This feature is planned to be removed from this plugin in the near future, and instead, it will be available as a separate plugin Rendered Block Link Suggestions, which supports all types of blocks not limited to display math. Currently awaiting for approval by the Obsidian team.

This plugin enhances Obsidian's built-in link autocomplete (the one that is triggered when you type [[ in the editor) by displaying formatted theorem titles and rendered equations.

Enhancing Obsidian's built-in link autocomplete-20231130210116619.webp

\ No newline at end of file diff --git a/docs/search-&-link-autocomplete/search-&-link-autocomplete.html b/docs/search-&-link-autocomplete/search-&-link-autocomplete.html new file mode 100644 index 0000000..5e66e78 --- /dev/null +++ b/docs/search-&-link-autocomplete/search-&-link-autocomplete.html @@ -0,0 +1 @@ +Search & link autocomplete

Search & link autocomplete
...

This plugin provides three different tools for quickly search or insert links to your theorems and equations:

Overview
...

This plugin enhances Obsidian's built-in link autocomplete (the one that is triggered when you type [[ in the editor) by displaying formatted theorem titles and rendered equations.

This built-in autocomplete works in a top-down manner: you select a note first, and then choose a theorem or an equation contained in it.
This is useful when you know exactly which note the theorem/equation that you want to reference lives in.

However, it might be also the case you're not very sure where the target theorem/equation is. In that case, you can use either of the following features this plugin offers:

Both of them work in a bottom-up manner as opposed to the built-in completion. Now you have different tools that you can use for different situations.

\ No newline at end of file diff --git a/docs/search-&-link-autocomplete/search-modal.html b/docs/search-&-link-autocomplete/search-modal.html new file mode 100644 index 0000000..c28f5fe --- /dev/null +++ b/docs/search-&-link-autocomplete/search-modal.html @@ -0,0 +1,4 @@ +Search modal

Search modal
...

New in version 2.2.0

If you have the Quick Preview plugin installed, holding down Alt/Option (by default) will trigger a quick preview of the selected suggestion with the context around it.

Run the Search command to open a search modal.

This is similar to Custom link autocomplete, but available as a search modal like Quick Switcher along with further search control functionalities.

Search modal can be controled by keyboard alone without mouse

Use the Tab/Shift+Tab to move back and forth between the search window and two drop-down menus (Query type & Search range).

Also, you can use Shift+ArrowUp/Shift+ArrowDown to move around in each dropdown menu.

Query type
...

Choose among:

  • search both theorems & equations
  • search theorem callouts only
  • search equations only

Search range
...

Specify the range of files to be searched. Available options are:

  • Vault: the entire vault
  • Recent notes: recently opened notes
  • Active note: the note currently edited
  • Dataview query: filter notes by powerful Dataview queries. Only available if Dataview is enabled.

Dataview queries
...

Although most parts of this plugin don't require Dataview, I recommend installing it together with this plugin because it brings a tremendous filtering capability to this theorem & equation searching functionality.

Note that only LIST queries without GROUP BY are supported.

Example
  • Filtering by a folder
    LIST FROM "Papers"
    +
  • Filtering by a tag
    LIST FROM #statistics
    +
  • Filtering by dates
    LIST WHERE file.cday >= date("2023-11-01")
    +
\ No newline at end of file diff --git a/docs/settings/local-settings.html b/docs/settings/local-settings.html new file mode 100644 index 0000000..d3dc142 --- /dev/null +++ b/docs/settings/local-settings.html @@ -0,0 +1,13 @@ +Local settings

Local settings
...

Many of this plugin's settings can be applied specifically to a certain folder or file, and they are called local settings.

In the plugin setting tab, you can set up the local settings for the vault root (which we usually call global).
They apply all the files in the vault unless overwritten by another local settings for a lower-level folder/file.

You can make use of this cascaded structure for customizing the settings specific to each textbook or paper, for example:

Folder structure                    Local settings
+───────────────────────────────────────────────────────────────
+vault root
+├── Textbook written in Japanese    <--- Profile = "Japanese"
+│   ├── Chapter 1
+│   │   ├── 1-1.md                  <--- Number prefix = "1.1."
+│   │   └── 1-2.md                  <--- Number prefix = "1.2."
+│   ├── Chapter 2
+│   │   ├── 2-1.md                  <--- Number prefix = "2.1."
+│   │   └── 2-2.md                  <--- Number prefix = "2.2."
+├── Paper written in English.md     <--- Profile = "English"
+├── ...
+

How to set up local settings
...

There are several ways to set up the local settings.

  • File explorer: Right-click on a file/folder, and then click LaTeX-like Theorem & Equation Referencer: Open local settings.
  • Plugin setting tab (Global for the vault root, Local for other files/folders)
  • Open local settings for the current note command
  • Open local settings for the current note button in theorem callout settings
\ No newline at end of file diff --git a/docs/settings/prefix-inference/5.6-sample-note.html b/docs/settings/prefix-inference/5.6-sample-note.html new file mode 100644 index 0000000..da7f071 --- /dev/null +++ b/docs/settings/prefix-inference/5.6-sample-note.html @@ -0,0 +1,4 @@ +5.6. Sample note

This is a sample note to demonstrate how prefix inference works.

This note has a front matter (properties) like this:

---
+section: 1.2
+---
+

Theorem callouts
...

In this sample note, theorem callouts are configured to use this section property (set to 1.2 for example) to infer a prefix.

Theorem 1.2.1 (Title).

Equations
...

In this sample note, equations are set to use the note title 5.6. Sample note instead:

\ No newline at end of file diff --git a/docs/settings/prefix-inference/a-sample-note.html b/docs/settings/prefix-inference/a-sample-note.html new file mode 100644 index 0000000..2e63d52 --- /dev/null +++ b/docs/settings/prefix-inference/a-sample-note.html @@ -0,0 +1,4 @@ +A. Sample note

This is a sample note to demonstrate how prefix inference works.

This note has a front matter (properties) like this:

---
+section: 1.2
+---
+

Theorem callouts
...

In this sample note, theorem callouts are configured to use this section property (set to 1.2 for example) to infer a prefix.

Theorem 1.2.1 (Title).

Equations
...

In this sample note, equations are set to use the note title A. Sample note instead:

\ No newline at end of file diff --git a/docs/settings/prefix-inference/prefix-inference.html b/docs/settings/prefix-inference/prefix-inference.html new file mode 100644 index 0000000..cab0bb5 --- /dev/null +++ b/docs/settings/prefix-inference/prefix-inference.html @@ -0,0 +1 @@ +Prefix inference

Prefix inference
...

Automatically infer a prefix from the note title or properties. If the inference source (title or property) contains whitespaces, the substring before the first whitespace will be parsed for generating a prefix.

See here for an example note.


\ No newline at end of file diff --git a/docs/settings/profiles.html b/docs/settings/profiles.html new file mode 100644 index 0000000..393ea22 --- /dev/null +++ b/docs/settings/profiles.html @@ -0,0 +1,11 @@ +Profiles

Profiles
...

Profiles define the displayed name of each theorem environment (theorem/definition/lemma/...) and proofs.
Click on the Manage profiles button in a local settings pop-up to open the profile manager.
Here, you can edit an existing profile, create a new one from scratch, copy an existing one, or delete one.

For example, the default "English" profile displays exercise as "Exercise."
If you want to change it to "Problem" inside a specific folder, define a new profile:

  1. Create a new profile by copying the "English" profile.
  2. Modify the "exercise" field of the new profile to "Problem."
  3. Apply the profile to the folder.

Tags
...

Each profile has tags. Tags are used to generate CSS classes.

For example, the preset profiles "English" and "Japanese" have "en" and "ja" as their tags, respectively.
In these cases, tags indicate the language used for the note, making it possible to use different styles depending on it. I recommend defining language tags for your custom profiles, too.

Here's a list of the CSS classes generated from tags:

  • .math-booster-{tag}: (New in 0.6.9) Applies to an entire note. This is very powerful... It acts just like Obsidian's built-in cssclasses property (see here), but you don't need to type in the YAML frontmatter manually. Also, remember that profiles have a cascaded structure determined by the folder hierarchy, just like other local settings.
  • .theorem-callout-{tag}: Applies to theorem callouts.
  • .math-booster-begin-proof-{tag}/.math-booster-end-proof-{tag}: Applies to proofs.

Now that we have .math-booster-{tag}, the last two might be unnecessary, but I will keep them to ensure backward compatibility and also let users use, for example, .theorem-callout-en as a handy alias for .math-booster-en .theorem-callout.

Here's a demonstration of how .math-booster-{tag} works (in the right pane, I'm using the CSS editor plugin).

tag.gif

Use case
...

You can use this note-level CSS class, for example, to switch font families depending on whether each note is mathematical.
I prefer the default font for non-mathematical notes in my use case, but I would like more stylish fonts such as CMU Serif and Noto Serif JP for mathematical ones. The following CSS snippet does exactly what I want (it includes additional lines for different line spacing between mathematical/non-mathematical notes).

/* Mathematical notes in Japanese */
+.math-booster-ja:not(.math-booster-nonmath) {
+  --font-text: CMU Serif, Noto Serif JP;
+  --line-height-normal: 1.8;
+}
+
+/* Non-mathematical notes in Japanese */
+.math-booster-ja.math-booster-nonmath {
+  --line-height-normal: 1.6;
+}
+
\ No newline at end of file diff --git a/docs/settings/settings.html b/docs/settings/settings.html new file mode 100644 index 0000000..482af20 --- /dev/null +++ b/docs/settings/settings.html @@ -0,0 +1 @@ +Settings

Settings
...

This plugin offers a high customizability through a lot of configurable options.

\ No newline at end of file diff --git a/docs/theorem-callouts/prefix-inference.html b/docs/theorem-callouts/prefix-inference.html new file mode 100644 index 0000000..0febd41 --- /dev/null +++ b/docs/theorem-callouts/prefix-inference.html @@ -0,0 +1 @@ +Prefix inference

Prefix inference
...

Automatically infer a prefix from the note title or properties. If the inference source (title or property) contains whitespaces, the substring before the first whitespace will be parsed for generating a prefix.

\ No newline at end of file diff --git a/docs/theorem-callouts/style-your-theorems.html b/docs/theorem-callouts/style-your-theorems.html new file mode 100644 index 0000000..a160b5d --- /dev/null +++ b/docs/theorem-callouts/style-your-theorems.html @@ -0,0 +1 @@ +Style your theorems

Style your theorems
...

You can customize the appearance of theorem callouts either by your own custom CSS snippets or by preset sample styles.

In the plugin settings, set Theorem callouts > Style to be Custom if you want to use your custom CSS snippets.
Math Booster defines several custom CSS classes, allowing you to change the styles depending on specific languages or environments (theorem/definition/...).

Otherwise, the selected preset style will be applied. You need to reopen the note to see the style change.
When Don't override the app's font setting when using sample styles is turned on, the preset style will not change the default font family defined in the app's Settings > Appearance > Font > Text font.

Note

"Custom" is highly recommended, since it will give you the most control.

The preset styles are only for a trial purpose, and they might not work well with some non-default themes. You can view the CSS snippets for all the preset styles in the documentation or at the GitHub repository, which you can use as a starting point.

CSS classes
...

Obsidian built-in classes
...

  • .callout
    • .callout > .callout-title
      • .callout > .callout-title > .callout-icon
      • .callout > .callout-title > .callout-title-inner
    • .callout > .callout-content

Math Booster's custom classes
...

  • .theorem-callout: Assigned to all theorem callouts.
  • .theorem-callout-{type}: Indicates the environment type. For example, a theorem callout whose type is "theorem" will be given the .theorem-callout-theorem class.
  • .theorem-callout-{tag}: A profile has tags, each of which is converted to this .theorem-callout-{tag} class. As for the default profiles "English" and "Japanese", the tags are "en" and "ja", respectively. They are used to generate CSS classes .theorem-callout-en and .theorem-callout-ja indicating the language used for the theorem callout, making it possible to use different styles depending on it. I recommend defining language tags for your custom profiles.
  • .theorem-callout-main-title: Lives under .callout-title-inner. For example, the "Theorem 1.1" part for Theorem 1.1 (Cauchy-Schwarz)
  • .theorem-callout-subtitle: Lives under .callout-title-inner. For example, the "(Cauchy-Schwarz)" part for Theorem 1.1 (Cauchy-Schwarz)

Here are the list of preset sample styles. You can also view the CSS snippet generating each style.

MathWiki style
...

This beautiful style is taken from MathWiki. A big thank you to Zhaoshen Zhai, the owner of MathWiki and the MathLinks plugin, for readily consenting to including it in this documentation.

MathWiki style

\ No newline at end of file diff --git a/docs/theorem-callouts/styling.html b/docs/theorem-callouts/styling.html new file mode 100644 index 0000000..a8a7f1a --- /dev/null +++ b/docs/theorem-callouts/styling.html @@ -0,0 +1 @@ +Styling

Styling
...

You can customize the appearance of theorem callouts either by your own custom CSS snippets or by preset sample styles.

In the plugin settings, set Theorem callouts > Style to be Custom if you want to use your custom CSS snippets.
This plugin defines several custom CSS classes, allowing you to change the styles depending on specific languages or environments (theorem/definition/...).

Otherwise, the selected preset style will be applied. You need to reopen the note to see the style change.
When Don't override the app's font setting when using sample styles is turned on, the preset style will not change the default font family defined in the app's Settings > Appearance > Font > Text font.

Remark.

"Custom" is highly recommended, since it will give you the most control.

The preset styles are only for a trial purpose, and they might not work well with some non-default themes. You can view the CSS snippets for all the preset styles in the documentation or at the GitHub repository, which you can use as a starting point.

CSS classes
...

Obsidian built-in classes
...

  • .callout
    • .callout > .callout-title
      • .callout > .callout-title > .callout-icon
      • .callout > .callout-title > .callout-title-inner
    • .callout > .callout-content

Custom classes defined by this plugin
...

  • .theorem-callout: Assigned to all theorem callouts.
  • .theorem-callout-{type}: Indicates the environment type. For example, a theorem callout whose type is "theorem" will be given the .theorem-callout-theorem class.
  • .theorem-callout-{tag}: A profile has tags, each of which is converted to this .theorem-callout-{tag} class. As for the default profiles "English" and "Japanese", the tags are "en" and "ja", respectively. They are used to generate CSS classes .theorem-callout-en and .theorem-callout-ja indicating the language used for the theorem callout, making it possible to use different styles depending on it. I recommend defining language tags for your custom profiles.
  • .theorem-callout-main-title: Lives under .callout-title-inner. For example, the "Theorem 1.1" part for Theorem 1.1 (Cauchy-Schwarz)
  • .theorem-callout-subtitle: Lives under .callout-title-inner. For example, the "(Cauchy-Schwarz)" part for Theorem 1.1 (Cauchy-Schwarz)

Here are the list of preset sample styles. You can also view the CSS snippet generating each style.

MathWiki style
...

This beautiful style is taken from MathWiki. A big thank you to Zhaoshen Zhai, the owner of MathWiki and the MathLinks plugin, for readily consenting to including it in this documentation.

MathWiki style

\ No newline at end of file diff --git a/docs/theorem-callouts/theorem-callouts.html b/docs/theorem-callouts/theorem-callouts.html new file mode 100644 index 0000000..97e2fad --- /dev/null +++ b/docs/theorem-callouts/theorem-callouts.html @@ -0,0 +1,55 @@ +Theorem callouts

Theorem callouts
...

This plugin offers theorem callouts, a special version of Obsidian's built-in callouts. They are designed for creating theorem environments (theorem, definition, lemma, ...) in Obsidian as -like as possible.

Basics
...

The syntax is:

> [!theorem] Title
+> Content
+
+> [!corollary]
+> Content
+

It will be rendered as below.[1] As you can see, theorem callouts are automatically numbered by default. The numbering style can be configured in the settings.

Theorem 1 (Title).

Content

Corollary 2.

Content

One more important thing about theorem callouts is that they can be referenced as if using the cleveref package for . See Clever referencing for the details.

Using aliases
...

Alternatively, you can use a shorer alias thm instead of theorem. It will be rendered the exactly same as above.

> [!thm] Title
+> Content
+
+> [!cor]
+> Content
+
Theorem 3 (Title).

Content

Corollary 4.

Content

Here's the complete list of available envionments and aliases.

Environment nameAlias
axiomaxm
definitiondef
lemmalem
propositionprp
theoremthm
corollarycor
claimclm
assumptionasm
example[2]exm
exerciseexr
conjecturecnj
hypothesishyp
remarkrmk
Note

The aliases are based on Bookdown, but some of the environments are not supported by it.

Unnumbered theorem callouts
...

Similarly to the syntax (e.g. \begin{theorem*}), the following will create an unnumbered theorem callout.

> [!lemma|*] Title
+> Content
+
Lemma (Title).

Content

Manually numbered theorem callouts
...

It is also possible to explicitly specify the theorem number by yourself. It's useful, for example, when making a side note about a textbook or a paper.

> [!def|A.1] Title
+> Content
+
Definition A.1 (Title).

Content

Latex Suite snippets
...

You can insert theorem callouts via the following Latex Suite snippets.

For example, type def+Tab or definition+Tab to insert a definition callout with tabstops.

    { trigger: "axm", replacement: "> [!axiom] $0\n> $1", options: "t" },
+    { trigger: "def", replacement: "> [!definition] $0\n> $1", options: "t" },
+    { trigger: "lem", replacement: "> [!lemma] $0\n> $1", options: "t" },
+    { trigger: "prp", replacement: "> [!proposition] $0\n> $1", options: "t" },
+    { trigger: "thm", replacement: "> [!theorem] $0\n> $1", options: "t" },
+    { trigger: "cor", replacement: "> [!corollary] $0\n> $1", options: "t" },
+    { trigger: "clm", replacement: "> [!claim] $0\n> $1", options: "t" },
+    { trigger: "asm", replacement: "> [!assumption] $0\n> $1", options: "t" },
+    { trigger: "exm", replacement: "> [!example] $0\n> $1", options: "t" },
+    { trigger: "exr", replacement: "> [!exercise] $0\n> $1", options: "t" },
+    { trigger: "cnj", replacement: "> [!conjecture] $0\n> $1", options: "t" },
+    { trigger: "hyp", replacement: "> [!hypothesis] $0\n> $1", options: "t" },
+    { trigger: "rmk", replacement: "> [!remark] $0\n> $1", options: "t" },
+    { trigger: "axiom", replacement: "> [!axiom] $0\n> $1", options: "t" },
+    { trigger: "definition", replacement: "> [!definition] $0\n> $1", options: "t" },
+    { trigger: "lemma", replacement: "> [!lemma] $0\n> $1", options: "t" },
+    { trigger: "proposition", replacement: "> [!proposition] $0\n> $1", options: "t" },
+    { trigger: "theorem", replacement: "> [!theorem] $0\n> $1", options: "t" },
+    { trigger: "corollary", replacement: "> [!corollary] $0\n> $1", options: "t" },
+    { trigger: "claim", replacement: "> [!claim] $0\n> $1", options: "t" },
+    { trigger: "assumption", replacement: "> [!assumption] $0\n> $1", options: "t" },
+    { trigger: "example", replacement: "> [!example] $0\n> $1", options: "t" },
+    { trigger: "exercise", replacement: "> [!exercise] $0\n> $1", options: "t" },
+    { trigger: "conjecture", replacement: "> [!conjecture] $0\n> $1", options: "t" },
+    { trigger: "hypothesis", replacement: "> [!hypothesis] $0\n> $1", options: "t" },
+    { trigger: "remark", replacement: "> [!remark] $0\n> $1", options: "t" },
+

Insert a theorem callout via GUI
...

If you prefer, you can run the Insert theorem callout command to insert a theorem callout via a popout like this.

Theorem callouts-20231117082725480.webp

Customizing the display names of theorem environments
...

See Profiles for the details.

Adding metadata with comments
...

You can add some metadata or options with markdown comments (%% ... %%).

Note

Equations also can be given metadata with comments; see here.

Currently, label, display, and main are available. The followings are all appropriate syntaxes.

> [!theorem]
+> %% label: my-theorem %%
+> %% display: My Awesome Theorem %%
+> %% main %%
+
+> [!theorem]
+> %% label: my-theorem %% %% display: My Awesome Theorem %% %% main %%
+
+> [!theorem]
+> %% 
+> label: my-theorem
+> display: My Awesome Theorem
+> main: true
+> %%
+

They are provided as key-value pairs with a yaml-like key: value format. But as an exception, you can write main instead of main: true.

label
...

Has no effect for now. It might be used when exporting to Pandoc markdown or is supported in the future.

display
...

When set, all links to this theorem callout are displayed with this text instead of the usual formatted theorem title (see also: Clever referencing).

main
...

When set, this theorem is marked as the "main" one of the note containing it. This means all links to this note are displayed with a formatted title of this theorem instead of the note title (see also: Clever referencing).

Styling theorem callouts
...

Although theorem callouts are styled by the preset style by default, you have the full control over their styling through CSS snippets.

See here for more details.

Note

I strongly recommend using your custom styling instead of the presets because the presets are only tested for the default theme and might not work well with other community themes.

To use your custom CSS snippets, go to Settings > Community plugins > LaTeX-like Theorem & Equation Referencer > Global > Theorem callouts > Style and set it to Custom.

As examples, the snippets used for the presets can be found on the GitHub repository of this plugin.

Remarks
...

Theorem callouts will not work perfectly if they are nested inside another callout. Always use them as top-level blocks of your note.


  1. Appearance of theorem callouts depends on how you style them. See Styling for details. ↩︎
  2. > [!example] conflicts with Obsidian's built-in callout type. There is a setting called Don't treat "> [!example]" as a theorem callout, and if it's turned on, an "example" theorem callout can be inserted only with the alias syntax > [!exm].↩︎

\ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..3935703 --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,51 @@ +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; +import inlineWorkerPlugin from "esbuild-plugin-inline-worker"; + +const banner = +`/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +`; + +const prod = (process.argv[2] === "production"); + +const context = await esbuild.context({ + banner: { + js: banner, + }, + entryPoints: ["src/main.ts"], + bundle: true, + plugins: [inlineWorkerPlugin()], + external: [ + "obsidian", + "electron", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", + ...builtins], + format: "cjs", + target: "es2018", + logLevel: "info", + minify: prod, + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/main.js", +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} \ No newline at end of file diff --git a/manifest-beta.json b/manifest-beta.json new file mode 100644 index 0000000..c52fb8d --- /dev/null +++ b/manifest-beta.json @@ -0,0 +1,12 @@ +{ + "id": "math-booster", + "name": "LaTeX-like Equation Referencer", + "version": "2.2.1-YouFoundJK", + "minAppVersion": "1.3.5", + "description": "A powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.", + "author": "Ryota Ushio", + "authorUrl": "https://github.com/RyotaUshio", + "fundingUrl": "https://www.buymeacoffee.com/ryotaushio", + "helpUrl": "https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..c52fb8d --- /dev/null +++ b/manifest.json @@ -0,0 +1,12 @@ +{ + "id": "math-booster", + "name": "LaTeX-like Equation Referencer", + "version": "2.2.1-YouFoundJK", + "minAppVersion": "1.3.5", + "description": "A powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.", + "author": "Ryota Ushio", + "authorUrl": "https://github.com/RyotaUshio", + "fundingUrl": "https://www.buymeacoffee.com/ryotaushio", + "helpUrl": "https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f7a4b3c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2388 @@ +{ + "name": "obsidian-math-booster", + "version": "2.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "obsidian-math-booster", + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.0.3", + "esbuild-plugin-inline-worker": "^0.1.1", + "flatqueue": "^2.0.3", + "monkey-around": "^2.3.0", + "sorted-btree": "^1.8.1" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "^0.19.4", + "obsidian": "latest", + "obsidian-mathlinks": "^0.5.1", + "obsidian-quick-preview": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.9.2", + "resolved": "git+ssh://git@github.com/lishid/cm-language.git#cc6a2cc30288db6be3f879ddf0e3ef64f14ed6ab", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==" + }, + "node_modules/@codemirror/view": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.18.1.tgz", + "integrity": "sha512-xcsXcMkIMd7l3WZEWoc4ljteAiqzxb5gVerRxk5132p5cLix6rTydWTQjsj2oxORepfsrwy1fC4r20iMa9plrg==", + "dependencies": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz", + "integrity": "sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz", + "integrity": "sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.4.tgz", + "integrity": "sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz", + "integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz", + "integrity": "sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz", + "integrity": "sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz", + "integrity": "sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz", + "integrity": "sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz", + "integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz", + "integrity": "sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz", + "integrity": "sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz", + "integrity": "sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz", + "integrity": "sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz", + "integrity": "sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz", + "integrity": "sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz", + "integrity": "sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz", + "integrity": "sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz", + "integrity": "sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz", + "integrity": "sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz", + "integrity": "sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz", + "integrity": "sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz", + "integrity": "sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "peer": true + }, + "node_modules/@lezer/common": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.1.tgz", + "integrity": "sha512-aAPB9YbvZHqAW+bIwiuuTDGB4DG0sYNRObGLxud8cW7osw1ZQxfDuTZ8KQiqfZ0QJGcR34CvpTMDXEyo/+Htgg==" + }, + "node_modules/@lezer/highlight": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", + "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.10.tgz", + "integrity": "sha512-BZfVvf7Re5BIwJHlZXbJn9L8lus5EonxQghyn+ih8Wl36XMFBPTXC0KM0IdUtj9w/diPHsKlXVgL+AlX2jYJ0Q==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.50.tgz", + "integrity": "sha512-OiDU5xRgYTJ203v4cprTs0RwOCd5c5Zjv+K5P8KSqfiCsB1W3LcamTUMcnQarpq5kOYbhHfSOgIEJvdPyb5xyw==", + "dev": true + }, + "node_modules/@types/tern": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.5.tgz", + "integrity": "sha512-POau56wDk3TQ0mQ0qG7XDzv96U5whSENZ9lC0htDvEH+9YUREo+J2U+apWcVRgR2UydEE70JXZo44goG+akTNQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", + "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/type-utils": "5.29.0", + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", + "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", + "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", + "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", + "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", + "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", + "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", + "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "peer": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.4.tgz", + "integrity": "sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.4", + "@esbuild/android-arm64": "0.19.4", + "@esbuild/android-x64": "0.19.4", + "@esbuild/darwin-arm64": "0.19.4", + "@esbuild/darwin-x64": "0.19.4", + "@esbuild/freebsd-arm64": "0.19.4", + "@esbuild/freebsd-x64": "0.19.4", + "@esbuild/linux-arm": "0.19.4", + "@esbuild/linux-arm64": "0.19.4", + "@esbuild/linux-ia32": "0.19.4", + "@esbuild/linux-loong64": "0.19.4", + "@esbuild/linux-mips64el": "0.19.4", + "@esbuild/linux-ppc64": "0.19.4", + "@esbuild/linux-riscv64": "0.19.4", + "@esbuild/linux-s390x": "0.19.4", + "@esbuild/linux-x64": "0.19.4", + "@esbuild/netbsd-x64": "0.19.4", + "@esbuild/openbsd-x64": "0.19.4", + "@esbuild/sunos-x64": "0.19.4", + "@esbuild/win32-arm64": "0.19.4", + "@esbuild/win32-ia32": "0.19.4", + "@esbuild/win32-x64": "0.19.4" + } + }, + "node_modules/esbuild-plugin-inline-worker": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/esbuild-plugin-inline-worker/-/esbuild-plugin-inline-worker-0.1.1.tgz", + "integrity": "sha512-VmFqsQKxUlbM51C1y5bRiMeyc1x2yTdMXhKB6S//++g9aCBg8TfGsbKxl5ZDkCGquqLY+RmEk93TBNd0i35dPA==", + "dependencies": { + "esbuild": "latest", + "find-cache-dir": "^3.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "peer": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-2.0.3.tgz", + "integrity": "sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==", + "engines": { + "node": ">= 12.17.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true, + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/monkey-around": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/monkey-around/-/monkey-around-2.3.0.tgz", + "integrity": "sha512-QWcCUWjqE/MCk9cXlSKZ1Qc486LD439xw/Ak8Nt6l2PuL9+yrc9TJakt7OHDuOqPRYY4nTWBAEFKn32PE/SfXA==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "peer": true + }, + "node_modules/obsidian": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz", + "integrity": "sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==", + "dev": true, + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/obsidian-mathlinks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/obsidian-mathlinks/-/obsidian-mathlinks-0.5.1.tgz", + "integrity": "sha512-VyCvjqcyPnrRj+OeHnQOLO440mosINASQ4Urr14uWqWkNCOcdFnomzKC9hickyY42Bwv1OWZ4+7pllq8woFlcQ==", + "dev": true, + "dependencies": { + "@codemirror/language": "^6.8.0", + "monkey-around": "^2.3.0" + } + }, + "node_modules/obsidian-quick-preview": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/obsidian-quick-preview/-/obsidian-quick-preview-0.5.5.tgz", + "integrity": "sha512-9KJcYfzLrBpt34Mtf4YewR/yAHJRyYUCYpvq487R/TWXEeyrq1vPiZQaedivAsQOFCVIf86ogbb037CQge/y3A==", + "dev": true, + "dependencies": { + "monkey-around": "^2.3.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "peer": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sorted-btree": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sorted-btree/-/sorted-btree-1.8.1.tgz", + "integrity": "sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", + "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "peer": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7dcc7c --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "obsidian-math-booster", + "version": "2.2.0", + "description": "An Obsidian.md plugin that provides a powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production && sass styles.scss styles.css", + "prod": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "dev-style": "sass --watch styles.scss styles.css", + "build-style": "sass --watch styles.scss styles.css", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": [], + "author": "Ryota Ushio", + "license": "MIT", + "devDependencies": { + "@types/node": "^16.11.6", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "^0.19.4", + "obsidian": "latest", + "obsidian-mathlinks": "^0.5.1", + "obsidian-quick-preview": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + }, + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.0.3", + "esbuild-plugin-inline-worker": "^0.1.1", + "flatqueue": "^2.0.3", + "monkey-around": "^2.3.0", + "sorted-btree": "^1.8.1" + } +} diff --git a/src/cleveref.ts b/src/cleveref.ts new file mode 100644 index 0000000..2989456 --- /dev/null +++ b/src/cleveref.ts @@ -0,0 +1,67 @@ +import { EquationBlock } from 'index/typings/markdown'; +import { TFile, HeadingSubpathResult, BlockSubpathResult, App } from 'obsidian'; +import * as MathLinks from 'obsidian-mathlinks'; + +import LatexReferencer from 'main'; +import { MathIndex } from 'index/math-index'; +import { MarkdownPage, MathBlock, TheoremCalloutBlock } from 'index/typings/markdown'; + + +export class CleverefProvider extends MathLinks.Provider { + app: App; + index: MathIndex; + + constructor(mathLinks: any, public plugin: LatexReferencer) { + super(mathLinks); + this.app = plugin.app; + this.index = plugin.indexManager.index; + } + + provide( + parsedLinktext: { path: string; subpath: string; }, + targetFile: TFile | null, + targetSubpathResult: HeadingSubpathResult | BlockSubpathResult | null, + ): string | null { + const { path, subpath } = parsedLinktext; + if (targetFile === null) return null; + const page = this.index.load(targetFile.path); + if (!MarkdownPage.isMarkdownPage(page)) return null + + // only path, no subpath: return page.$refName if it exists, otherwise there's nothing to do + if (!subpath) return page.$refName ?? null; + + const processedPath = path ? page.$refName ?? path : ''; + + // subpath resolution failed, do nothing + if (targetSubpathResult === null) return null; + + // subpath resolution succeeded + if (targetSubpathResult.type === 'block') { + // handle block links + + // get the target block + const block = page.$blocks.get(targetSubpathResult.block.id); + + if (MathBlock.isMathBlock(block)) { + // display text set manually: higher priority + if (block.$display) return path && this.shouldShowNoteTitle(block) ? processedPath + ' > ' + block.$display : block.$display; + // display text computed automatically: lower priority + if (block.$refName) return path && this.shouldShowNoteTitle(block) ? processedPath + ' > ' + block.$refName : block.$refName; + } + } else { + // handle heading links + // just ignore (return null) if we don't need to perform any particular processing + if (path && page.$refName) { + return processedPath + ' > ' + subpath; + } + } + + return null; + } + + shouldShowNoteTitle(block: MathBlock): boolean { + if (TheoremCalloutBlock.isTheoremCalloutBlock(block)) return this.plugin.extraSettings.noteTitleInTheoremLink; + if (EquationBlock.isEquationBlock(block)) return this.plugin.extraSettings.noteTitleInEquationLink; + return true; + } +} \ No newline at end of file diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 0000000..a8e3c45 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,63 @@ +// length must be >= 5 +export const THEOREM_LIKE_ENV_IDs = [ + "axiom", + "definition", + "lemma", + "proposition", + "theorem", + "corollary", + "claim", + "assumption", + "example", + "exercise", + "conjecture", + "hypothesis", + "remark", +] as const; + +export const PROOF_LIKE_ENV_IDs = [ + "proof", + "solution", +] as const; + +export const ENV_IDs = [...THEOREM_LIKE_ENV_IDs, ...PROOF_LIKE_ENV_IDs,] as const; + +// length must be <= 4 +export const THEOREM_LIKE_ENV_PREFIXES = [ + "axm", + "def", + "lem", + "prp", + "thm", + "cor", + "clm", + "asm", + "exm", + "exr", + "cnj", + "hyp", + "rmk", +] as const; + +export type TheoremLikeEnvID = typeof THEOREM_LIKE_ENV_IDs[number]; +export type TheoremLikeEnvPrefix = typeof THEOREM_LIKE_ENV_PREFIXES[number]; + +export interface TheoremLikeEnv { + id: TheoremLikeEnvID, + prefix: TheoremLikeEnvPrefix, +} + +export const THEOREM_LIKE_ENVs = {} as Record; +THEOREM_LIKE_ENV_IDs.forEach((id, index) => { + THEOREM_LIKE_ENVs[id] = {id, prefix: THEOREM_LIKE_ENV_PREFIXES[index]}; +}); + +export const THEOREM_LIKE_ENV_PREFIX_ID_MAP = {} as Record; +THEOREM_LIKE_ENV_PREFIXES.forEach((prefix, index) => { + THEOREM_LIKE_ENV_PREFIX_ID_MAP[prefix] = THEOREM_LIKE_ENV_IDs[index]; +}); + +export const THEOREM_LIKE_ENV_ID_PREFIX_MAP = {} as Record; +THEOREM_LIKE_ENV_IDs.forEach((id, index) => { + THEOREM_LIKE_ENV_ID_PREFIX_MAP[id] = THEOREM_LIKE_ENV_PREFIXES[index]; +}); diff --git a/src/equations/common.ts b/src/equations/common.ts new file mode 100644 index 0000000..76db47f --- /dev/null +++ b/src/equations/common.ts @@ -0,0 +1,68 @@ +import { EquationBlock } from "index/typings/markdown"; +import { finishRenderMath, renderMath } from "obsidian"; +import { MathContextSettings } from "settings/settings"; +import { parseLatexComment } from "utils/parse"; + + + +export function replaceMathTag(displayMathEl: HTMLElement, equation: EquationBlock, settings: Required) { + if (equation.$manualTag) return; // respect a tag (\tag{...}) manually set by the user + + const taggedText = getMathTextWithTag(equation, settings.lineByLine); + if (taggedText) { + const mjxContainerEl = renderMath(taggedText, true); + if (equation.$printName !== null) { + displayMathEl.setAttribute('width', 'full'); + displayMathEl.style.cssText = mjxContainerEl.style.cssText; + } else { + displayMathEl.removeAttribute('width'); + displayMathEl.removeAttribute('style'); + } + displayMathEl.replaceChildren(...mjxContainerEl.childNodes); + } +} + +export function getMathTextWithTag(equation: EquationBlock, lineByLine?: boolean): string | undefined { + if (equation.$printName !== null) { + const tagResult = equation.$printName.match(/^\((.*)\)$/); + if (tagResult) { + const tagContent = tagResult[1]; + return insertTagInMathText(equation.$mathText, tagContent, lineByLine); + } + } + return equation.$mathText; +} + +export function insertTagInMathText(text: string, tagContent: string, lineByLine?: boolean): string { + if (!lineByLine) return text + `\\tag{${tagContent}}`; + + const alignResult = text.match(/^\s*\\begin\{align\}([\s\S]*)\\end\{align\}\s*$/); + if (!alignResult) return text + `\\tag{${tagContent}}`; + + const envStack: string[] = []; + + // remove comments + let alignContent = alignResult[1] + .split('\n') + .map(line => parseLatexComment(line).nonComment) + .join('\n'); + // add tags + let index = 1; + alignContent = alignContent + .split("\\\\") + .map((alignLine) => { + const pattern = /\\(?begin|end)\{(?.*?)\}/g; + let result; + while (result = pattern.exec(alignLine)) { + const { which, env } = result.groups!; + if (which === 'begin') envStack.push(env); + else if (envStack.last() === env) envStack.pop(); + } + if (envStack.length || !alignLine.trim() || alignLine.contains("\\nonumber")) return alignLine; + return alignLine + `\\tag{${tagContent}-${index++}}`; + }).join("\\\\"); + + if (index <= 2) return text + `\\tag{${tagContent}}`; + + return "\\begin{align}" + alignContent + "\\end{align}"; +} diff --git a/src/equations/live-preview.ts b/src/equations/live-preview.ts new file mode 100644 index 0000000..0c1d513 --- /dev/null +++ b/src/equations/live-preview.ts @@ -0,0 +1,119 @@ +/** + * Display equation numbers in Live Preview. + */ + +import { EditorState, StateEffect } from '@codemirror/state'; +import { PluginValue, ViewPlugin, EditorView, ViewUpdate } from '@codemirror/view'; +import { EquationBlock, MarkdownBlock, MarkdownPage } from 'index/typings/markdown'; +import LatexReferencer from 'main'; +import { MarkdownView, TFile, editorInfoField, finishRenderMath } from 'obsidian'; +import { resolveSettings } from 'utils/plugin'; +import { replaceMathTag } from './common'; +import { DEFAULT_SETTINGS, MathContextSettings } from 'settings/settings'; + + +export function createEquationNumberPlugin(plugin: LatexReferencer) { + + const { app, indexManager: { index } } = plugin; + + const forceUpdateEffect = StateEffect.define(); + + plugin.registerEvent(plugin.indexManager.on('index-updated', (file) => { + app.workspace.iterateAllLeaves((leaf) => { + if ( + leaf.view instanceof MarkdownView + && leaf.view.file?.path === file.path + && leaf.view.getMode() === 'source' + ) { + leaf.view.editor.cm?.dispatch({ effects: forceUpdateEffect.of(null) }); + } + }); + })); + + return ViewPlugin.fromClass(class implements PluginValue { + file: TFile | null; + page: MarkdownPage | null; + settings: Required; + + constructor(view: EditorView) { + this.file = view.state.field(editorInfoField).file; + this.page = null; + this.settings = DEFAULT_SETTINGS; + + if (this.file) { + this.settings = resolveSettings(undefined, plugin, this.file); + const page = index.load(this.file.path); + if (MarkdownPage.isMarkdownPage(page)) { + this.page = page; + this.updateEquationNumber(view, this.page); + } + } + } + + updateFile(state: EditorState) { + this.file = state.field(editorInfoField).file; + if (this.file) this.settings = resolveSettings(undefined, plugin, this.file); + } + + async updatePage(file: TFile): Promise { + const page = index.load(file.path); + if (MarkdownPage.isMarkdownPage(page)) this.page = page; + if (!this.page) { + this.page = await plugin.indexManager.reload(file); + } + return this.page; + } + + update(update: ViewUpdate) { + if (!this.file) this.updateFile(update.state); + if (!this.file) return; + + if (update.transactions.some(tr => tr.effects.some(effect => effect.is(forceUpdateEffect)))) { + // index updated + this.settings = resolveSettings(undefined, plugin, this.file); + this.updatePage(this.file).then((updatedPage) => this.updateEquationNumber(update.view, updatedPage)) + } else if (update.geometryChanged) { + if (this.page) this.updateEquationNumber(update.view, this.page); + else this.updatePage(this.file).then((updatedPage) => this.updateEquationNumber(update.view, updatedPage)); + } + } + + async updateEquationNumber(view: EditorView, page: MarkdownPage) { + const mjxContainerElements = view.contentDOM.querySelectorAll(':scope > .cm-embed-block.math > mjx-container.MathJax[display="true"]'); + + for (const mjxContainerEl of mjxContainerElements) { + + // skip if the equation is being edited to avoid the delay of preview + const mightBeClosingDollars = mjxContainerEl.parentElement?.previousElementSibling?.lastElementChild; + const isBeingEdited = mightBeClosingDollars?.matches('span.cm-formatting-math-end'); + if (isBeingEdited) continue; + + const pos = view.posAtDOM(mjxContainerEl); + let block: MarkdownBlock | undefined; + try { + const line = view.state.doc.lineAt(pos).number - 1; // sometimes throws an error for reasons that I don't understand + block = page.getBlockByLineNumber(line); + } catch (err) { + block = page.getBlockByOffset(pos); + } + if (!(block instanceof EquationBlock)) continue; + + // only update if necessary + if (mjxContainerEl.getAttribute('data-equation-print-name') !== block.$printName) { + replaceMathTag(mjxContainerEl, block, this.settings); + } + if (block.$printName !== null) mjxContainerEl.setAttribute('data-equation-print-name', block.$printName); + else mjxContainerEl.removeAttribute('data-equation-print-name'); + } + // DON'T FOREGET THIS CALL!! + // https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer/issues/203 + // https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer/issues/200 + finishRenderMath(); + } + + destroy() { + // I don't know if this is really necessary, but just in case... + finishRenderMath(); + } + }); +} diff --git a/src/equations/reading-view.ts b/src/equations/reading-view.ts new file mode 100644 index 0000000..90f98ab --- /dev/null +++ b/src/equations/reading-view.ts @@ -0,0 +1,162 @@ +/** + * Display equation numbers in reading view, embeds, hover page preview, and PDF export. + */ + +import { App, MarkdownRenderChild, finishRenderMath, MarkdownPostProcessorContext, TFile, Notice } from "obsidian"; + +import LatexReferencer from 'main'; +import { resolveSettings } from 'utils/plugin'; +import { EquationBlock, MarkdownPage } from "index/typings/markdown"; +import { MathIndex } from "index/math-index"; +import { isPdfExport, resolveLinktext } from "utils/obsidian"; +import { replaceMathTag } from "./common"; + + +export const createEquationNumberProcessor = (plugin: LatexReferencer) => async (el: HTMLElement, ctx: MarkdownPostProcessorContext) => { + if (isPdfExport(el)) preprocessForPdfExport(plugin, el, ctx); + + const sourceFile = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath); + if (!(sourceFile instanceof TFile)) return; + + const mjxContainerElements = el.querySelectorAll('mjx-container.MathJax[display="true"]'); + for (const mjxContainerEl of mjxContainerElements) { + ctx.addChild( + new EquationNumberRenderer(mjxContainerEl, plugin, sourceFile, ctx) + ); + } + finishRenderMath(); +} + + +/** + * As a preprocessing for displaying equation numbers in the exported PDF, + * add an attribute representing a block ID to each numbered equation element + * so that EquationNumberRenderer can find the corresponding block from the index + * without relying on the line number. + */ +function preprocessForPdfExport(plugin: LatexReferencer, el: HTMLElement, ctx: MarkdownPostProcessorContext) { + + try { + const topLevelMathDivs = el.querySelectorAll(':scope > div.math.math-block > mjx-container.MathJax[display="true"]'); + + const page = plugin.indexManager.index.getMarkdownPage(ctx.sourcePath); + if (!page) { + new Notice(`${plugin.manifest.name}: Failed to fetch the metadata for PDF export; equation numbers will not be displayed in the exported PDF.`); + return; + } + + let equationIndex = 0; + for (const section of page.$sections) { + for (const block of section.$blocks) { + if (!EquationBlock.isEquationBlock(block)) continue; + + const div = topLevelMathDivs[equationIndex++]; + if (block.$printName) div.setAttribute('data-equation-id', block.$id); + } + } + + if (topLevelMathDivs.length != equationIndex) { + new Notice(`${plugin.manifest.name}: Something unexpected occured while preprocessing for PDF export. Equation numbers might not be displayed properly in the exported PDF.`); + } + } catch (err) { + new Notice(`${plugin.manifest.name}: Something unexpected occured while preprocessing for PDF export. See the developer console for the details. Equation numbers might not be displayed properly in the exported PDF.`); + console.error(err); + } +} + + +export class EquationNumberRenderer extends MarkdownRenderChild { + app: App + index: MathIndex; + + constructor(containerEl: HTMLElement, public plugin: LatexReferencer, public file: TFile, public context: MarkdownPostProcessorContext) { + // containerEl, currentEL are mjx-container.MathJax elements + super(containerEl); + this.app = plugin.app; + this.index = this.plugin.indexManager.index; + + this.registerEvent(this.plugin.indexManager.on("index-initialized", () => { + setTimeout(() => this.update()); + })); + + this.registerEvent(this.plugin.indexManager.on("index-updated", (file) => { + setTimeout(() => { + if (file.path === this.file.path) this.update(); + }); + })); + } + + getEquationCache(lineOffset: number = 0): EquationBlock | null { + const info = this.context.getSectionInfo(this.containerEl); + const page = this.index.getMarkdownPage(this.file.path); + if (!info || !page) return null; + + // get block ID + const block = page.getBlockByLineNumber(info.lineStart + lineOffset) ?? page.getBlockByLineNumber(info.lineEnd + lineOffset); + if (EquationBlock.isEquationBlock(block)) return block; + + return null; + } + + async onload() { + setTimeout(() => this.update()); + } + + onunload() { + // I don't know if this is really necessary, but just in case... + finishRenderMath(); + } + + update() { + // for PDF export + const id = this.containerEl.getAttribute('data-equation-id'); + + const equation = id ? this.index.getEquationBlock(id) : this.getEquationCacheCaringHoverAndEmbed(); + if (!equation) return; + const settings = resolveSettings(undefined, this.plugin, this.file); + replaceMathTag(this.containerEl, equation, settings); + } + + getEquationCacheCaringHoverAndEmbed(): EquationBlock | null { + /** + * https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer/issues/179 + * + * In the case of embeds or hover popovers, the line numbers contained + * in the result of MarkdownPostProcessorContext.getSectionInfo() is + * relative to the content included in the embed. + * In other words, they does not always represent the offset from the beginning of the file. + * So they require special handling. + */ + + const equation = this.getEquationCache(); + + let linktext = this.containerEl.closest('[src]')?.getAttribute('src'); // in the case of embeds + + if (!linktext) { + const hoverEl = this.containerEl.closest('.hover-popover:not(.hover-editor)'); + if (hoverEl) { + // The current context is hover page preview; read the linktext saved in the plugin instance. + linktext = this.plugin.lastHoverLinktext; + } + } + + if (linktext) { // linktext was found + const { file, subpathResult } = resolveLinktext(this.app, linktext, this.context.sourcePath) ?? {}; + + if (!file || !subpathResult) return null; + + const page = this.index.load(file.path); + if (!MarkdownPage.isMarkdownPage(page)) return null; + + if (subpathResult.type === "block") { + const block = page.$blocks.get(subpathResult.block.id); + if (!EquationBlock.isEquationBlock(block)) return null; + return block; + } else { + return this.getEquationCache(subpathResult.start.line); + } + } + + return equation; + } +} diff --git a/src/file-io.ts b/src/file-io.ts new file mode 100644 index 0000000..a07a481 --- /dev/null +++ b/src/file-io.ts @@ -0,0 +1,113 @@ +import { CachedMetadata, Editor, MarkdownView, Pos, TFile } from "obsidian"; + +import LatexReferencer from "./main"; +import { isEditingView, locToEditorPosition } from "utils/editor"; +import { insertAt, splitIntoLines } from "utils/general"; + + +export abstract class FileIO { + constructor(public plugin: LatexReferencer, public file: TFile) { } + abstract setLine(lineNumber: number, text: string): Promise; + abstract setRange(position: Pos, text: string): Promise; + abstract insertLine(lineNumber: number, text: string): Promise; + abstract getLine(lineNumber: number): Promise; + abstract getRange(position: Pos): Promise; +} + + +export class ActiveNoteIO extends FileIO { + /** + * File IO for the currently active markdown view. + * Uses the Editor interface instead of Vault. + * (See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Prefer+the+Editor+API+instead+of+%60Vault.modify%60) + * @param editor + */ + constructor(plugin: LatexReferencer, file: TFile, public editor: Editor) { + super(plugin, file); + } + + async setLine(lineNumber: number, text: string): Promise { + this.editor.setLine(lineNumber, text); + } + + async setRange(position: Pos, text: string): Promise { + const from = locToEditorPosition(position.start); + const to = locToEditorPosition(position.end); + this.editor.replaceRange(text, from, to); + } + + async insertLine(lineNumber: number, text: string): Promise { + this.editor.replaceRange(text + "\n", { line: lineNumber, ch: 0 }); + } + + async getLine(lineNumber: number): Promise { + return this.editor.getLine(lineNumber); + } + + async getRange(position: Pos): Promise { + const from = locToEditorPosition(position.start); + const to = locToEditorPosition(position.end); + const text = this.editor.getRange(from, to); + return text; + } +} + + +export class NonActiveNoteIO extends FileIO { + _data: string | null = null; + + /** + * File IO for non-active (= currently not opened / currently opened but not focused) notes. + * Uses the Vault interface instead of Editor. + */ + constructor(plugin: LatexReferencer, file: TFile) { + super(plugin, file); + } + + async setLine(lineNumber: number, text: string): Promise { + this.plugin.app.vault.process(this.file, (data: string): string => { + const lines = splitIntoLines(data); + lines[lineNumber] = text; + return lines.join('\n'); + }) + } + + async setRange(position: Pos, text: string): Promise { + this.plugin.app.vault.process(this.file, (data: string): string => { + return data.slice(0, position.start.offset) + text + data.slice(position.end.offset + 1, data.length); + }) + } + + async insertLine(lineNumber: number, text: string): Promise { + this.plugin.app.vault.process(this.file, (data: string): string => { + const lines = splitIntoLines(data); + insertAt(lines, text, lineNumber); + return lines.join('\n'); + }) + } + + async getLine(lineNumber: number): Promise { + const data = await this.plugin.app.vault.cachedRead(this.file); + const lines = splitIntoLines(data); + return lines[lineNumber]; + } + + async getRange(position: Pos): Promise { + const content = await this.plugin.app.vault.cachedRead(this.file); + return content.slice(position.start.offset, position.end.offset); + } +} + + +/** + * Automatically judges which of ActiveNoteIO or NonActiveNoteIO + * should be used for the given file. + */ +export function getIO(plugin: LatexReferencer, file: TFile, activeMarkdownView?: MarkdownView | null) { + activeMarkdownView = activeMarkdownView ?? plugin.app.workspace.getActiveViewOfType(MarkdownView); + if (activeMarkdownView && activeMarkdownView.file == file && isEditingView(activeMarkdownView)) { + return new ActiveNoteIO(plugin, file, activeMarkdownView.editor); + } else { + return new NonActiveNoteIO(plugin, file); + } +} diff --git a/src/index/README.md b/src/index/README.md new file mode 100644 index 0000000..baef8cf --- /dev/null +++ b/src/index/README.md @@ -0,0 +1,23 @@ +The source code contained in this directory was taken from the Datacore plugin (https://github.com/blacksmithgu/datacore) and adapted. + +MIT License + +Copyright (c) 2023 Michael Brenan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/index/expression/link.ts b/src/index/expression/link.ts new file mode 100644 index 0000000..84630f4 --- /dev/null +++ b/src/index/expression/link.ts @@ -0,0 +1,184 @@ +import { getFileTitle } from "../utils/normalizers"; + +/** The Obsidian 'link', used for uniquely describing a file, header, or block. */ +export class Link { + /** The file path this link points to. */ + public path: string; + /** The display name associated with the link. */ + public display?: string; + /** The block ID or header this link points to within a file, if relevant. */ + public subpath?: string; + /** Is this link an embedded link (of form '![[hello]]')? */ + public embed: boolean; + /** The type of this link, which determines what 'subpath' refers to, if anything. */ + public type: "file" | "header" | "block"; + + /** Create a link to a specific file. */ + public static file(path: string, embed: boolean = false, display?: string): Link { + return new Link({ + path, + embed, + display, + subpath: undefined, + type: "file", + }); + } + + /** Infer the type of the link from the full internal link path. */ + public static infer(linkpath: string, embed: boolean = false, display?: string): Link { + if (linkpath.includes("#^")) { + let split = linkpath.split("#^"); + return Link.block(split[0], split[1], embed, display); + } else if (linkpath.includes("#")) { + let split = linkpath.split("#"); + return Link.header(split[0], split[1], embed, display); + } else return Link.file(linkpath, embed, display); + } + + /** Create a link to a specific file and header in that file. */ + public static header(path: string, header: string, embed?: boolean, display?: string): Link { + // Headers need to be normalized to alpha-numeric & with extra spacing removed. + return new Link({ + path, + embed, + display, + subpath: header, //normalizeHeaderForLink(header), + type: "header", + }); + } + + /** Create a link to a specific file and block in that file. */ + public static block(path: string, blockId: string, embed?: boolean, display?: string): Link { + return new Link({ + path, + embed, + display, + subpath: blockId, + type: "block", + }); + } + + /** Load a link from it's raw JSON representation. */ + public static fromObject(object: Record): Link { + return new Link(object); + } + + /** Create a link by parsing it's interior part (inside of the '[[]]'). */ + public static parseInner(rawlink: string): Link { + let [link, display] = splitOnUnescapedPipe(rawlink); + return Link.infer(link, false, display); + } + + private constructor(fields: Partial) { + Object.assign(this, fields); + } + + /** Update this link with a new path. */ + public withPath(path: string): Link { + return new Link(Object.assign({}, this, { path })); + } + + /** Return a new link which points to the same location but with a new display value. */ + public withDisplay(display?: string): Link { + return new Link(Object.assign({}, this, { display })); + } + + /** Return a new link which has the given embedded status. */ + public withEmbed(embed: boolean): Link { + if (this.embed == embed) return this; + + return new Link(Object.assign({}, this, { embed })); + } + + /** Convert a file link into a link to a specific header. */ + public withHeader(header: string): Link { + return Link.header(this.path, header, this.embed, this.display); + } + + /** Convert a file link into a link to a specificb lock. */ + public withBlock(block: string): Link { + return Link.block(this.path, block, this.embed, this.display); + } + + /** Checks for link equality (i.e., that the links are pointing to the same exact location). */ + public equals(other: Link): boolean { + if (other == undefined || other == null) return false; + + return this.path == other.path && this.type == other.type && this.subpath == other.subpath; + } + + /** Convert this link to it's markdown representation. */ + public toString(): string { + return this.markdown(); + } + + /** Convert this link to a raw object which is serialization-friendly. */ + public toObject(): Record { + return { + path: this.path, + type: this.type, + subpath: this.subpath, + display: this.display, + embed: this.embed, + }; + } + + /** Convert any link into a link to its file. */ + public toFile() { + return Link.file(this.path, this.embed, this.display); + } + + /** Convert this link into an embedded link. */ + public toEmbed(): Link { + return this.withEmbed(true); + } + + /** Convert this link into a non-embedded link. */ + public fromEmbed(): Link { + return this.withEmbed(false); + } + + /** Convert this link to markdown so it can be rendered. */ + public markdown(): string { + let result = (this.embed ? "!" : "") + "[[" + this.obsidianLink(); + result += this.displayOrDefault(); + result += "]]"; + return result; + } + + /** Obtain the display for this link, or return a simple default display. */ + public displayOrDefault() { + if (this.display) { + return this.display; + } else { + let result = getFileTitle(this.path); + if (this.type == "header" || this.type == "block") result += " > " + this.subpath; + + return result; + } + } + + /** Convert the inner part of the link to something that Obsidian can open / understand. */ + public obsidianLink(): string { + const escaped = this.path.replace("|", "\\|"); + if (this.type == "header") return escaped + "#" + this.subpath?.replace("|", "\\|"); + if (this.type == "block") return escaped + "#^" + this.subpath?.replace("|", "\\|"); + else return escaped; + } + + /** The stripped name of the file this link points to. */ + public fileName(): string { + return getFileTitle(this.path); + } +} + +/** Split on unescaped pipes in an inner link. */ +export function splitOnUnescapedPipe(link: string): [string, string | undefined] { + let pipe = -1; + while ((pipe = link.indexOf("|", pipe + 1)) >= 0) { + if (pipe > 0 && link[pipe - 1] == "\\") continue; + return [link.substring(0, pipe).replace(/\\\|/g, "|"), link.substring(pipe + 1)]; + } + + return [link.replace(/\\\|/g, "|"), undefined]; +} diff --git a/src/index/expression/literal.ts b/src/index/expression/literal.ts new file mode 100644 index 0000000..452fbd7 --- /dev/null +++ b/src/index/expression/literal.ts @@ -0,0 +1,405 @@ +// import { DateTime, Duration } from "luxon"; +import { Link } from "./link"; +// import { renderMinimalDate, renderMinimalDuration } from "utils/normalizers"; + +// Re-exports of useful generic types. +export { Link }; + +/** Shorthand for a mapping from keys to values. */ +export type DataObject = Record; +/** The literal types supported by the query engine. */ +export type LiteralType = + | "boolean" + | "number" + | "string" + // | "date" + // | "duration" + | "link" + | "array" + | "object" + | "function" + | "null"; +/** The raw values that a literal can take on. */ +export type Literal = + | boolean + | number + | string + // | DateTime + // | Duration + | Link + | Array + | DataObject + | Function + | null; + +/** Maps the string type to it's external, API-facing representation. */ +export type LiteralRepr = T extends "boolean" + ? boolean + : T extends "number" + ? number + : T extends "string" + ? string + // : T extends "duration" + // ? Duration + // : T extends "date" + // ? DateTime + : T extends "null" + ? null + : T extends "link" + ? Link + : T extends "array" + ? Array + : T extends "object" + ? DataObject + : T extends "function" + ? Function + : any; + +/** A wrapped literal value which can be switched on. */ +export type WrappedLiteral = + | LiteralWrapper<"string"> + | LiteralWrapper<"number"> + | LiteralWrapper<"boolean"> + // | LiteralWrapper<"date"> + // | LiteralWrapper<"duration"> + | LiteralWrapper<"link"> + | LiteralWrapper<"array"> + | LiteralWrapper<"object"> + | LiteralWrapper<"function"> + | LiteralWrapper<"null">; + +/** Combines a textual type and value; primarily useful for switching on. */ +export interface LiteralWrapper { + type: T; + value: LiteralRepr; +} + +/** Utility library for handling literal values. */ +export namespace Literals { + /** Settings used when formatting literal values to text. */ + export interface ToStringSettings { + /** What a null will render as. */ + nullRepresentation: string; + + /** Date format. */ + dateFormat: string; + + /** Date-time format. */ + dateTimeFormat: string; + } + + /** Sane, English-based defaults for date formats. */ + export const DEFAULT_TO_STRING: ToStringSettings = { + nullRepresentation: "-", + + dateFormat: "MMMM dd, yyyy", + dateTimeFormat: "h:mm a - MMMM dd, yyyy", + }; + + /** Convert an arbitrary value into a reasonable, Markdown-friendly string if possible. */ + export function toString( + field: any, + setting: ToStringSettings = DEFAULT_TO_STRING, + recursive: boolean = false + ): string { + let wrapped = wrapValue(field); + if (!wrapped) return setting.nullRepresentation; + + switch (wrapped.type) { + case "null": + return setting.nullRepresentation; + case "string": + return wrapped.value; + case "number": + case "boolean": + return "" + wrapped.value; + case "link": + return wrapped.value.markdown(); + case "function": + return ""; + case "array": + let result = ""; + if (recursive) result += "["; + result += wrapped.value.map((f) => toString(f, setting, true)).join(", "); + if (recursive) result += "]"; + return result; + case "object": + return ( + "{ " + + Object.entries(wrapped.value) + .map((e) => e[0] + ": " + toString(e[1], setting, true)) + .join(", ") + + " }" + ); + // case "date": + // return renderMinimalDate(wrapped.value, setting.dateFormat, setting.dateTimeFormat); + // case "duration": + // return renderMinimalDuration(wrapped.value); + } + } + + /** Wrap a literal value so you can switch on it easily. */ + export function wrapValue(val: Literal): WrappedLiteral | undefined { + if (isNull(val)) return { type: "null", value: val }; + else if (isNumber(val)) return { type: "number", value: val }; + else if (isString(val)) return { type: "string", value: val }; + else if (isBoolean(val)) return { type: "boolean", value: val }; + // else if (isDuration(val)) return { type: "duration", value: val }; + // else if (isDate(val)) return { type: "date", value: val }; + else if (isArray(val)) return { type: "array", value: val }; + else if (isLink(val)) return { type: "link", value: val }; + else if (isFunction(val)) return { type: "function", value: val }; + else if (isObject(val)) return { type: "object", value: val }; + else return undefined; + } + + /** Recursively map complex objects at the leaves. */ + export function mapLeaves(val: Literal, func: (t: Literal) => Literal): Literal { + if (isObject(val)) { + let result: DataObject = {}; + for (let [key, value] of Object.entries(val)) result[key] = mapLeaves(value, func); + return result; + } else if (isArray(val)) { + let result: Literal[] = []; + for (let value of val) result.push(mapLeaves(value, func)); + return result; + } else { + return func(val); + } + } + + /** Compare two arbitrary JavaScript values. Produces a total ordering over ANY possible datacore value. */ + export function compare( + val1: Literal | undefined, + val2: Literal | undefined, + linkNormalizer?: (link: string) => string + ): number { + // Handle undefined/nulls first. + if (val1 === undefined) val1 = null; + if (val2 === undefined) val2 = null; + if (val1 === null && val2 === null) return 0; + else if (val1 === null) return -1; + else if (val2 === null) return 1; + + // A non-null value now which we can wrap & compare on. + let wrap1 = wrapValue(val1); + let wrap2 = wrapValue(val2); + + if (wrap1 === undefined && wrap2 === undefined) return 0; + else if (wrap1 === undefined) return -1; + else if (wrap2 === undefined) return 1; + + // Short-circuit on different types or on reference equality. + if (wrap1.type != wrap2.type) return wrap1.type.localeCompare(wrap2.type); + if (wrap1.value === wrap2.value) return 0; + + switch (wrap1.type) { + case "string": + return wrap1.value.localeCompare(wrap2.value as string); + case "number": + if (wrap1.value < (wrap2.value as number)) return -1; + else if (wrap1.value == (wrap2.value as number)) return 0; + return 1; + case "null": + return 0; + case "boolean": + if (wrap1.value == wrap2.value) return 0; + else return wrap1.value ? 1 : -1; + case "link": + let link1 = wrap1.value; + let link2 = wrap2.value as Link; + let normalize = linkNormalizer ?? ((x: string) => x); + + // We can't compare by file name or display, since that would break link equality. Compare by path. + let pathCompare = normalize(link1.path).localeCompare(normalize(link2.path)); + if (pathCompare != 0) return pathCompare; + + // Then compare by type. + let typeCompare = link1.type.localeCompare(link2.type); + if (typeCompare != 0) return typeCompare; + + // Then compare by subpath existence. + if (link1.subpath && !link2.subpath) return 1; + if (!link1.subpath && link2.subpath) return -1; + if (!link1.subpath && !link2.subpath) return 0; + + // Since both have a subpath, compare by subpath. + return (link1.subpath ?? "").localeCompare(link2.subpath ?? ""); + // case "date": + // return wrap1.value < (wrap2.value as DateTime) + // ? -1 + // : wrap1.value.equals(wrap2.value as DateTime) + // ? 0 + // : 1; + // case "duration": + // return wrap1.value < (wrap2.value as Duration) + // ? -1 + // : wrap1.value.equals(wrap2.value as Duration) + // ? 0 + // : 1; + case "array": + let f1 = wrap1.value; + let f2 = wrap2.value as any[]; + for (let index = 0; index < Math.min(f1.length, f2.length); index++) { + let comp = compare(f1[index], f2[index]); + if (comp != 0) return comp; + } + return f1.length - f2.length; + case "object": + let o1 = wrap1.value; + let o2 = wrap2.value as Record; + let k1 = Array.from(Object.keys(o1)); + let k2 = Array.from(Object.keys(o2)); + k1.sort(); + k2.sort(); + + let keyCompare = compare(k1, k2); + if (keyCompare != 0) return keyCompare; + + for (let key of k1) { + let comp = compare(o1[key], o2[key]); + if (comp != 0) return comp; + } + + return 0; + case "function": + return 0; + } + } + + /** Find the corresponding datacore type for an arbitrary value. */ + export function typeOf(val: any): LiteralType | undefined { + return wrapValue(val)?.type; + } + + /** Determine if the given value is "truthy" (i.e., is non-null and has data in it). */ + export function isTruthy(field: Literal): boolean { + let wrapped = wrapValue(field); + if (!wrapped) return false; + + switch (wrapped.type) { + case "number": + return wrapped.value != 0; + case "string": + return wrapped.value.length > 0; + case "boolean": + return wrapped.value; + case "link": + return !!wrapped.value.path; + // case "date": + // return wrapped.value.toMillis() != 0; + // case "duration": + // return wrapped.value.as("seconds") != 0; + case "object": + return Object.keys(wrapped.value).length > 0; + case "array": + return wrapped.value.length > 0; + case "null": + return false; + case "function": + return true; + } + } + + /** Deep copy a field. */ + export function deepCopy(field: T): T { + if (field === null || field === undefined) return field; + + if (Literals.isArray(field)) { + return ([] as Literal[]).concat(field.map((v) => deepCopy(v))) as T; + } else if (Literals.isObject(field)) { + let result: Record = {}; + for (let [key, value] of Object.entries(field)) result[key] = deepCopy(value); + return result as T; + } else { + return field; + } + } + + /** Determine if the value is a string. */ + export function isString(val: any): val is string { + return typeof val == "string"; + } + + /** Determine if the value is a number. */ + export function isNumber(val: any): val is number { + return typeof val == "number"; + } + + // /** Determine if the value is a date. */ + // export function isDate(val: any): val is DateTime { + // return val instanceof DateTime; + // } + + // /** Determine if the value is a duration. */ + // export function isDuration(val: any): val is Duration { + // return val instanceof Duration; + // } + + /** Determine if the value is null or undefined. */ + export function isNull(val: any): val is null | undefined { + return val === null || val === undefined; + } + + /** Determine if the value is an array. */ + export function isArray(val: any): val is any[] { + return Array.isArray(val); + } + + /** Determine if the value is a boolean. */ + export function isBoolean(val: any): val is boolean { + return typeof val === "boolean"; + } + + /** Determine if the value is a link. */ + export function isLink(val: any): val is Link { + return val instanceof Link; + } + + /** Checks if the given value is an object (and not any other datacore-recognized object-like type). */ + export function isObject(val: any): val is Record { + return ( + val !== undefined && + typeof val == "object" && + !isArray(val) && + // !isDuration(val) && + // !isDate(val) && + !isLink(val) && + !isNull(val) + ); + } + + /** Determines if the given value is a javascript function. */ + export function isFunction(val: any): val is Function { + return typeof val == "function"; + } +} + +/** A grouping on a type which supports recursively-nested groups. */ +export type GroupElement = { key: Literal; rows: Grouping }; +export type Grouping = T[] | GroupElement[]; + +export namespace Groupings { + /** Determines if the given group entry is a standalone value, or a grouping of sub-entries. */ + export function isElementGroup(entry: T | GroupElement): entry is GroupElement { + return Literals.isObject(entry) && Object.keys(entry).length == 2 && "key" in entry && "rows" in entry; + } + + /** Determines if the given array is a grouping array. */ + export function isGrouping(entry: Grouping): entry is GroupElement[] { + for (let element of entry) if (!isElementGroup(element)) return false; + + return true; + } + + /** Count the total number of elements in a recursive grouping. */ + export function count(elements: Grouping): number { + if (isGrouping(elements)) { + let result = 0; + for (let subgroup of elements) result += count(subgroup.rows); + return result; + } else { + return elements.length; + } + } +} diff --git a/src/index/import/markdown.ts b/src/index/import/markdown.ts new file mode 100644 index 0000000..ccd27b5 --- /dev/null +++ b/src/index/import/markdown.ts @@ -0,0 +1,227 @@ +import { Link } from "index/expression/link"; +import { getFileTitle } from "index/utils/normalizers"; +import { CachedMetadata, SectionCache } from "obsidian"; +import BTree from "sorted-btree"; +import { + JsonMarkdownBlock, + JsonMarkdownPage, + JsonMarkdownSection, + JsonTheoremCalloutBlock, + JsonEquationBlock, +} from "index/typings/json"; +import { MinimalTheoremCalloutSettings } from "settings/settings"; +import { parseMarkdownComment, parseYamlLike, readTheoremCalloutSettings, trimMathText } from "utils/parse"; +import { parseLatexComment } from "utils/parse"; + + +/** + * Given the raw source and Obsidian metadata for a given markdown file, + * return full markdown file metadata. + */ +export function markdownImport( + path: string, + markdown: string, + metadata: CachedMetadata, + excludeExample: boolean +): JsonMarkdownPage { + // Total length of the file. + const lines = markdown.split("\n"); + const empty = !lines.some((line) => line.trim() !== ""); + + ////////////// + // Sections // + ////////////// + + const metaheadings = metadata.headings ?? []; + metaheadings.sort((a, b) => a.position.start.line - b.position.start.line); + + const sections = new BTree(undefined, (a, b) => a - b); + for (let index = 0; index < metaheadings.length; index++) { + const section = metaheadings[index]; + const start = section.position.start.line; + const end = + index == metaheadings.length - 1 ? lines.length - 1 : metaheadings[index + 1].position.start.line - 1; + + sections.set(start, { + $ordinal: index + 1, + $title: section.heading, + $level: section.level, + $position: { start, end }, + $blocks: [], + $links: [], + }); + } + + // Add an implicit section for the "heading" section of the page if there is not an immediate header but there is + // some content in the file. If there are other sections, then go up to that, otherwise, go for the entire file. + const firstSection: [number, JsonMarkdownSection] | undefined = sections.getPairOrNextHigher(0); + if ((!firstSection && !empty) || (firstSection && !emptylines(lines, 0, firstSection[1].$position.start))) { + const end = firstSection ? firstSection[1].$position.start - 1 : lines.length; + sections.set(0, { + $ordinal: 0, + $title: getFileTitle(path), + $level: 1, + $position: { start: 0, end }, + $blocks: [], + $links: [], + }); + } + + //////////// + // Blocks // + //////////// + + // All blocks; we will assign tags and other metadata to blocks as we encounter them. At the end, only blocks that + // have actual metadata will be stored to save on memory pressure. + const blocks = new BTree(undefined, (a, b) => a - b); + let blockOrdinal = 1; + for (const block of metadata.sections || []) { + // Skip headings blocks, we handle them specially as sections. + if (block.type === "heading") continue; + + const start = block.position.start.line; + const end = block.position.end.line; + + let theoremCalloutSettings: MinimalTheoremCalloutSettings | null = null; + let v1 = false; + if (block.type === "callout") { + const settings = readTheoremCalloutSettings(lines[start], excludeExample); + theoremCalloutSettings = settings ?? null; + v1 = !!(settings?.legacy); + } + + if (block.type === "math") { + // Read the LaTeX source + const mathText = trimMathText(getBlockText(markdown, block)); + + // If manually tagged (`\tag{...}`), extract the tag + const tagMatch = mathText.match(/\\tag\{(.*)\}/); + + // Parse additional metadata from LaTeX comments + const metadata: Record = {}; + for (const line of mathText.split('\n')) { + const { comment } = parseLatexComment(line); + if (!comment) continue; + Object.assign(metadata, parseYamlLike(comment)); + } + + blocks.set(start, { + $ordinal: blockOrdinal++, + $position: { start, end }, + $pos: block.position, + $links: [], + $blockId: block.id, + $manualTag: tagMatch?.[1] ?? null, + $mathText: mathText, + $type: "equation", + $label: metadata.label, + $display: metadata.display, + } as JsonEquationBlock); + } else if (theoremCalloutSettings) { + + // Parse additional metadata from Markdown comments + const contentText = lines.slice(start + 1, end + 1).join('\n'); + const commentLines = parseMarkdownComment(contentText); + const metadata: Record = {}; + for (let line of commentLines) { + if (line.startsWith('>')) line = line.slice(1).trim(); + if (!line) continue; + if (line === 'main') metadata.main = 'true'; // %% main %% is the same as %% main: true %% + else Object.assign(metadata, parseYamlLike(line)); + } + + blocks.set(start, { + $ordinal: blockOrdinal++, + $position: { start, end }, + $pos: block.position, + $links: [], + $blockId: block.id, + $settings: theoremCalloutSettings, + $type: "theorem", + $label: metadata.label, + $display: metadata.display, + $main: metadata.main === 'true', + $v1: v1, + } as JsonTheoremCalloutBlock); + } else { + blocks.set(start, { + $ordinal: blockOrdinal++, + $position: { start, end }, + $pos: block.position, + $links: [], + $blockId: block.id, + $type: block.type, + }); + } + } + + // Add blocks to sections. + for (const block of blocks.values() as Iterable) { + const section = sections.getPairOrNextLower(block.$position.start); + + if (section && section[1].$position.end >= block.$position.end) { + section[1].$blocks.push(block); + } + } + + /////////// + // Links // + /////////// + + const links: Link[] = []; + for (let linkdef of metadata.links ?? []) { + const link = Link.infer(linkdef.link); + const line = linkdef.position.start.line; + addLink(links, link); + + const section = sections.getPairOrNextLower(line); + if (section && section[1].$position.end >= line) addLink(section[1].$links, link); + + const block = blocks.getPairOrNextLower(line); + if (block && block[1].$position.end >= line) addLink(block[1].$links, link); + + const listItem = blocks.getPairOrNextHigher(line); + if (listItem && listItem[1].$position.end >= line) addLink(listItem[1].$links, link); + } + + /////////////////////// + // Frontmatter Links // + /////////////////////// + + // Frontmatter links are only assigned to the page. + for (const linkdef of metadata.frontmatterLinks ?? []) { + const link = Link.infer(linkdef.link, false, linkdef.displayText); + addLink(links, link); + } + + return { + $path: path, + $links: links, + $sections: sections.valuesArray(), + $extension: "md", + $position: { start: 0, end: lines.length }, + }; +} + +/** Check if the given line range is all empty. Start is inclusive, end exclusive. */ +function emptylines(lines: string[], start: number, end: number): boolean { + for (let index = start; index < end; index++) { + if (lines[index].trim() !== "") return false; + } + + return true; +} + +/** + * Mutably add the given link to the list only if it is not already present. + * This is O(n) but should be fine for most files; we could eliminate the O(n) by instead + * using intermediate sets but not worth the complexity. + */ +function addLink(target: Link[], incoming: Link) { + if (target.find((v) => v.equals(incoming))) return; + target.push(incoming); +} + +function getBlockText(data: string, block: SectionCache) { + return data.slice(block.position.start.offset, block.position.end.offset); +} \ No newline at end of file diff --git a/src/index/manager.ts b/src/index/manager.ts new file mode 100644 index 0000000..d652812 --- /dev/null +++ b/src/index/manager.ts @@ -0,0 +1,429 @@ +import { App, Component, EventRef, Events, MetadataCache, TAbstractFile, TFile, Vault } from "obsidian"; +import { Deferred, deferred } from "./utils/deferred"; +import { ImporterSettings } from "../settings/settings"; +import { MathImporter } from "./web-worker/importer"; +import { MathIndex } from "./math-index"; +import { ImportResult } from "./web-worker/message"; +import { MarkdownPage } from "./typings/markdown"; +import LatexReferencer from "../main"; +import { iterDescendantFiles } from "utils/obsidian"; +import * as MathLinks from "obsidian-mathlinks"; + + +export class MathIndexManager extends Component { + app: App; + /** Access to the obsidian vault. */ + vault: Vault; + /** Provides access to per-(markdown)-file metadata. */ + metadataCache: MetadataCache; + /** Datacore events, mainly used to update downstream views. This object is shadowed by the Datacore object itself. */ + events: Events; + + /** In-memory index over all stored metadata. */ + index: MathIndex; + /** Asynchronous multi-threaded file importer with throttling. */ + importer: MathImporter; + // /** Local-storage backed cache of metadata objects. */ + // persister: LocalStorageCache; + /** Only set when the index is in the midst of initialization; tracks current progress. */ + initializer?: MathIndexInitializer; + /** If true, the index is fully hydrated and all files have been indexed. */ + initialized: boolean; + + constructor( + public plugin: LatexReferencer, + // public version: string, + public settings: ImporterSettings + ) { + super(); + + const { app } = plugin; + this.app = app; + this.vault = app.vault; + this.metadataCache = app.metadataCache; + // this.persister = new LocalStorageCache("primary", version); + this.events = new Events(); + + this.index = new MathIndex(plugin, app.vault, app.metadataCache); + this.initialized = false; + + this.addChild( + (this.importer = new MathImporter(this.plugin, app.vault, app.metadataCache, () => { + return { + workers: settings.importerNumThreads, + utilization: Math.max(0.1, Math.min(1.0, settings.importerUtilization)), + }; + })) + ); + } + + /** Obtain the current index revision, for determining if anything has changed. */ + get revision() { + return this.index.revision; + } + + /** Initialize datacore by scanning persisted caches and all available files, and queueing parses as needed. */ + initialize() { + // The metadata cache is updated on initial file index and file loads. + this.registerEvent(this.metadataCache.on("resolve", (file) => this.updateLinked(file))); + // Renames do not set off the metadata cache; catch these explicitly. + this.registerEvent(this.vault.on("rename", this.rename, this)); + + // File creation does cause a metadata change, but deletes do not. Clear the caches for this. + this.registerEvent( + this.vault.on("delete", async (file) => { + if (file instanceof TFile) { + await this.updateLinkedOnDeltion(file); + } + if (file.path in this.plugin.settings) { + delete this.plugin.settings[file.path]; + } + this.plugin.excludedFiles.remove(file.path); + }) + ); + + this.registerEvent( + this.on("local-settings-updated", async (file) => { + iterDescendantFiles(file, (descendantFile) => { + if (descendantFile.extension === "md") { + this.index.updateNames(descendantFile); + MathLinks.update(this.app, descendantFile); + }; + }); + }) + ); + + this.registerEvent( + this.on("global-settings-updated", () => { + // re-index the whole vault + const init = new MathIndexInitializer(this); + init.finished().then(() => { + this.removeChild(init); + this.index.touch(); + this.trigger("update", this.revision); + }); + this.addChild(init); + }) + ); + + // Asynchronously initialize actual content in the background using a lifecycle-respecting object. + const init = (this.initializer = new MathIndexInitializer(this)); + init.finished().then((stats: InitializationStats) => { + this.initialized = true; + this.initializer = undefined; + this.removeChild(init); + + const durationSecs = (stats.durationMs / 1000.0).toFixed(3); + console.log( + `${this.plugin.manifest.name}: Imported all theorems and equations in the vault in ${durationSecs}s ` + + `(${stats.imported} notes imported, ${stats.skipped} notes skipped).` + ); + + this.index.touch(); + this.trigger("update", this.revision); + this.trigger("index-initialized"); + MathLinks.update(this.app); + }); + + this.addChild(init); + } + + private async rename(file: TAbstractFile, oldPath: string) { + if (!(file instanceof TFile)) return; + + this.plugin.settings[file.path] = structuredClone(this.plugin.settings[oldPath]); + delete this.plugin.settings[oldPath]; + this.plugin.excludedFiles.remove(oldPath); + this.plugin.excludedFiles.push(file.path); + + // Delete the file at the old path, then request a reload at the new path. + // This is less optimal than what can probably be done, but paths are used in a bunch of places + // (for sections, tasks, etc to refer to their parent file) and it requires some finesse to fix. + this.index.delete(oldPath); + await this.reload(file); + this.index.updateNames(file); + MathLinks.update(this.app); + } + + /** Queue a file for reloading; this is done asynchronously in the background and may take a few seconds. */ + public async reload(file: TFile): Promise { + const result = await this.importer.import(file); + + if (result.type === "error") { + throw new Error(`Failed to import file '${file.name}: ${result.$error}`); + } else if (result.type === "markdown") { + const parsed = MarkdownPage.from(result.result, (link) => { + const rpath = this.metadataCache.getFirstLinkpathDest(link.path, result.result.$path!); + if (rpath) return link.withPath(rpath.path); + else return link; + }); + + this.index.store(parsed, (object, store) => { + store(object.$sections, (section, store) => { + store(section.$blocks); + }); + }); + + this.trigger("update", this.revision); + this.trigger('index-updated', file); + return parsed; + } + + throw new Error("Encountered unrecognized import result type: " + (result as any).type); + } + + /** Given an array of TFiles, this function does two things: + * 1. It reloads (re-imports) each file in the array. + * 2. It re-computes the theorem/equation numbers for all the files containing blocks + * that each file in the array previously linked to. + *   EDIT: Wow, I forgot to update the files that each file in the array newly links to. + * + * This should be named like updateOldAndNewLinkDestinations. + */ + public async updateLinked(file: TFile) { + // Since only linked/referenced equations are numbered, we need to recompute + // the equation numbers for all the files such that + // 1. contained blocks that this file previously linked to, or + // 2. is now containing blocks that this file newly links to. + const toBeUpdated = new Set([file]); // Use Set, not Array, to avoid updating the same file multiple times. + + // get the old outgoing block links (each of which can be potentially a link to some equation) + // before reloading the given file + const oldPage = this.index.load(file.path); + if (MarkdownPage.isMarkdownPage(oldPage)) { + for (const link of oldPage.$links) { + if (link.type === "block") { + const linkedFile = this.vault.getAbstractFileByPath(link.path); + if (linkedFile instanceof TFile) { + toBeUpdated.add(linkedFile); + } + } + } + } + + // re-import the file changed + const newPage = await this.reload(file); + + // get the new outgoing block links (each of which can be potentially a link to some equation) + for (const link of newPage.$links) { + if (link.type === "block") { + const linkedFile = this.vault.getAbstractFileByPath(link.path); + if (linkedFile instanceof TFile) { + toBeUpdated.add(linkedFile); + } + } + } + + // recompute theorem/equation numbers for the previously or currently linked files + toBeUpdated.forEach((fileToBeUpdated) => { + this.index.updateNames(fileToBeUpdated); + MathLinks.update(this.app, fileToBeUpdated); + }); + this.trigger("update", this.revision); + } + + public async updateLinkedOnDeltion(file: TFile) { + // Since only linked/referenced equations are numbered, we need to recompute + // the equation numbers for all the files that contained blocks that this file previously linked to + const toBeUpdated = new Set(); // Use Set, not Array, to avoid updating the same file multiple times. + + // get the old outgoing block links (each of which can be potentially a link to some equation) + // before deleting the given file + const oldPage = this.index.load(file.path); + if (MarkdownPage.isMarkdownPage(oldPage)) { + for (const link of oldPage.$links) { + if (link.type === "block") { + const linkedFile = this.vault.getAbstractFileByPath(link.path); + if (linkedFile instanceof TFile) { + toBeUpdated.add(linkedFile); + } + } + } + } + + // execute deletion + this.index.delete(file.path); + + // recompute theorem/equation numbers for the previously linked files + toBeUpdated.forEach((fileToBeUpdated) => { + this.index.updateNames(fileToBeUpdated); + }); + this.trigger("update", this.revision); + MathLinks.update(this.app); + } + + // Event propogation. + + /** From Datacore: Called whenever the index updates to a new revision. This is the broadest possible datacore event. */ + public on(evt: "update", callback: (revision: number) => any, context?: any): EventRef; + + /** This plugin's custom events */ + // triggered when the index is updated, which means the datacore-level metadata or latex-referencer-level metadata (such as $printName) are updated + public on(evt: "index-updated", callback: (file: TFile) => any): EventRef; + public on(evt: "local-settings-updated", callback: (file: TAbstractFile) => any): EventRef; + public on(evt: "global-settings-updated", callback: () => any): EventRef; + public on(evt: "index-initialized", callback: () => any): EventRef; + + on(evt: string, callback: (...data: any) => any, context?: any): EventRef { + return this.events.on(evt, callback, context); + } + + /** Unsubscribe from an event using the event and original callback. */ + off(evt: string, callback: (...data: any) => any) { + this.events.off(evt, callback); + } + + /** Unsubscribe from an event using the event reference. */ + offref(ref: EventRef) { + this.events.offref(ref); + } + + /** From Datacore: Trigger an update event. */ + public trigger(evt: "update", revision: number): void; + /** This plugin's custom events */ + public trigger(evt: "index-updated", file: TFile): void; + public trigger(evt: "local-settings-updated", file: TAbstractFile): void; + public trigger(evt: "global-settings-updated"): void; + public trigger(evt: "index-initialized"): void; + + /** Trigger an event. */ + trigger(evt: string, ...args: any[]): void { + this.events.trigger(evt, ...args); + } +} + + +/** Lifecycle-respecting file queue which will import files, reading them from the file cache if needed. */ +export class MathIndexInitializer extends Component { + /** Number of concurrent operations the initializer will perform. */ + static BATCH_SIZE: number = 8; + + /** Whether the initializer should continue to run. */ + active: boolean; + + /** Queue of files to still import. */ + queue: TFile[]; + /** The files actively being imported. */ + current: TFile[]; + /** Deferred promise which resolves when importing is done. */ + done: Deferred; + + /** The time that init started in milliseconds. */ + start: number; + /** Total number of files to import. */ + files: number; + /** Total number of imported files so far. */ + initialized: number; + /** Total number of imported files. */ + imported: number; + /** Total number of skipped files. */ + skipped: number; + + constructor(public manager: MathIndexManager) { + super(); + + this.active = false; + this.queue = this.manager.vault.getMarkdownFiles(); + this.files = this.queue.length; + this.start = Date.now(); + this.current = []; + this.done = deferred(); + + this.initialized = this.imported = this.skipped = 0; + } + + async onload() { + // Queue BATCH_SIZE elements from the queue to import. + this.active = true; + + this.runNext(); + } + + /** Promise which resolves when the initialization completes. */ + finished(): Promise { + return this.done; + } + + /** Cancel initialization. */ + onunload() { + if (this.active) { + this.active = false; + this.done.reject("Initialization was cancelled before completing."); + } + } + + /** Poll for another task to execute from the queue. */ + private runNext() { + // Do nothing if max number of concurrent operations already running. + if (!this.active || this.current.length >= MathIndexInitializer.BATCH_SIZE) { + return; + } + + // There is space available to execute another. + const next = this.queue.pop(); + if (next) { + this.current.push(next); + this.init(next) + .then((result) => this.handleResult(next, result)) + .catch((result) => this.handleResult(next, result)); + + this.runNext(); + } else if (!next && this.current.length == 0) { + this.active = false; + + this.manager.vault.getMarkdownFiles().forEach((file) => this.manager.index.updateNames(file)); + MathLinks.update(this.manager.app); + + // All work is done, resolve. + this.done.resolve({ + durationMs: Date.now() - this.start, + files: this.files, + imported: this.imported, + skipped: this.skipped, + }); + } + } + + /** Process the result of an initialization and queue more runs. */ + private handleResult(file: TFile, result: InitializationResult) { + this.current.remove(file); + this.initialized++; + + if (result.status === "skipped") this.skipped++; + else if (result.status === "imported") this.imported++; + + // Queue more jobs for processing. + this.runNext(); + } + + /** Initialize a specific file. */ + private async init(file: TFile): Promise { + try { + const metadata = this.manager.metadataCache.getFileCache(file); + if (!metadata) return { status: "skipped" }; + + await this.manager.reload(file); + return { status: "imported" }; + } catch (ex) { + console.log(`${this.manager.plugin.manifest.name}: Failed to import file: `, ex); + return { status: "skipped" }; + } + } +} + +/** Statistics about a successful vault initialization. */ +export interface InitializationStats { + /** How long initializaton took in miliseconds. */ + durationMs: number; + /** Total number of files that were imported */ + files: number; + /** The number of files that were loaded and imported via background workers. */ + imported: number; + /** The number of files that were skipped due to no longer existing or not being ready. */ + skipped: number; +} + +/** The result of initializing a file. */ +interface InitializationResult { + status: "skipped" | "imported"; +} diff --git a/src/index/math-index.ts b/src/index/math-index.ts new file mode 100644 index 0000000..bb732e7 --- /dev/null +++ b/src/index/math-index.ts @@ -0,0 +1,382 @@ +import { MetadataCache, TFile, Vault } from 'obsidian'; + +import { InvertedIndex } from './storage/inverted'; +import { Indexable, LINKBEARING_TYPE, Linkable } from './typings/indexable'; +import { Link } from 'index/expression/literal'; +import { EquationBlock, MarkdownPage, TheoremCalloutBlock } from './typings/markdown'; + +import LatexReferencer from 'main'; +import { CONVERTER, formatTitle, formatTitleWithoutSubtitle, getEqNumberPrefix } from 'utils/format'; +import { resolveSettings } from 'utils/plugin'; +import { ResolvedMathSettings, TheoremRefFormat } from 'settings/settings'; + + +export class MathIndex { + /** The current store revision. */ + public revision: number; + /** + * Master collection of all object IDs. This is technically redundant with objects.keys() but this is a fast set + * compared to an iterator. + */ + private ids: Set; + /** The master collection of ALL indexed objects, mapping ID -> the object. */ + private objects: Map; + /** Map parent object to it's direct child objects. */ + private children: Map>; + + // Indices for the various accepted query types. These will probably be moved to a different type later. + /** Global map of object type -> list of all objects of that type. */ + private types: InvertedIndex; + // /** Tracks exact tag occurence in objects. */ + // private etags: InvertedIndex; + // /** Tracks tag occurence in objects. */ + // private tags: InvertedIndex; + /** Maps link strings to the object IDs that link to those links. */ + private links: InvertedIndex; + /** Tracks the existence of fields (indexed by normalized key name). */ + // private fields: Map; // irrelevant because we are not going to search/query + /** + * Quick searches for objects in folders. This index only tracks top-level objects - it is expanded recursively to + * find child objects. + */ + // private folder: FolderIndex; // irrelevant because we are not going to search/query + + public constructor(public plugin: LatexReferencer, public vault: Vault, public metadataCache: MetadataCache, + // public settings: Settings // irrelevant because we are not going to search/query + ) { + this.revision = 0; + this.ids = new Set(); + this.objects = new Map(); + this.children = new Map(); + + this.types = new InvertedIndex(); + // this.etags = new InvertedIndex(); + // this.tags = new InvertedIndex(); + this.links = new InvertedIndex(); + // this.fields = new Map(); + // this.folder = new FolderIndex(vault); + } + + /** Update the revision of the datastore due to an external update. */ + public touch() { + this.revision += 1; + } + + /** Load an object by ID. */ + public load(id: string): Indexable | undefined; + /** Load a list of objects by ID. */ + public load(ids: string[]): Indexable[]; + + /** Load an object by ID or list of IDs. */ + load(id: string | string[]): Indexable | Indexable[] | undefined { + if (Array.isArray(id)) { + return id.map((a) => this.load(a)).filter((obj): obj is Indexable => obj !== undefined); + } + + return this.objects.get(id); + } + + /** + * Store the given object, making it immediately queryable. Storing an object + * takes ownership over it, and index-specific variables (prefixed via '$') may be + * added to the object. + */ + public store(object: T | T[], substorer?: Substorer) { + this._recursiveStore(object, this.revision++, substorer, undefined); + } + + /** Recursively store objects using a potential subindexer. */ + private _recursiveStore( + object: T | T[], + revision: number, + substorer?: Substorer, + parent?: Indexable + ) { + // Handle array inputs. + if (Array.isArray(object)) { + for (let element of object) { + this._recursiveStore(element, revision, substorer, parent); + } + + return; + } + + // Delete the previous instance of this object if present. + // TODO: Probably only actually need to delete the root objects. + this._deleteRecursive(object.$id); + + // Assign the next revision to this object; indexed objects are implied to be root objects. + object.$revision = revision; + object.$parent = parent; + + // Add the object to the appropriate object maps. + this.ids.add(object.$id); + this.objects.set(object.$id, object); + + // Add the object to the parent children map. + if (parent) { + if (!this.children.has(parent.$id)) this.children.set(parent.$id, new Set()); + this.children.get(parent.$id)!.add(object.$id); + } + + this._index(object); + + // Index any subordinate objects in this object. + substorer?.(object, (incoming, subindex) => this._recursiveStore(incoming, revision, subindex, object)); + } + + /** Delete an object by ID from the index, recursively deleting any child objects as well. */ + public delete(id: string): boolean { + if (this._deleteRecursive(id)) { + this.revision++; + return true; + } + + return false; + } + + /** Internal method that does not bump the revision. */ + private _deleteRecursive(id: string): boolean { + const object = this.objects.get(id); + if (!object) { + return false; + } + + // Recursively delete all child objects. + const children = this.children.get(id); + if (children) { + for (let child of children) { + this._deleteRecursive(child); + } + + this.children.delete(id); + } + + // Drop this object from the appropriate maps. + this._unindex(object); + this.ids.delete(id); + this.objects.delete(id); + return true; + } + + /** Add the given indexable to the appropriate indices. */ + private _index(object: Indexable) { + this.types.set(object.$id, object.$types); + + // // Exact and derived tags. + // if (object.$types.contains(TAGGABLE_TYPE) && iterableExists(object, "$tags")) { + // const tags = object.$tags as Set; + + // this.etags.set(object.$id, tags); + // this.tags.set(object.$id, extractSubtags(tags)); + // } + + // Exact and derived links. + if (object.$types.contains(LINKBEARING_TYPE) && iterableExists(object, "$links")) { + this.links.set( + object.$id, + (object.$links as Link[]).map((link) => link.obsidianLink()) + ); + } + + // // All fields on an object. + // if (object.$types.contains(FIELDBEARING_TYPE) && "fields" in object) { + // for (const field of object.fields as Iterable) { + // // Skip any index fields. + // if (INDEX_FIELDS.has(field.key)) continue; + + // const norm = field.key.toLowerCase(); + // if (!this.fields.has(norm)) this.fields.set(norm, new FieldIndex(false)); + + // this.fields.get(norm)!.add(object.$id, field.value); + // } + // } + } + + /** Remove the given indexable from all indices. */ + private _unindex(object: Indexable) { + this.types.delete(object.$id, object.$types); + + // if (object.$types.contains(TAGGABLE_TYPE) && iterableExists(object, "$tags")) { + // const tags = object.$tags as Set; + + // this.etags.delete(object.$id, tags); + // this.tags.delete(object.$id, extractSubtags(tags)); + // } + + if (object.$types.contains(LINKBEARING_TYPE) && iterableExists(object, "$links")) { + // Assume links are normalized when deleting them. Could be broken but I hope not. We can always use a 2-way index to + // fix this if we encounter non-normalized links. + this.links.delete( + object.$id, + (object.$links as Link[]).map((link) => link.obsidianLink()) + ); + } + + // if (object.$types.contains(FIELDBEARING_TYPE) && "fields" in object) { + // for (const field of object.fields as Iterable) { + // // Skip any index fields. + // if (INDEX_FIELDS.has(field.key)) continue; + + // const norm = field.key.toLowerCase(); + // if (!this.fields.has(norm)) continue; + + // this.fields.get(norm)!.delete(object.$id, field.value); + // } + // } + } + + /** Completely clear the datastore of all values. */ + public clear() { + this.ids.clear(); + this.objects.clear(); + this.children.clear(); + + this.types.clear(); + // this.tags.clear(); + // this.etags.clear(); + this.links.clear(); + // this.fields.clear(); + + this.revision++; + } + + /** Get all the backlinks to the given linkable. */ + public getBacklinks(object: Linkable) { + const normalizedLink = object.$link.obsidianLink(); + return this.links.get(normalizedLink); + } + + /** Check if the given linkable object has any backlinks. */ + public isLinked(object: Linkable): boolean { + return this.getBacklinks(object).size > 0; + } + + /** + * Update $printName and $refName of theorems and equations. + * Additionally, set $main of a theorem callout to true if it is the only one in the file, if configured as such. + * Fiinally, set $refName of the page to the refName of the main theorem, if it exists. + * + * Warning: This function doesn't trigger MathLinks.update(), so you have to call it by yourself! + */ + public updateNames(file: TFile) { + const settings = resolveSettings(undefined, this.plugin, file); + + let blockOrdinal = 1; + let block: Indexable | undefined; + + let theorems: TheoremCalloutBlock[] = [] + let autoNumberedTheoremCount = 0; + let mainTheorem: TheoremCalloutBlock | null = null; + + const equationNumberInit = +(settings.eqNumberInit); + let equationCount = 0; + const eqPrefix = getEqNumberPrefix(this.plugin.app, file, settings); + const eqSuffix = settings.eqNumberSuffix; + + while (block = this.load(`${file.path}/block${blockOrdinal++}`)) { + if (TheoremCalloutBlock.isTheoremCalloutBlock(block)) { + theorems.push(block); + if (block.$main) mainTheorem = block; + // Theorem numbers start at 1, and are incremented by 1 + // for each theorem callout. + // They may be additionally formatted according to the settings. + const resolvedSettings = Object.assign({}, settings, block.$settings); + if (block.$settings.number == 'auto') { + block.$index = autoNumberedTheoremCount; + (resolvedSettings as ResolvedMathSettings)._index = autoNumberedTheoremCount++; + } + // const printName = formatTitle(this.plugin, file, resolvedSettings); + const mainTitle = formatTitleWithoutSubtitle(this.plugin, file, resolvedSettings); + const refName = this.formatMathLink(file, resolvedSettings, "refFormat"); + block.$theoremMainTitle = mainTitle; + block.$refName = refName; + block.$titleSuffix = settings.titleSuffix; + } else if (EquationBlock.isEquationBlock(block)) { + // Equation numbers start at settings.eqNumberInit, and are incremented by 1 + // for eqch equation block that doesn't have a manual tag (i.e. \tag{...}) but + // has any backlinks. + // If an equation block has a manual tag, it is used as printNames & refNames. + let printName: string | null = null; + let refName: string | null = null; + if (block.$manualTag) { + printName = `(${block.$manualTag})`; + } else if (!settings.numberOnlyReferencedEquations || block.$link && this.isLinked(block as Linkable)) { + block.$index = equationCount; + printName = "(" + eqPrefix + CONVERTER[settings.eqNumberStyle](equationNumberInit + equationCount) + eqSuffix + ")"; + equationCount++; + } + if (printName !== null) refName = settings.eqRefPrefix + printName + settings.eqRefSuffix; + block.$printName = printName; + block.$refName = refName; + } + } + + if (this.plugin.extraSettings.setOnlyTheoremAsMain && theorems.length == 1) { + theorems[0].$main = true; + mainTheorem = theorems[0]; + } + + const page = this.load(file.path); + if (MarkdownPage.isMarkdownPage(page)) { + if (mainTheorem) { + const resolvedSettings = Object.assign({}, settings, mainTheorem.$settings); + (resolvedSettings as ResolvedMathSettings)._index = mainTheorem.$index; + + if (!resolvedSettings.ignoreMainTheoremCalloutWithoutTitle || mainTheorem.$theoremSubtitle) + page.$refName = this.formatMathLink(file, resolvedSettings, "noteMathLinkFormat"); + } + } + + this.plugin.indexManager.trigger("index-updated", file); + } + + formatMathLink(file: TFile, resolvedSettings: ResolvedMathSettings, key: "refFormat" | "noteMathLinkFormat"): string { + const refFormat: TheoremRefFormat = resolvedSettings[key]; + if (refFormat == "[type] [number] ([title])") { + return formatTitle(this.plugin, file, resolvedSettings, true); + } + if (refFormat == "[type] [number]") { + return formatTitleWithoutSubtitle(this.plugin, file, resolvedSettings); + } + if (refFormat == "[title] if title exists, [type] [number] otherwise") { + return resolvedSettings.title ? resolvedSettings.title : formatTitleWithoutSubtitle(this.plugin, file, resolvedSettings); + } + // if (refFormat == "[title] ([type] [number]) if title exists, [type] [number] otherwise") + const typePlusNumber = formatTitleWithoutSubtitle(this.plugin, file, resolvedSettings); + return resolvedSettings.title ? `${resolvedSettings.title} (${typePlusNumber})` : typePlusNumber; + } + + getByType(type: string) { + return this.types.get(type); + } + + getMarkdownPage(path: string): MarkdownPage | null { + const page = this.load(path); + return MarkdownPage.isMarkdownPage(page) ? page : null; + } + + getTheoremCalloutBlock(id: string): TheoremCalloutBlock | null { + const block = this.load(id); + return TheoremCalloutBlock.isTheoremCalloutBlock(block) ? block : null; + } + + getEquationBlock(id: string): EquationBlock | null { + const block = this.load(id); + return EquationBlock.isEquationBlock(block) ? block : null; + } +} + +/** A general function for storing sub-objects in a given object. */ +export type Substorer = ( + object: T, + add: (object: U | U[], subindex?: Substorer) => void +) => void; + +/** Type guard which checks if object[key] exists and is an iterable. */ +function iterableExists, K extends string>( + object: T, + key: K +): object is T & Record> { + return key in object && object[key] !== undefined && Symbol.iterator in object[key]; +} diff --git a/src/index/storage/inverted.ts b/src/index/storage/inverted.ts new file mode 100644 index 0000000..19584c0 --- /dev/null +++ b/src/index/storage/inverted.ts @@ -0,0 +1,41 @@ +/** Tracks an inverted index of value -> set. */ +export class InvertedIndex { + private inverted: Map>; + + public constructor() { + this.inverted = new Map(); + } + + /** Set the key to the given values. */ + public set(key: string, values: Iterable) { + for (let value of values) { + if (!this.inverted.has(value)) this.inverted.set(value, new Set()); + this.inverted.get(value)!.add(key); + } + } + + /** Get all keys that map to the given value. */ + public get(value: V): Set { + return this.inverted.get(value) ?? InvertedIndex.EMPTY_SET; + } + + /** Delete a key from the set of associated values. */ + public delete(key: string, values: Iterable) { + for (let value of values) { + const set = this.inverted.get(value); + if (set) { + set.delete(key); + } + + if (set && set.size == 0) { + this.inverted.delete(value); + } + } + } + + public clear() { + this.inverted.clear(); + } + + private static EMPTY_SET: Set = new Set(); +} diff --git a/src/index/typings/indexable.ts b/src/index/typings/indexable.ts new file mode 100644 index 0000000..0bd964d --- /dev/null +++ b/src/index/typings/indexable.ts @@ -0,0 +1,64 @@ +import { Link } from "../expression/link"; +// import { DateTime } from "luxon"; + +/** The names of all index fields that are present on ALL indexed types. */ +export const INDEX_FIELDS = new Set([ + "$types", + // "$typename", + "$id", + "$revision" +]); + +/** Any indexable field, which must have a few index-relevant properties. */ +export interface Indexable { + /** The object types that this indexable is. */ + $types: string[]; + // /** Textual description of the object, such as `Page` or `Section`. Used in visualizations. */ + // $typename: string; + /** The unique index ID for this object. */ + $id: string; + /** + * The indexable object that is the parent of this object. Only set after the object is actually indexed. + */ + $parent?: Indexable; + /** If present, the revision in the index of this object. */ + $revision?: number; + /** The file that this indexable was derived from, if file-backed. */ + $file?: string; +} + +/** Metadata for objects which support linking. */ +export const LINKABLE_TYPE = "linkable"; +export interface Linkable { + /** A link to this linkable object. */ + $link: Link; +} + +/** General metadata for any file. */ +export const FILE_TYPE = "file"; +export interface File extends Linkable { + /** The path this file exists at. */ + $path: string; + // /** Obsidian-provided date this page was created. */ + // $ctime: DateTime; + // /** Obsidian-provided date this page was modified. */ + // $mtime: DateTime; + // /** Obsidian-provided size of this page in bytes. */ + // $size: number; + /** The extension of the file. */ + $extension: string; +} + +// /** Metadata for taggable objects. */ +// export const TAGGABLE_TYPE = "taggable"; +// export interface Taggable { +// /** The exact tags on this object. (#a/b/c or #foo/bar). */ +// $tags: string[]; +// } + +/** Metadata for objects which can link to other things. */ +export const LINKBEARING_TYPE = "links"; +export interface Linkbearing { + /** The links in this file. */ + $links: Link[]; +} diff --git a/src/index/typings/json.ts b/src/index/typings/json.ts new file mode 100644 index 0000000..0545ee8 --- /dev/null +++ b/src/index/typings/json.ts @@ -0,0 +1,82 @@ +//! Note: These are "serialization" types for metadata, which contain +// the absolute minimum information needed to save and load data. + +import { Link } from "index/expression/literal"; +import { Pos } from "obsidian"; +import { MinimalTheoremCalloutSettings, TheoremCalloutSettings } from "settings/settings"; + +/** A span of contiguous lines. */ +export interface LineSpan { + /** The inclusive start line. */ + start: number; + /** The inclusive end line. */ + end: number; +} + +/** Stores just the minimal information needed to create a markdown file; used for saving and loading these files. */ +export interface JsonMarkdownPage { + /** The path this file exists at. */ + $path: string; + /** The extension; for markdown files, almost always '.md'. */ + $extension: string; + /** The full extent of the file (start 0, end the number of lines in the file.) */ + $position: LineSpan; + /** All links in the file. */ + $links: Link[]; + /** + * All child markdown sections of this markdown file. The initial section before any content is special and is + * named with the title of the file. + */ + $sections: JsonMarkdownSection[]; +} + +export interface JsonMarkdownSection { + /** The index of this section in the file. */ + $ordinal: number; + /** The title of the section; the root (implicit) section will have the title of the page. */ + $title: string; + /** The indentation level of the section (1 - 6). */ + $level: number; + /** The span of lines indicating the position of the section. */ + $position: LineSpan; + // /** All tags on the file. */ + // $tags: string[]; + /** All links in the file. */ + /** -> All links in the SECTION? */ + $links: Link[]; + /** All of the markdown blocks in this section. */ + $blocks: JsonMarkdownBlock[]; +} + +export interface JsonMarkdownBlock { + /** The index of this block in the file. */ + $ordinal: number; + /** The position/extent of the block. */ + $position: LineSpan; + $pos: Pos; + /** All links in the file. */ + $links: Link[]; + /** If present, the distinct block ID for this block. */ + $blockId?: string; + /** The type of block - paragraph, list, and so on. */ + $type: string; +} + +export interface JsonMathBlock extends JsonMarkdownBlock { + $type: "theorem" | "equation"; + $label?: string; + $display?: string; +} + +export interface JsonTheoremCalloutBlock extends JsonMathBlock { + $type: "theorem"; + $settings: MinimalTheoremCalloutSettings; + $main: boolean; + $v1: boolean; +} + +export interface JsonEquationBlock extends JsonMathBlock { + $type: "equation"; + $manualTag: string | null; + $mathText: string; +} diff --git a/src/index/typings/markdown.ts b/src/index/typings/markdown.ts new file mode 100644 index 0000000..b7defae --- /dev/null +++ b/src/index/typings/markdown.ts @@ -0,0 +1,445 @@ +import { Link } from "index/expression/literal"; +import { getFileTitle } from "index/utils/normalizers"; +import { + FILE_TYPE, + File, + Indexable, + LINKABLE_TYPE, + LINKBEARING_TYPE, + Linkable, + Linkbearing, +} from "index/typings/indexable"; +import { + LineSpan, + JsonMarkdownPage, + JsonMarkdownSection, + JsonMarkdownBlock, + JsonTheoremCalloutBlock, + JsonEquationBlock, +} from "./json"; +import { MinimalTheoremCalloutSettings } from "settings/settings"; +import { Pos } from "obsidian"; + +/** A link normalizer which takes in a raw link and produces a normalized link. */ +export type LinkNormalizer = (link: Link) => Link; +export const NOOP_NORMALIZER: LinkNormalizer = (x) => x; + +/** A markdown file in the vault; the source of most metadata. */ +export class MarkdownPage implements File, Linkbearing, Indexable { + /** All of the types that a markdown file is. */ + static TYPES = [FILE_TYPE, "markdown", "page", LINKABLE_TYPE, LINKBEARING_TYPE]; + + // Use static types for all markdown files. + $types: string[] = MarkdownPage.TYPES; + $typename: string = "Page"; + + // Markdown file IDs are always just the full path. + get $id() { + return this.$path; + } + // The file of a file is... it's file. + get $file() { + return this.$path; + } + + /** The path this file exists at. */ + $path: string; + /** The extension; for markdown files, almost always '.md'. */ + $extension: string; + /** The full extent of the file (start 0, end the number of lines in the file.) */ + $position: LineSpan; + /** All links in the file. */ + $links: Link[]; + /** + * All child markdown sections of this markdown file. The initial section before any content is special and is + * named with the title of the file. + */ + $sections: MarkdownSection[] = []; + + /** + * Maps a block ID to the corresponding markdown block in this page. + */ + $blocks: Map; + + /** $refName of the main theorem callout, if any. */ + $refName?: string + + /** Create a markdown file from the given raw values. */ + static from(raw: JsonMarkdownPage, normalizer: LinkNormalizer = NOOP_NORMALIZER): MarkdownPage { + const sections = raw.$sections.map((sect) => MarkdownSection.from(sect, raw.$path, normalizer)); + const blocks = new Map(); + for (const section of sections) { + for (const block of section.$blocks) { + if (block.$blockId) blocks.set(block.$blockId, block); + } + } + + return new MarkdownPage({ + $path: raw.$path, + $extension: raw.$extension, + $position: raw.$position, + $links: raw.$links.map(normalizer), + $sections: sections, + $blocks: blocks, + }); + } + + private constructor(init: Partial) { + Object.assign(this, init); + } + + /** Return the number of lines in the document. */ + get $lineCount() { + return this.$position.end; + } + + /** The name of the file. */ + get $name() { + return getFileTitle(this.$path); + } + + /** A link to this file. */ + get $link() { + return Link.file(this.$path); + } + + /** Convert this page into it's partial representation for saving. */ + public partial(): JsonMarkdownPage { + return { + $path: this.$path, + $extension: this.$extension, + $position: this.$position, + $links: this.$links, + $sections: this.$sections.map((sect) => sect.partial()), + }; + } + + public getBlockByLineNumber(line: number) { + const section = this.$sections.find((section) => section.$position.start <= line && line <= section.$position.end); + const block = section?.$blocks.find((block) => block.$position.start <= line && line <= block.$position.end); + return block; + } + + public getBlockByOffset(offset: number) { + for (const section of this.$sections) { + for (const block of section.$blocks) { + if (block.$pos.start.offset <= offset && offset <= block.$pos.end.offset) return block; + } + } + } + + static isMarkdownPage(object: Indexable | undefined): object is MarkdownPage { + return object !== undefined && '$typename' in object && (object as any).$typename === 'Page'; + } +} + +export class MarkdownSection implements Indexable, Linkable, Linkbearing { + /** All of the types that a markdown section is. */ + static TYPES = ["markdown", "section", LINKABLE_TYPE, LINKBEARING_TYPE]; + + /** Path of the file that this section is in. */ + $types: string[] = MarkdownSection.TYPES; + $typename: string = "Section"; + $id: string; + $file: string; + + /** The index of this section in the file. */ + $ordinal: number; + /** The title of the section; the root (implicit) section will have the title of the page. */ + $title: string; + /** The indentation level of the section (1 - 6). */ + $level: number; + /** The span of lines indicating the position of the section. */ + $position: LineSpan; + /** All tags on the file. */ + $tags: string[]; + /** All links in the file. */ + $links: Link[]; + /** All of the markdown blocks in this section. */ + $blocks: MarkdownBlock[]; + + /** Convert raw markdown section data to the appropriate class. */ + static from(raw: JsonMarkdownSection, file: string, normalizer: LinkNormalizer = NOOP_NORMALIZER): MarkdownSection { + const blocks = raw.$blocks.map((block) => MarkdownBlock.from(block, file, normalizer)); + return new MarkdownSection({ + $file: file, + $id: MarkdownSection.readableId(file, raw.$title, raw.$ordinal), + $ordinal: raw.$ordinal, + $title: raw.$title, + $level: raw.$level, + $position: raw.$position, + $links: raw.$links.map(normalizer), + $blocks: blocks, + }); + } + + private constructor(init: Partial) { + Object.assign(this, init); + } + + /** Obtain the number of lines in the section. */ + get $lineCount(): number { + return this.$position.end - this.$position.start; + } + + /** Alias for title which allows searching over pages and sections by 'name'. */ + get $name(): string { + return this.$title; + } + + /** Return a link to this section. */ + get $link(): Link { + return Link.header(this.$file, this.$title); + } + + public partial(): JsonMarkdownSection { + return { + $ordinal: this.$ordinal, + $title: this.$title, + $level: this.$level, + $position: this.$position, + $links: this.$links, + $blocks: this.$blocks.map((block) => block.partial()), + }; + } + + /** Generate a readable ID for this section using the first 8 characters of the string and the ordinal. */ + static readableId(file: string, title: string, ordinal: number): string { + const first8 = title.substring(0, Math.min(title.length, 8)).replace(/[^A-Za-z0-9-_]+/gi, "-"); + + return `${file}/section${ordinal}/${first8}`; + } + + static isMarkdownSection(object: Indexable | undefined): object is MarkdownSection { + return object !== undefined && '$typename' in object && (object as any).$typename === 'Section'; + } +} + +/** Base class for all markdown blocks. */ +export class MarkdownBlock implements Indexable, Linkbearing { + static TYPES = ["markdown", "block", LINKBEARING_TYPE]; + + $types: string[] = MarkdownBlock.TYPES; + $typename: string = "Block"; + $id: string; + $file: string; + + /** The index of this block in the file. */ + $ordinal: number; + /** The position/extent of the block. */ + $position: LineSpan; + $pos: Pos; + /** All links in the file. */ + $links: Link[]; + /** If present, the distinct block ID for this block. */ + $blockId?: string; + /** The type of block - paragraph, list, and so on. */ + $type: string; + + static from(object: JsonMarkdownBlock, file: string, normalizer: LinkNormalizer = NOOP_NORMALIZER): MarkdownBlock { + if (object.$type === "theorem") { + return TheoremCalloutBlock.from(object as JsonTheoremCalloutBlock, file, normalizer); + } else if (object.$type === "equation") { + return EquationBlock.from(object as JsonEquationBlock, file, normalizer); + } + + return new MarkdownBlock({ + $file: file, + $id: MarkdownBlock.readableId(file, object.$ordinal), + $ordinal: object.$ordinal, + $position: object.$position, + $pos: object.$pos, + $links: object.$links.map(normalizer), + $blockId: object.$blockId, + $type: object.$type, + }); + } + + protected constructor(init: Partial) { + Object.assign(this, init); + } + + /** If this block has a block ID, the link to this block. */ + get $link(): Link | undefined { + if (this.$blockId) return Link.block(this.$file, this.$blockId); + else return undefined; + } + public partial(): JsonMarkdownBlock { + return { + $ordinal: this.$ordinal, + $position: this.$position, + $pos: this.$pos, + $links: this.$links, + $blockId: this.$blockId, + $type: this.$type, + }; + } + + /** Generate a readable ID for this block using the ordinal of the block. */ + static readableId(file: string, ordinal: number): string { + return `${file}/block${ordinal}`; + } + + static isMarkdownBlock(object: Indexable | undefined): object is MarkdownSection { + return object !== undefined && object.$types.includes('block'); + } +} + + + +export abstract class MathBlock extends MarkdownBlock { + // only set after backlinks are ready + abstract $printName: string | null; + // declaring as abstract to treat like an interface + $refName: string | null; + + /** Additional metadata specified via comments */ + $label?: string; + $display?: string; + // set if this block is auto-numbered. This is the index of this block among all auto-numbered blocks in the file + $index?: number; + + static isMathBlock(object: Indexable | undefined): object is MathBlock { + return object !== undefined && object.$types.includes('block-math-booster'); + } +} + +export class TheoremCalloutBlock extends MathBlock implements Linkbearing { + static TYPES = ["markdown", "block", "block-math-booster", "block-theorem", LINKBEARING_TYPE]; + + $types: string[] = TheoremCalloutBlock.TYPES; + $typename: string = "Theorem Callout Block"; + $type: string = "theorem"; + + /** True if written in the version-1 format of '> [!math|{"type":"theorem",...}]"' */ + $v1: boolean; + + /** The settings for this theorem callout. */ + $settings: MinimalTheoremCalloutSettings; + + $titleSuffix: string; + + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "Theorem 1.1" */ + $theoremMainTitle: string; + + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "theorem" */ + get $theoremType(): string { + return this.$settings.type; + } + + get $numberSpec(): string { + return this.$settings.number; + } + + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "Cauchy-Schwarz" */ + get $theoremSubtitle(): string | undefined { + return this.$settings.title; + } + + /** e.g. "Theorem 1.1 (Cauchy-Schwarz)" */ + get $printName(): string { + return this.$theoremSubtitle ? `${this.$theoremMainTitle} (${this.$theoremSubtitle})` : this.$theoremMainTitle; + } + + /** Additional metadata specified via comments */ + $main: boolean; + + static from( + object: JsonTheoremCalloutBlock, + file: string, + normalizer: LinkNormalizer = NOOP_NORMALIZER + ): TheoremCalloutBlock { + /** + * `printName` and `refName` can be computed only after the backlinks get ready + * because only linked equations are numbered. We have to wait until then. + */ + return new TheoremCalloutBlock({ + $file: file, + $id: MarkdownBlock.readableId(file, object.$ordinal), + $ordinal: object.$ordinal, + $position: object.$position, + $pos: object.$pos, + $links: object.$links.map(normalizer), + $blockId: object.$blockId, + $type: object.$type, + $settings: object.$settings, + $label: object.$label, + $display: object.$display, + $main: object.$main, + $v1: object.$v1, + }); + } + + public partial(): JsonMarkdownBlock { + return Object.assign(super.partial(), { + $settings: this.$settings, + $label: this.$label, + $display: this.$display, + $main: this.$main, + $v1: this.$v1, + }); + } + + public constructor(init: Partial) { + super(init); + } + + static isTheoremCalloutBlock(object: Indexable | undefined): object is TheoremCalloutBlock { + return object !== undefined && object.$types.includes('block-theorem'); + } +} + +export class EquationBlock extends MathBlock { + static TYPES = ["markdown", "block", "block-math-booster", "block-equation"]; + + $types: string[] = EquationBlock.TYPES; + $typename: string = "Equation Block"; + $type: string = "equation"; + + $printName: string | null = null; + /** The math text of this equation. */ + $mathText: string; + $manualTag: string | null; + + static from( + object: JsonEquationBlock, + file: string, + normalizer: LinkNormalizer = NOOP_NORMALIZER + ): EquationBlock { + /** + * `printName` and `refName` can be computed only after the backlinks get ready + * because only linked equations are numbered. We have to wait until then. + */ + return new EquationBlock({ + $file: file, + $id: MarkdownBlock.readableId(file, object.$ordinal), + $ordinal: object.$ordinal, + $position: object.$position, + $pos: object.$pos, + $links: object.$links.map(normalizer), + $blockId: object.$blockId, + $type: object.$type, + $mathText: object.$mathText, + $manualTag: object.$manualTag, + $label: object.$label, + $display: object.$display, + }); + } + + public partial(): JsonMarkdownBlock { + return Object.assign(super.partial(), { + $mathText: this.$mathText, + $manualTag: this.$manualTag, + $label: this.$label, + $display: this.$display, + }); + } + + public constructor(init: Partial) { + super(init); + } + + static isEquationBlock(object: Indexable | undefined): object is EquationBlock { + return object !== undefined && object.$types.includes('block-equation'); + } +} diff --git a/src/index/utils/deferred.ts b/src/index/utils/deferred.ts new file mode 100644 index 0000000..61c0653 --- /dev/null +++ b/src/index/utils/deferred.ts @@ -0,0 +1,22 @@ +/** A promise that can be resolved directly. */ +export type Deferred = Promise & { + resolve: (value: T) => void; + reject: (error: any) => void; +}; + +/** Create a new deferred object, which is a resolvable promise. */ +export function deferred(): Deferred { + let resolve: (value: T) => void; + let reject: (error: any) => void; + + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + const deferred = promise as any as Deferred; + deferred.resolve = resolve!; + deferred.reject = reject!; + + return deferred; +} diff --git a/src/index/utils/normalizers.ts b/src/index/utils/normalizers.ts new file mode 100644 index 0000000..efe519c --- /dev/null +++ b/src/index/utils/normalizers.ts @@ -0,0 +1,6 @@ +/** Get the "title" for a file, by stripping other parts of the path as well as the extension. */ +export function getFileTitle(path: string): string { + if (path.includes("/")) path = path.substring(path.lastIndexOf("/") + 1); + if (path.endsWith(".md")) path = path.substring(0, path.length - 3); + return path; +} diff --git a/src/index/web-worker/importer.ts b/src/index/web-worker/importer.ts new file mode 100644 index 0000000..49ac717 --- /dev/null +++ b/src/index/web-worker/importer.ts @@ -0,0 +1,199 @@ +/** Controls and creates Dataview file importers, allowing for asynchronous loading and parsing of files. */ + +import { Component, MetadataCache, TFile, Vault } from "obsidian"; +import LatexReferencer from 'main'; +import { Transferable } from "./transferable"; +import ImportWorker from "index/web-worker/importer.worker"; +import { ImportCommand } from "./message"; + +/** Settings for throttling import. */ +export interface ImportThrottle { + /** The number of workers to use for imports. */ + workers: number; + /** A number between 0.1 and 1 which indicates total cpu utilization target; 0.1 means spend 10% of time */ + utilization: number; +} + +/** Default throttle configuration. */ +export const DEFAULT_THROTTLE: ImportThrottle = { + workers: 2, + utilization: 0.75, +}; + +/** Multi-threaded file parser which debounces rapid file requests automatically. */ +export class MathImporter extends Component { + /* Background workers which do the actual file parsing. */ + workers: Map; + /** The next worker ID to hand out. */ + nextWorkerId: number; + /** Is the importer active? */ + shutdown: boolean; + + /** List of files which have been queued for a reload. */ + queue: [TFile, (success: any) => void, (failure: any) => void][]; + /** Outstanding loads indexed by path. */ + outstanding: Map>; + /** Throttle settings. */ + throttle: () => ImportThrottle; + + public constructor(public plugin: LatexReferencer, public vault: Vault, public metadataCache: MetadataCache, throttle?: () => ImportThrottle) { + super(); + this.workers = new Map(); + this.shutdown = false; + this.nextWorkerId = 0; + this.throttle = throttle ?? (() => DEFAULT_THROTTLE); + + this.queue = []; + this.outstanding = new Map(); + } + + /** + * Queue the given file for importing. Multiple import requests for the same file in a short time period will be de-bounced + * and all be resolved by a single actual file reload. + */ + public import(file: TFile): Promise { + // De-bounce repeated requests for the same file. + let existing = this.outstanding.get(file.path); + if (existing) return existing; + + let promise: Promise = new Promise((resolve, reject) => { + this.queue.push([file, resolve, reject]); + }); + + this.outstanding.set(file.path, promise); + this.schedule(); + return promise; + } + + /** Reset any active throttles on the importer (such as if the utilization changes). */ + public unthrottle() { + for (let worker of this.workers.values()) { + worker.availableAt = Date.now(); + } + } + + /** Poll from the queue and execute if there is an available worker. */ + private schedule() { + if (this.queue.length == 0 || this.shutdown) return; + + const worker = this.availableWorker(); + if (!worker) return; + + const [file, resolve, reject] = this.queue.shift()!; + + worker.active = [file, resolve, reject, Date.now()]; + this.vault.cachedRead(file).then((c) => + worker!.worker.postMessage( + Transferable.transferable({ + type: "markdown", + path: file.path, + contents: c, + metadata: this.metadataCache.getFileCache(file), + excludeExampleCallout: this.plugin.extraSettings.excludeExampleCallout, + } as ImportCommand) + ) + ); + } + + /** Finish the parsing of a file, potentially queueing a new file. */ + private finish(worker: PoolWorker, data: any) { + let [file, resolve, reject] = worker.active!; + + // Resolve promises to let users know this file has finished. + if ("$error" in data) reject(data["$error"]); + else resolve(data); + + // Remove file from outstanding. + this.outstanding.delete(file.path); + + // Remove this worker if we are over capacity. + // Otherwise, notify the queue this file is available for new work. + if (this.workers.size > this.throttle().workers) { + this.workers.delete(worker.id); + terminate(worker); + } else { + const now = Date.now(); + const start = worker.active![3]; + const throttle = Math.max(0.1, this.throttle().utilization) - 1.0; + const delay = (now - start) * throttle; + + worker.active = undefined; + + if (delay <= 1e-10) { + worker.availableAt = now; + this.schedule(); + } else { + worker.availableAt = now + delay; + + // Note: I'm pretty sure this will garauntee that this executes AFTER delay milliseconds, + // so this should be fine; if it's not, we'll have to swap to an external timeout loop + // which infinitely reschedules itself to the next available execution time. + setTimeout(this.schedule.bind(this), delay); + } + } + } + + /** Obtain an available worker, returning undefined if one does not exist. */ + private availableWorker(): PoolWorker | undefined { + const now = Date.now(); + for (let worker of this.workers.values()) { + if (!worker.active && worker.availableAt <= now) { + return worker; + } + } + + // Make a new worker if we can. + if (this.workers.size < this.throttle().workers) { + let worker = this.newWorker(); + this.workers.set(worker.id, worker); + return worker; + } + + return undefined; + } + + /** Create a new worker bound to this importer. */ + private newWorker(): PoolWorker { + let worker: PoolWorker = { + id: this.nextWorkerId++, + availableAt: Date.now(), + worker: new ImportWorker(), + }; + + worker.worker.onmessage = (evt) => this.finish(worker, Transferable.value(evt.data)); + return worker; + } + + /** Reject all outstanding promises and close all workers on close. */ + public onunload(): void { + for (let worker of this.workers.values()) { + terminate(worker); + } + + for (let [_file, _success, reject] of this.queue) { + reject("Terminated"); + } + + this.shutdown = true; + } +} + +/** A worker in the pool of executing workers. */ +interface PoolWorker { + /** The id of this worker. */ + id: number; + /** The raw underlying worker. */ + worker: Worker; + /** UNIX time indicating the next time this worker is available for execution according to target utilization. */ + availableAt: number; + /** The active promise this worker is working on, if any. */ + active?: [TFile, (success: any) => void, (failure: any) => void, number]; +} + +/** Terminate a pool worker. */ +function terminate(worker: PoolWorker) { + worker.worker.terminate(); + + if (worker.active) worker.active[2]("Terminated"); + worker.active = undefined; +} diff --git a/src/index/web-worker/importer.worker.ts b/src/index/web-worker/importer.worker.ts new file mode 100644 index 0000000..a6d8651 --- /dev/null +++ b/src/index/web-worker/importer.worker.ts @@ -0,0 +1,25 @@ +import { markdownImport } from "index/import/markdown"; +import { ImportCommand, MarkdownImportResult } from "index/web-worker/message"; +import { Transferable } from "index/web-worker/transferable"; + +/** Web worker entry point for importing. */ +onmessage = (event) => { + try { + const message = Transferable.value(event.data) as ImportCommand; + + if (message.type === "markdown") { + const markdown = markdownImport(message.path, message.contents, message.metadata, message.excludeExampleCallout); + + postMessage( + Transferable.transferable({ + type: "markdown", + result: markdown, + } as MarkdownImportResult) + ); + } else { + postMessage({ $error: "Unsupported import method." }); + } + } catch (error) { + postMessage({ $error: error.message }); + } +}; diff --git a/src/index/web-worker/message.ts b/src/index/web-worker/message.ts new file mode 100644 index 0000000..915c121 --- /dev/null +++ b/src/index/web-worker/message.ts @@ -0,0 +1,39 @@ +import { JsonMarkdownPage } from "index/typings/json"; +import { CachedMetadata, FileStats } from "obsidian"; + +/** A command to import a markdown file. */ +export interface MarkdownImport { + type: "markdown"; + + /** The path we are importing. */ + path: string; + /** The file contents to import. */ + contents: string; + // /** The stats for the file. */ + // stat: FileStats; + /** Metadata for the file. */ + metadata: CachedMetadata; + excludeExampleCallout: boolean; +} + + +/** Available import commands to be sent to an import web worker. */ +export type ImportCommand = MarkdownImport; //| CanvasImport; + +/** The result of importing a file of some variety. */ +export interface MarkdownImportResult { + /** The type of import. */ + type: "markdown"; + /** The result of importing. */ + result: JsonMarkdownPage; +} + +export interface ImportFailure { + /** Failed to import. */ + type: "error"; + + /** The error that the worker indicated on failure. */ + $error: string; +} + +export type ImportResult = MarkdownImportResult | ImportFailure; diff --git a/src/index/web-worker/transferable.ts b/src/index/web-worker/transferable.ts new file mode 100644 index 0000000..fc5d681 --- /dev/null +++ b/src/index/web-worker/transferable.ts @@ -0,0 +1,98 @@ +import { Link, Literals } from "index/expression/literal"; +// import { DateTime, Duration, SystemZone } from "luxon"; + +/** Simplifies passing complex values across the JS web worker barrier. */ +export namespace Transferable { + /** Convert a literal value to a serializer-friendly transferable value. */ + export function transferable(value: any): any { + // Handle simple universal types first. + if (value instanceof Map) { + let copied = new Map(); + for (let [key, val] of value.entries()) copied.set(transferable(key), transferable(val)); + return copied; + } else if (value instanceof Set) { + let copied = new Set(); + for (let val of value) copied.add(transferable(val)); + return copied; + } + + let wrapped = Literals.wrapValue(value); + if (wrapped === undefined) throw Error("Unrecognized transferable value: " + value); + + switch (wrapped.type) { + case "null": + case "number": + case "string": + case "boolean": + return wrapped.value; + // case "date": + // return { + // "$transfer-type": "date", + // value: transferable(wrapped.value.toObject()), + // options: { + // zone: wrapped.value.zone.equals(SystemZone.instance) ? undefined : wrapped.value.zoneName, + // }, + // }; + // case "duration": + // return { + // "$transfer-type": "duration", + // value: transferable(wrapped.value.toObject()), + // }; + case "array": + return wrapped.value.map((v) => transferable(v)); + case "link": + return { + "$transfer-type": "link", + value: transferable(wrapped.value.toObject()), + }; + case "object": + let result: Record = {}; + + // Only copy owned properties, and not derived/readonly properties like getters/computed fields. + for (let key of Object.getOwnPropertyNames(wrapped.value)) + result[key] = transferable(wrapped.value[key]); + return result; + } + } + + /** Convert a transferable value back to a literal value we can work with. */ + export function value(transferable: any): any { + if (transferable === null) { + return null; + } else if (transferable === undefined) { + return undefined; + } else if (transferable instanceof Map) { + let real = new Map(); + for (let [key, val] of transferable.entries()) real.set(value(key), value(val)); + return real; + } else if (transferable instanceof Set) { + let real = new Set(); + for (let val of transferable) real.add(value(val)); + return real; + } else if (Array.isArray(transferable)) { + return transferable.map((v) => value(v)); + } else if (typeof transferable === "object") { + if ("$transfer-type" in transferable) { + switch (transferable["$transfer-type"]) { + // case "date": + // let dateOpts = value(transferable.options); + // let dateData = value(transferable.value) as any; + + // return DateTime.fromObject(dateData, { zone: dateOpts.zone }); + // case "duration": + // return Duration.fromObject(value(transferable.value)); + case "link": + return Link.fromObject(value(transferable.value)); + default: + throw Error(`Unrecognized transfer type '${transferable["$transfer-type"]}'`); + } + } + + let result: Record = {}; + for (let [key, val] of Object.entries(transferable)) result[key] = value(val); + return result; + } + + return transferable; + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..53a2bd1 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,404 @@ +import { MarkdownView, Plugin } from 'obsidian'; +import { StateField, Extension, RangeSet } from '@codemirror/state'; + +import * as MathLinks from 'obsidian-mathlinks'; +import { registerQuickPreview } from 'obsidian-quick-preview'; + +import { MathContextSettings, DEFAULT_SETTINGS, ExtraSettings, DEFAULT_EXTRA_SETTINGS, UNION_TYPE_MATH_CONTEXT_SETTING_KEYS, UNION_TYPE_EXTRA_SETTING_KEYS } from 'settings/settings'; +import { MathSettingTab } from "settings/tab"; +import { CleverefProvider } from 'cleveref'; +import { createTheoremCalloutPostProcessor } from 'theorem-callouts/renderer'; +import { createTheoremCalloutNumberingViewPlugin } from 'theorem-callouts/view-plugin'; +import { ContextSettingModal, TheoremCalloutModal } from 'settings/modals'; +import { createEquationNumberProcessor } from 'equations/reading-view'; +import { createEquationNumberPlugin } from 'equations/live-preview'; +import { getMarkdownPreviewViewEl, getMarkdownSourceViewEl, isPluginOlderThan } from 'utils/obsidian'; +import { getProfile, staticifyEqNumber, insertDisplayMath, insertTheoremCallout, insertProof } from 'utils/plugin'; +import { MathIndexManager } from 'index/manager'; +import { DependencyNotificationModal, MigrationModal, PluginSplitNoticeModal, RenameNoticeModal } from 'notice'; +import { LinkAutocomplete } from 'search/editor-suggest'; +import { MathSearchModal } from 'search/modal'; +import { TheoremCalloutInfo, createTheoremCalloutsField } from 'theorem-callouts/state-field'; +import { patchLinkCompletion } from 'patches/link-completion'; +import { patchPagePreview } from 'patches/page-preview'; +import { createProofDecoration } from 'proof/live-preview'; +import { createProofProcessor } from 'proof/reading-view'; +import { MathBlock } from 'index/typings/markdown'; + + +export const VAULT_ROOT = '/'; + + +export default class LatexReferencer extends Plugin { + settings: Record>; + extraSettings: ExtraSettings; + excludedFiles: string[]; + dependencies: Record = { + "mathlinks": { id: "mathlinks", name: "MathLinks", version: "0.5.3" } + }; + indexManager: MathIndexManager; + editorExtensions: Extension[]; + theoremCalloutsField: StateField>; + // proofPositionField: StateField; + lastHoverLinktext: string | null; + + async onload() { + + /** Settings */ + + const data = await this.loadData(); + const first = data === null; + const { version } = data ?? {}; + + await this.loadSettings(); + await this.saveSettings(); + this.addSettingTab(new MathSettingTab(this.app, this)); + + /** Dependencies check */ + + this.app.workspace.onLayoutReady(async () => { + const dependenciesOK = Object.keys(this.dependencies).every((id) => this.checkDependency(id)); + const v1 = !first && ((version as string | undefined)?.startsWith("1.") ?? true); + + if (v1 || version.localeCompare('2.2.0', undefined, { numeric: true }) < 0) { + new RenameNoticeModal(this).open(); + } + + if (v1 || version.localeCompare('2.3.0', undefined, { numeric: true }) < 0) { + new PluginSplitNoticeModal(this).open(); + } + + if (!dependenciesOK || v1) { + new DependencyNotificationModal(this, dependenciesOK, v1).open(); + } + }); + + + /** Indexing */ + + this.addChild((this.indexManager = new MathIndexManager(this, this.extraSettings))); + this.app.workspace.onLayoutReady(async () => this.indexManager.initialize()); + // @ts-ignore + (window['mathIndex'] = this.indexManager.index) && this.register(() => delete window['mathIndex']) + + // wait until the layout is ready to ensure MathLinks has been loaded when calling addProvider() + this.app.workspace.onLayoutReady(() => { + this.addChild( + MathLinks.addProvider(this.app, (mathLinks) => new CleverefProvider(mathLinks, this)) + ); + }); + + + this.registerEvent( + this.indexManager.on("local-settings-updated", async (file) => { + // Add profile's tags as CSS classes + this.app.workspace.iterateRootLeaves((leaf) => { + if (leaf.view instanceof MarkdownView) { + this.setProfileTagAsCSSClass(leaf.view); + } + }); + }) + ); + + this.registerEvent( + this.indexManager.on("global-settings-updated", async () => { + // Add profile's tags as CSS classes + this.app.workspace.iterateRootLeaves((leaf) => { + if (leaf.view instanceof MarkdownView) { + this.setProfileTagAsCSSClass(leaf.view); + } + }); + }) + ); + + + /** Add profile's tags as CSS classes */ + + this.app.workspace.onLayoutReady(() => { + this.app.workspace.iterateRootLeaves((leaf) => { + if (leaf.view instanceof MarkdownView) { + this.setProfileTagAsCSSClass(leaf.view); + } + }); + }); + + this.registerEvent( + this.app.workspace.on("active-leaf-change", (leaf) => { + if (leaf?.view instanceof MarkdownView) { + this.setProfileTagAsCSSClass(leaf.view); + } + }) + ); + + + /** Commands */ + this.registerCommands(); + + + /** Editor Extensions */ + this.editorExtensions = [] + this.registerEditorExtension(this.editorExtensions); + this.updateEditorExtensions(); + + + /** Theorem/equation link autocompletion */ + this.updateLinkAutocomplete(); + this.app.workspace.onLayoutReady(() => patchLinkCompletion(this)); + const itemNormalizer = (item: MathBlock) => { + return { + linktext: item.$file, + sourcePath: '', + line: item.$position.start, + }; + }; + registerQuickPreview(this.app, this, LinkAutocomplete, itemNormalizer); + registerQuickPreview(this.app, this, MathSearchModal, itemNormalizer); + + /** Markdown post processors */ + + // theorem callouts + this.registerMarkdownPostProcessor(createTheoremCalloutPostProcessor(this)); + + // equation numbers + this.registerMarkdownPostProcessor(createEquationNumberProcessor(this)); + this.app.workspace.onLayoutReady(() => this.forceRerender()); + + // proof environments + this.registerMarkdownPostProcessor(createProofProcessor(this)); + + // patch hover page preview to display theorem numbers in it + this.lastHoverLinktext = null; + this.app.workspace.onLayoutReady(() => patchPagePreview(this)); + + /** File menu */ + + this.registerEvent( + this.app.workspace.on("file-menu", (menu, file) => { + menu.addSeparator() + .addItem((item) => { + item.setTitle(`${this.manifest.name}: Open local settings`) + .onClick(() => { + new ContextSettingModal(this.app, this, file).open(); + }); + }) + .addSeparator(); + }) + ); + } + + async loadSettings() { + this.settings = { [VAULT_ROOT]: JSON.parse(JSON.stringify(DEFAULT_SETTINGS)) }; + this.extraSettings = JSON.parse(JSON.stringify(DEFAULT_EXTRA_SETTINGS)); + this.excludedFiles = []; + // this.projectManager = new ProjectManager(this); + + const loadedData = await this.loadData(); + if (loadedData) { + const { settings, extraSettings, excludedFiles, + // dumpedProjects + } = loadedData; + for (const path in settings) { + if (path != VAULT_ROOT) { + this.settings[path] = {}; + } + for (const _key in DEFAULT_SETTINGS) { + const key = _key as keyof MathContextSettings; + let val = settings[path][key]; + if (val !== undefined) { + if (key in UNION_TYPE_MATH_CONTEXT_SETTING_KEYS) { + const allowableValues = UNION_TYPE_MATH_CONTEXT_SETTING_KEYS[key]; + if (!(allowableValues?.includes(val))) { + // invalid value encountered, substitute the default value instead + val = DEFAULT_SETTINGS[key]; + } + } + if (typeof val == typeof DEFAULT_SETTINGS[key]) { + // @ts-ignore + this.settings[path][key] = val; + } + } + } + } + + for (const _key in DEFAULT_EXTRA_SETTINGS) { + const key = _key as keyof ExtraSettings; + let val = extraSettings[key]; + if (val !== undefined) { + if (key in UNION_TYPE_EXTRA_SETTING_KEYS) { + const allowableValues = UNION_TYPE_EXTRA_SETTING_KEYS[key]; + if (!(allowableValues?.includes(val))) { + val = DEFAULT_EXTRA_SETTINGS[key]; + } + } + if (typeof val == typeof DEFAULT_EXTRA_SETTINGS[key]) { + (this.extraSettings[key] as ExtraSettings[keyof ExtraSettings]) = val; + } + } + } + + this.excludedFiles = excludedFiles; + + // At the time the plugin is loaded, the data vault is not ready and + // vault.getAbstractFile() returns null for any path. + // So we have to wait for the vault to start up and store a dumped version of the projects until then. + // this.projectManager = new ProjectManager(this, dumpedProjects); + } + } + + async saveSettings() { + await this.saveData({ + version: this.manifest.version, + settings: this.settings, + extraSettings: this.extraSettings, + excludedFiles: this.excludedFiles, + // dumpedProjects: this.projectManager.dump(), + }); + } + + updateLinkAutocomplete() { + // reset editor suggest(s) registered by this plugin + const suggestManager = (this.app.workspace as any).editorSuggest; + for (const suggest of suggestManager.suggests) { + if (suggest instanceof LinkAutocomplete) suggestManager.removeSuggest(suggest); + } + + this.registerEditorSuggest(new LinkAutocomplete(this)); + } + + /** + * Return true if the required plugin with the specified id is enabled and its version matches the requriement. + * @param id + * @returns + */ + checkDependency(id: string): boolean { + if (!this.app.plugins.enabledPlugins.has(id)) { + return false; + } + const depPlugin = this.app.plugins.getPlugin(id); + if (depPlugin) { + return !isPluginOlderThan(depPlugin, this.dependencies[id].version) + } + return false; + } + + setProfileTagAsCSSClass(view: MarkdownView) { + if (!view.file) return; + const profile = getProfile(this, view.file); + const classes = [ + ...profile.meta.tags.map((tag) => `math-booster-${tag}`), // deprecated + ...profile.meta.tags.map((tag) => `latex-referencer-${tag}`), + ]; + for (const el of [getMarkdownSourceViewEl(view), getMarkdownPreviewViewEl(view)]) { + if (el) { + el.classList.forEach((cls) => { + if (cls.startsWith("math-booster-") || cls.startsWith("latex-referencer-")) { + el.classList.remove(cls); + } + }); + el?.addClass(...classes); + } + } + } + + updateEditorExtensions() { + this.editorExtensions.length = 0; + + // theorem callouts + this.editorExtensions.push(this.theoremCalloutsField = createTheoremCalloutsField(this)); + this.editorExtensions.push(createTheoremCalloutNumberingViewPlugin(this)); + + // equation numbers + this.editorExtensions.push(createEquationNumberPlugin(this)); + + // proofs + if (this.extraSettings.enableProof) { + this.editorExtensions.push(createProofDecoration(this)); + // this.editorExtensions.push(this.proofPositionField = proofPositionFieldFactory(this)); + // this.editorExtensions.push(proofDecorationFactory(this)); + // this.editorExtensions.push(proofFoldFactory(this)); + } + + this.app.workspace.updateOptions(); + } + + registerCommands() { + this.addCommand({ + id: 'insert-display-math', + name: 'Insert display math', + editorCallback: insertDisplayMath, + }); + + this.addCommand({ + id: 'insert-theorem-callout', + name: 'Insert theorem callout', + editorCheckCallback: (checking, editor, context) => { + if (!context.file) return false; + + if (!checking) { + new TheoremCalloutModal( + this.app, this, context.file, + (config) => { + insertTheoremCallout(editor, config); + }, + "Insert", "Insert theorem callout", + ).open(); + } + + return true; + } + }); + + this.addCommand({ + id: 'search', + name: 'Search', + callback: () => { + new MathSearchModal(this).open(); + } + }) + + this.addCommand({ + id: 'open-local-settings-for-current-note', + name: 'Open local settings for the current note', + callback: () => { + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + if (view?.file) { + new ContextSettingModal(this.app, this, view.file).open(); + } + } + }); + + this.addCommand({ + id: 'insert-proof', + name: 'Insert proof', + editorCallback: (editor, context) => insertProof(this, editor, context) + }); + + this.addCommand({ + id: 'convert-equation-number-to-tag', + name: 'Convert equation numbers in the current note to static \\tag{}', + callback: () => { + const file = this.app.workspace.getActiveFile(); + if (file) staticifyEqNumber(this, file); + } + }); + + this.addCommand({ + id: 'migrate-from-v1', + name: 'Migrate from version 1', + callback: () => { + new MigrationModal(this).open(); + } + }); + } + + forceRerender() { + setTimeout(async () => { + for (const leaf of this.app.workspace.getLeavesOfType('markdown')) { + const view = leaf.view as MarkdownView; + const state = view.getEphemeralState(); + view.previewMode.rerender(true); + view.setEphemeralState(state); + } + }, 800); + } +} diff --git a/src/notice.ts b/src/notice.ts new file mode 100644 index 0000000..7989764 --- /dev/null +++ b/src/notice.ts @@ -0,0 +1,371 @@ +import { Modal, Setting, Component, MarkdownRenderer, Notice } from "obsidian"; + +import LatexReferencer from "main"; +import { isPluginOlderThan } from "utils/obsidian"; +import { rewriteTheoremCalloutFromV1ToV2 } from "utils/plugin"; + + +export class PluginSplitNoticeModal extends Modal { + component: Component; + + constructor(public plugin: LatexReferencer) { + super(plugin.app); + this.component = new Component(); + } + + onOpen() { + const { app, plugin, contentEl, titleEl } = this; + plugin.addChild(this.component); + contentEl.empty(); + + titleEl.setText(`${plugin.manifest.name} ver. ${plugin.manifest.version}`); + + new Setting(contentEl) + .setName('Some features of this plugin have been rewritten with a bunch of improvements, and they are now available as the following separate plugins:') + .setHeading(); + + for (const { name, id, desc } of [ + { + name: 'Better Math in Callouts & Blockquotes', + id: 'math-in-callout', + desc: 'Add better Live Preview support for math rendering inside callouts & blockquotes. It renders math expressions in callouts and provides appropriate handling of multi-line equations inside blockquotes.' + }, + { + name: 'Rendered Block Link Suggestions', + id: 'rendered-block-link-suggestions', + desc: 'Render equations and other types of blocks in Obsidian\'s built-in link suggestions' + } + ]) { + const installed = id in (app.plugins as any).manifests; + const enabled = app.plugins.enabledPlugins.has(id); + + new Setting(contentEl) + .setName(name) + .setDesc(desc) + .addButton((button) => { + button + .setButtonText(enabled ? 'Already enabled!' : installed ? 'Enable' : 'Install') + .then((button) => enabled || button.setCta()) + .onClick(() => { + self.open(`obsidian://show-plugin?id=${id}`); + this.component.registerDomEvent(window, 'click', (evt) => { + this.onOpen(); + }) + }); + }) + } + } + + onClose() { + this.contentEl.empty(); + this.component.unload(); + } +} + +export class RenameNoticeModal extends Modal { + component: Component; + + constructor(public plugin: LatexReferencer) { + super(plugin.app); + this.component = new Component(); + } + + onOpen() { + this.plugin.addChild(this.component) + + const { contentEl, titleEl } = this; + contentEl.empty(); + + titleEl.setText('Math Booster has been renamed'); + + MarkdownRenderer.render( + this.app, + `Starting from version 2.2.0, Math Booster has been renamed to ***LaTeX-like Theorem & Equation Referencer*** for better clarity and discoverability.\n\nWhile the display name in the community plugin browser may still reflect the previous version, it will be updated shortly.\n\nA big thank you for those who shared their ideas [here](https://github.com/RyotaUshio/obsidian-math-booster/issues/210)!\n\n> [!warning]\n> If you have custom CSS snippets with CSS classes .math-booster-*, don't worry, they still work!\n> \n> But I do recommend you to replace them with .latex-referencer-* as the old class names might be removed in the future.`, + contentEl, + '', + this.component + ); + + new Setting(contentEl) + .addButton((button) => { + button.setCta() + .setButtonText('Okay, I got it') + .onClick(() => this.close()); + }) + .then((setting) => setting.settingEl.style.border = 'none'); + } + + onClose() { + this.contentEl.empty(); + this.component.unload(); + } +} + + +export class DependencyNotificationModal extends Modal { + component: Component; + + constructor(public plugin: LatexReferencer, public dependenciesOK: boolean, public v1: boolean) { + super(plugin.app); + this.component = new Component(); + } + + async onOpen() { + this.plugin.addChild(this.component); + + const { contentEl, titleEl } = this; + contentEl.empty(); + + titleEl.setText(`${this.plugin.manifest.name} ${this.plugin.manifest.version}`) + + if (!this.dependenciesOK) this.showDependencies(); + + if (this.v1) this.showMigrationGuild(); + } + + showDependencies() { + this.contentEl.createDiv({ + text: `${this.plugin.manifest.name} requires the following plugin to work properly.`, + attr: { style: "margin-bottom: 1em;" } + }); + + /** + * Validity indicator was taken from the Latex Suite plugin (https://github.com/artisticat1/obsidian-latex-suite/blob/a5914c70c16d5763a182ec51d9716110b40965cf/src/settings.ts) + * + * MIT License + * + * Copyright (c) 2022 artisticat1 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + for (const depenedency of Object.values(this.plugin.dependencies)) { + const depPlugin = this.app.plugins.getPlugin(depenedency.id); + const isValid = depPlugin && !isPluginOlderThan(depPlugin, depenedency.version); + const setting = new Setting(this.contentEl) + .setName(depenedency.name) + .addExtraButton((button) => { + button.setIcon(isValid ? "checkmark" : "cross"); + const el = button.extraSettingsEl; + el.addClass("math-booster-dependency-validation"); + el.removeClass(isValid ? "invalid" : "valid"); + el.addClass(isValid ? "valid" : "invalid"); + }); + setting.descEl.createDiv( + { + text: + `Required version: ${depenedency.version}+ / ` + + (depPlugin ? `Currently installed: ${depPlugin.manifest.version}` + : `Not installed or enabled`) + } + ); + } + } + + async showMigrationGuild() { + this.contentEl.createEl('h3', { text: "Migration from version 1" }); + + MarkdownRenderer.render( + this.app, + `LaTeX-like Theorem & Equation Referencer (formerly called Math Booster) version 2 introduces a [new format for theorem callouts](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/theorem-callouts.html). + +To fully enjoy version 2, click the button below to convert the old theorem format to the new one. Alternatively, you can do it later by running the command "Migrate from version 1".`, + this.contentEl.createDiv(), + '', + this.component + ); + + new Setting(this.contentEl) + .addButton((button) => { + button.setButtonText('Convert') + .setCta() + .onClick(() => { + this.close(); + new MigrationModal(this.plugin).open(); + }) + }) + .addButton((button) => button.setButtonText('Not now') + .onClick(() => this.close())) + .then(setting => setting.settingEl.style.border = 'none'); + + + + + const descEl = this.contentEl.createDiv({ cls: 'math-booster-version-2-release-note-modal' }); + + await MarkdownRenderer.render(this.app, + `### What's new in version 2 + +- [New format for theorem callouts](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/theorem-callouts.html): + - *much cleaner*, + - *more intuitive*, + - *more keyboard-friendly*, + - and *less plugin-dependent* than the previous format +- New indexing mechanism: + - no longer blocks UI + - no longer hard-codes theorem indices in notes directly +- [Enhancing Obsidian's built-in link autocomplete](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/enhancing-obsidian's-built-in-link-autocomplete.html): now equations are rendered in the built-in autocomplete as well. +- [Custom link autocomplete](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/custom-link-autocomplete.html) improvements: filter theorems & equations (*entire vault/recent notes/active note*) +- [Search modal](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/search-&-link-autocomplete/search-modal.html): more control & flexibility than editor autocomplete, including *Dataview queries* +- Adding metadata to [theorems](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/theorem-callouts/theorem-callouts.html) and [equations](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/equations.html) with comments +- Theorem numbers and [equation numbers](https://ryotaushio.github.io/obsidian-latex-theorem-equation-referencer/equations.html) now can be displayed *almost everywhere*: + +##### Version 1: + +| | Theorem number | Equation number | +| ------------------ | -------------- | --------------- | +| Reading view | ✅ | ✅ | +| Live preview | ✅ | ✅ | +| Embeds | ✅ | | +| Hover page preview | ✅ | | +| PDF export | ✅ | | + +##### **🎉 Version 2:** + +| | Theorem number | Equation number | +| ------------------ | -------------- | --------------- | +| Reading view | ✅ | ✅ | +| Live preview | ✅ | ✅ | +| Embeds | ✅ | ✅ | +| Hover page preview | ✅ | ✅ | +| PDF export | ✅ | ✅ | + +### No longer supported + +- ["Show backlinks" right-click menu](https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer/blob/1.0.4/docs/backlinks.md) + - Use [Strange New Worlds](https://github.com/TfTHacker/obsidian42-strange-new-worlds) instead. +- [Projects](https://github.com/RyotaUshio/obsidian-latex-theorem-equation-referencer/blob/1.0.4/docs/projects.md) + - might be supported later with some improvements + +`, descEl, '', this.component); + descEl.querySelectorAll('.copy-code-button').forEach((el) => el.remove()); + } + + onClose() { + this.contentEl.empty(); + this.component.unload(); + } +} + + +export class MigrationModal extends Modal { + component: Component; + + constructor(public plugin: LatexReferencer) { + super(plugin.app); + this.component = new Component(); + this.plugin.addChild(this.component); + } + + async onOpen() { + let { contentEl, modalEl, titleEl } = this; + contentEl.empty(); + + modalEl.querySelector('.modal-close-button')?.remove(); + titleEl.setText("Convert theorem callouts' format from v1 to v2") + + const descEl = contentEl.createDiv(); + await MarkdownRenderer.render( + this.app, + ` +In order to enjoy LaTeX-like Theorem & Equation Referencer, you need to convert the old theorem format from Math Booster version 1: + +\`\`\`md +> [!math|{"type":"theorem","number":"auto","title":"Main result","label":"main-result","_index":0}] Theorem 1 (Main result). +\`\`\` + +to the new format: + +\`\`\`md +> [!theorem] Main result +> %% label: main-result %% +\`\`\` + +> [!WARNING] +> **MAKE SURE YOU HAVE A BACKUP OF YOUR VAULT BEFORE CONTINUING. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM THIS OPERATION.** +`, + descEl, '', this.component); + descEl.querySelectorAll('.copy-code-button').forEach(el => el.remove()); + + await new Promise((resolve) => { + new Setting(contentEl) + .setName('Are you sure to proceed?') + .addButton((button) => { + button.setButtonText('Yes').setWarning().onClick(() => resolve()) + }) + .addButton((button) => { + button.setButtonText('No').onClick(() => this.close()) + }); + }); + + // @ts-ignore + if (!this.app.metadataCache.initialized || !this.plugin.indexManager.initialized) { + new Notice('Obsidian is still indexing the vault. Try again after the cache is fully initialized.'); + return; + } + + contentEl.empty(); + + const waitForCacheRefresh = new Setting(contentEl) + .setName('Preparing the fresh cache...'); + await new Promise((resolve) => { + waitForCacheRefresh.addProgressBar((bar) => { + let progress = 0; + const timer = window.setInterval(() => { + bar.setValue(progress++) + if (progress >= 100) { + window.clearInterval(timer); + resolve(); + } + }, 3 * 10); + }) + }); + waitForCacheRefresh.setName('Preparing the fresh cache... Done!'); + + const converting = new Setting(contentEl) + .setName('Converting...') + // .then(setting => setting.controlEl.style.width = '50px'); + await new Promise((resolve) => { + converting.addProgressBar(async (bar) => { + const files = this.app.vault.getMarkdownFiles(); + let done = 0; + const all = files.length; + for (const file of files) { + await rewriteTheoremCalloutFromV1ToV2(this.plugin, file); + bar.setValue(done++ / all * 100); + } + bar.setValue(100); + resolve(); + }) + }); + converting.setName('Converting... Done!'); + + new Setting(contentEl) + .addButton((button) => { + button.setButtonText('Close') + .setCta() + .onClick(() => this.close()) + }) + } + + onClose() { + this.contentEl.empty(); + this.component.unload(); + } + +} \ No newline at end of file diff --git a/src/patches/link-completion.ts b/src/patches/link-completion.ts new file mode 100644 index 0000000..36e1318 --- /dev/null +++ b/src/patches/link-completion.ts @@ -0,0 +1,69 @@ +import { EditorSuggest, Notice, TFile, renderMath } from "obsidian"; +import { around } from "monkey-around"; + +import LatexReferencer from "main"; +import { MarkdownPage, TheoremCalloutBlock } from 'index/typings/markdown'; +import { isTheoremCallout, resolveSettings } from 'utils/plugin'; +import { formatTitle } from 'utils/format'; +import { _readTheoremCalloutSettings } from 'utils/parse'; +import { capitalize } from 'utils/general'; +import { renderTextWithMath } from "utils/render"; + +export const patchLinkCompletion = (plugin: LatexReferencer) => { + const suggest = (plugin.app.workspace as any).editorSuggest.suggests[0]; // built-in link completion + if (!Object.hasOwn(suggest, 'suggestManager')) new Notice(`Failed to patch Obsidian\'s built-in link completion. Please reload ${plugin.manifest.name}.`); + const prototype = suggest.constructor.prototype as EditorSuggest; + + plugin.register(around(prototype, { + renderSuggestion(old) { + return function (item: any, el: HTMLElement) { + old.call(this, item, el); + + if (plugin.extraSettings.showTheoremTitleinBuiltin && item.type === 'block' && item.node.type === 'callout' && isTheoremCallout(plugin, item.node.callout.type)) { + let title: string = item.node.children.find((child: any) => child.type === 'callout-title')?.children.map((child: any) => child.value).join('') ?? ''; + const content = item.display.slice(title.length); + const page = plugin.indexManager.index.load(item.file.path); + if (MarkdownPage.isMarkdownPage(page)) { + const block = page.getBlockByLineNumber(item.node.position.start.line - 1); // line number starts from 1 + if (TheoremCalloutBlock.isTheoremCalloutBlock(block)) { + renderInSuggestionTitleEl(el, (suggestionTitleEl) => { + el.addClass('math-booster', 'suggestion-item-theorem-callout'); + suggestionTitleEl.replaceChildren(); + const children = renderTextWithMath(block.$printName); + suggestionTitleEl + .createDiv() + .replaceChildren(...children); + if (plugin.extraSettings.showTheoremContentinBuiltin && content) suggestionTitleEl.createDiv({ text: content }); + }); + return; + } + } + const parsed = _readTheoremCalloutSettings({ type: item.node.callout.type, metadata: item.node.callout.data }, plugin.extraSettings.excludeExampleCallout); + if (parsed) { + const { type, number } = parsed; + if (title === capitalize(type)) title = ''; + const formattedTitle = formatTitle(plugin, item.file as TFile, resolveSettings({ type, number, title }, plugin, item.file as TFile), true); + renderInSuggestionTitleEl(el, (suggestionTitleEl) => { + el.addClass('math-booster', 'suggestion-item-theorem-callout'); + suggestionTitleEl.replaceChildren(); + const children = renderTextWithMath(formattedTitle); + suggestionTitleEl + .createDiv() + .replaceChildren(...children); + if (plugin.extraSettings.showTheoremContentinBuiltin && content) suggestionTitleEl.createDiv({ text: content }); + }); + return; + } + } + } + } + })); +}; + + +function renderInSuggestionTitleEl(el: HTMLElement, cb: (suggestionTitleEl: HTMLElement) => void) { + // setTimeout(() => { + const suggestionTitleEl = el.querySelector('.suggestion-title'); + if (suggestionTitleEl) cb(suggestionTitleEl); + // }); +} \ No newline at end of file diff --git a/src/patches/page-preview.ts b/src/patches/page-preview.ts new file mode 100644 index 0000000..b387bbe --- /dev/null +++ b/src/patches/page-preview.ts @@ -0,0 +1,24 @@ +import { HoverParent } from 'obsidian'; +import { around } from 'monkey-around'; +import LatexReferencer from '../main'; + +// Inspired by Hover Editor (https://github.com/nothingislost/obsidian-hover-editor/blob/c038424acb15c542f0ad5f901d74c75d4316f553/src/main.ts#L396) + +// Save the last linktext that triggered hover page preview in the plugin instance to display theorem/equation numbers in it + +export const patchPagePreview = (plugin: LatexReferencer) => { + const { app } = plugin; + + plugin.register( + // @ts-ignore + around(app.internalPlugins.plugins['page-preview'].instance.constructor.prototype, { + onLinkHover(old: Function) { + return function (parent: HoverParent, targetEl: HTMLElement, linktext: string, ...args: unknown[]) { + old.call(this, parent, targetEl, linktext, ...args); + // Save the linktext in the plugin instance + plugin.lastHoverLinktext = linktext; + } + } + }) + ); +} \ No newline at end of file diff --git a/src/proof/common.ts b/src/proof/common.ts new file mode 100644 index 0000000..e840659 --- /dev/null +++ b/src/proof/common.ts @@ -0,0 +1,17 @@ +import { Profile } from "settings/profile"; + +export function makeProofClasses(which: "begin" | "end", profile: Profile) { + return [ + "math-booster-" + which + "-proof", // deprecated + "latex-referencer-" + which + "-proof", + ...profile.meta.tags.map((tag) => "math-booster-" + which + "-proof-" + tag), // deprecated + ...profile.meta.tags.map((tag) => "latex-referencer-" + which + "-proof-" + tag) + ]; +} + +export function makeProofElement(which: "begin" | "end", profile: Profile) { + return createSpan({ + text: profile.body.proof[which], + cls: makeProofClasses(which, profile) + }) +} diff --git a/src/proof/live-preview.ts b/src/proof/live-preview.ts new file mode 100644 index 0000000..0ee25de --- /dev/null +++ b/src/proof/live-preview.ts @@ -0,0 +1,211 @@ +import { editorInfoField } from 'obsidian'; +import { RangeSetBuilder } from '@codemirror/state'; +import { Decoration, DecorationSet, EditorView, PluginValue, ViewPlugin, ViewUpdate, WidgetType } from '@codemirror/view'; +import { SyntaxNodeRef } from '@lezer/common'; +import { syntaxTree } from '@codemirror/language'; + +import LatexReferencer from 'main'; +import { nodeText, rangesHaveOverlap } from 'utils/editor'; +import { Profile } from 'settings/profile'; +import { renderMarkdown } from 'utils/render'; +import { resolveSettings } from 'utils/plugin'; +import { makeProofClasses, makeProofElement } from './common'; + +export const INLINE_CODE = "inline-code"; +export const LINK_BEGIN = "formatting-link_formatting-link-start"; +export const LINK = "hmd-internal-link"; +export const LINK_END = "formatting-link_formatting-link-end"; + + +abstract class ProofWidget extends WidgetType { + containerEl: HTMLElement | null; + + constructor(public plugin: LatexReferencer, public profile: Profile) { + super(); + this.containerEl = null; + } + + eq(other: EndProofWidget): boolean { + return this.profile.id === other.profile.id; + } + + toDOM(): HTMLElement { + return this.containerEl ?? (this.containerEl = this.initDOM()); + } + + abstract initDOM(): HTMLElement; + + ignoreEvent(event: Event): boolean { + // the DOM element won't respond to clicks without this + return false; + } +} + +class BeginProofWidget extends ProofWidget { + containerEl: HTMLElement | null; + + constructor( + plugin: LatexReferencer, profile: Profile, + public display: string | null, + public linktext: string | null, + public sourcePath: string + ) { + super(plugin, profile); + } + + eq(other: BeginProofWidget): boolean { + return this.profile.id === other.profile.id && this.display === other.display && this.linktext === other.linktext && this.sourcePath == other.sourcePath; + } + + initDOM(): HTMLElement { + let display = this.linktext + ? `${this.profile.body.proof.linkedBeginPrefix} [[${this.linktext}]]${this.profile.body.proof.linkedBeginSuffix}` + : this.display; + + if (display) { + const el = createSpan({ cls: makeProofClasses("begin", this.profile) }); + BeginProofWidget.renderDisplay(el, display, this.sourcePath, this.plugin); + return el; + } + + return makeProofElement("begin", this.profile); + } + + static async renderDisplay(el: HTMLElement, display: string, sourcePath: string, plugin: LatexReferencer) { + const children = await renderMarkdown(display, sourcePath, plugin); + if (children) { + el.replaceChildren(...children); + } + } +} + + +class EndProofWidget extends ProofWidget { + containerEl: HTMLElement | null; + + initDOM(): HTMLElement { + return makeProofElement("end", this.profile); + } +} + + +export interface ProofPosition { + display?: string, + linktext?: string, + linknodes?: { linkBegin: SyntaxNodeRef, link: SyntaxNodeRef, linkEnd: SyntaxNodeRef }, +} + + +export const createProofDecoration = (plugin: LatexReferencer) => ViewPlugin.fromClass( + class implements PluginValue { + decorations: DecorationSet; + + constructor(view: EditorView) { + this.decorations = this.makeDeco(view); + } + + update(update: ViewUpdate) { + if (update.docChanged || update.viewportChanged || update.selectionSet) { + if (update.view.composing) { + this.decorations = this.decorations.map(update.changes); // User is using IME + } else { + this.decorations = this.makeDeco(update.view); + } + } + } + + makeDeco(view: EditorView): DecorationSet { + const { state } = view; + const { app } = plugin; + const tree = syntaxTree(state); + const ranges = state.selection.ranges; + + const file = state.field(editorInfoField).file; + const sourcePath = file?.path ?? ""; + const settings = resolveSettings(undefined, plugin, file ?? app.vault.getRoot()); + const profile = plugin.extraSettings.profiles[settings.profile]; + + const builder = new RangeSetBuilder(); + + for (const { from, to } of view.visibleRanges) { + tree.iterate({ + from, to, + enter(node) { + if (node.name !== INLINE_CODE) return; + + let start = -1; + let end = -1; + let display: string | null = null; + let linktext: string | null = null; + + const text = nodeText(node, state); + + if (text.startsWith(settings.beginProof)) { + // handle "\begin{proof}" + const rest = text.slice(settings.beginProof.length); + if (!rest) { // only "\begin{proof}" + start = node.from - 1; + end = node.to + 1; // 1 = "`".length + display = null; + } else { + const match = rest.match(/^\[(.*)\]$/); + if (match) { // custom display text is given, e.g. "\begin{proof}[Solutions.]" + start = node.from - 1; + end = node.to + 1; // 1 = "`".length + display = match[1]; + } + } + + if (start === -1 || end === -1) return; // not proof + + if (state.sliceDoc(node.to + 1, node.to + 2) == "@") { // Check if "`\begin{proof}`@[[link]]" or "`\begin{proof}[display]`@[[link]]" + const next = node.node.nextSibling?.nextSibling; + const afterNext = node.node.nextSibling?.nextSibling?.nextSibling; + const afterAfterNext = node.node.nextSibling?.nextSibling?.nextSibling?.nextSibling; + if (next?.name === LINK_BEGIN && afterNext?.name === LINK && afterAfterNext?.name === LINK_END) { + linktext = nodeText(afterNext, state); + end = afterAfterNext.to; + } + } + + if (!rangesHaveOverlap(ranges, start, end)) { + builder.add( + start, end, + Decoration.replace({ + widget: new BeginProofWidget(plugin, profile, display, linktext, sourcePath) + }) + ); + } + + } else if (text === settings.endProof) { + // handle "\end{proof}" + start = node.from - 1; + end = node.to + 1; // 1 = "`".length + + if (!rangesHaveOverlap(ranges, start, end)) { + builder.add( + start, end, + Decoration.replace({ + widget: new EndProofWidget(plugin, profile) + }) + ); + } + } + } + }); + } + return builder.finish(); + } + }, { + decorations: instance => instance.decorations +}); + +// export const proofFoldFactory = (plugin: LatexReferencer) => foldService.of((state: EditorState, lineStart: number, lineEnd: number) => { +// const positions = state.field(plugin.proofPositionField); +// for (const pos of positions) { +// if (pos.begin && pos.end && lineStart <= pos.begin.from && pos.begin.to <= lineEnd) { +// return { from: pos.begin.to, to: pos.end.to }; +// } +// } +// return null; +// }); diff --git a/src/proof/reading-view.ts b/src/proof/reading-view.ts new file mode 100644 index 0000000..8f6faf0 --- /dev/null +++ b/src/proof/reading-view.ts @@ -0,0 +1,120 @@ +import LatexReferencer from "main"; +import { App, MarkdownPostProcessorContext, MarkdownRenderChild, TFile } from "obsidian"; +import { resolveSettings } from "utils/plugin"; +import { makeProofClasses, makeProofElement } from "./common"; +import { renderMarkdown } from "utils/render"; +import { Profile } from "settings/profile"; + +export const createProofProcessor = (plugin: LatexReferencer) => (element: HTMLElement, context: MarkdownPostProcessorContext) => { + if (!plugin.extraSettings.enableProof) return; + + const { app } = plugin; + + const file = app.vault.getAbstractFileByPath(context.sourcePath); + if (!(file instanceof TFile)) return; + + const settings = resolveSettings(undefined, plugin, file); + const codes = element.querySelectorAll("code"); + for (const code of codes) { + const text = code.textContent; + if (!text) continue; + + if (text.startsWith(settings.beginProof)) { + const rest = text.slice(settings.beginProof.length); + let displayMatch; + if (!rest) { + context.addChild(new ProofRenderer(app, plugin, code, "begin", file)); + } else if (displayMatch = rest.match(/^\[(.*)\]$/)) { + const display = displayMatch[1]; + context.addChild(new ProofRenderer(app, plugin, code, "begin", file, display)); + } + } else if (code.textContent == settings.endProof) { + context.addChild(new ProofRenderer(app, plugin, code, "end", file)); + } + } +}; + + +function parseAtSignLink(codeEl: HTMLElement) { + const next = codeEl.nextSibling; + const afterNext = next?.nextSibling; + const afterAfterNext = afterNext?.nextSibling; + if (afterNext) { + if (next.nodeType == Node.TEXT_NODE && next.textContent == "@" + && afterNext instanceof HTMLElement && afterNext.matches("a.original-internal-link") + && afterAfterNext instanceof HTMLElement && afterAfterNext.matches("a.mathLink-internal-link")) { + return { atSign: next, links: [afterNext, afterAfterNext] }; + } + } +} + +export class ProofRenderer extends MarkdownRenderChild { + atSignParseResult: { atSign: ChildNode, links: HTMLElement[] } | undefined; + + constructor(public app: App, public plugin: LatexReferencer, containerEl: HTMLElement, public which: "begin" | "end", public file: TFile, public display?: string) { + super(containerEl); + this.atSignParseResult = parseAtSignLink(this.containerEl); + } + + onload(): void { + this.update(); + this.registerEvent( + this.plugin.indexManager.on("local-settings-updated", (file) => { + if (file == this.file) { + this.update(); + } + }) + ); + this.registerEvent( + this.plugin.indexManager.on("global-settings-updated", () => { + this.update(); + }) + ); + } + + update(): void { + const settings = resolveSettings(undefined, this.plugin, this.file); + const profile = this.plugin.extraSettings.profiles[settings.profile]; + + /** + * `\begin{proof}`@[[]] => Proof of Theorem 1. + */ + if (this.atSignParseResult) { + const { atSign, links } = this.atSignParseResult; + const newEl = createSpan({ cls: makeProofClasses(this.which, profile) }); + newEl.replaceChildren(profile.body.proof.linkedBeginPrefix, ...links, profile.body.proof.linkedBeginSuffix); + this.containerEl.replaceWith(newEl); + this.containerEl = newEl; + atSign.textContent = ""; + return; + } + + /** + * `\begin{proof}[Foo.]` => Foo. + */ + if (this.display) { + this.renderDisplay(profile); + return; + } + + /** + * `\begin{proof}` => Proof. + */ + const newEl = makeProofElement(this.which, profile); + this.containerEl.replaceWith(newEl); + this.containerEl = newEl; + } + + async renderDisplay(profile: Profile) { + if (this.display) { + const children = await renderMarkdown(this.display, this.file.path, this.plugin); + if (children) { + const el = createSpan({ cls: makeProofClasses(this.which, profile) }); + el.replaceChildren(...children); + this.containerEl.replaceWith(el); + this.containerEl = el; + } + } + } +} + diff --git a/src/search/core.ts b/src/search/core.ts new file mode 100644 index 0000000..2e3482d --- /dev/null +++ b/src/search/core.ts @@ -0,0 +1,311 @@ +import { App, EditorSuggestContext, Instruction, Notice, Scope, SearchResult, TFile, finishRenderMath, prepareFuzzySearch, prepareSimpleSearch, renderMath, sortSearchResults } from 'obsidian'; + +import LatexReferencer from 'main'; +import { MathIndex } from 'index/math-index'; +import { EquationBlock, MarkdownBlock, MarkdownPage, MathBlock, TheoremCalloutBlock } from 'index/typings/markdown'; +import { getFileTitle } from 'index/utils/normalizers'; +import { LEAF_OPTION_TO_ARGS } from 'settings/settings'; +import { formatLabel } from 'utils/format'; +import { getModifierNameInPlatform, openFileAndSelectPosition } from 'utils/obsidian'; +import { insertBlockIdIfNotExist, resolveSettings } from 'utils/plugin'; +import { MathSearchModal } from './modal'; +import { renderTextWithMath } from 'utils/render'; + + +export type ScoredMathBlock = { match: SearchResult, block: MathBlock }; + +export type SearchRange = 'active' | 'vault' | 'recent' | 'dataview'; +export type QueryType = 'theorem' | 'equation' | 'both'; + +export type MathSearchCoreCreator = (parent: SuggestParent) => MathSearchCore; + +export interface SuggestParent { + app: App; + plugin: LatexReferencer; + scope: Scope; + range: SearchRange; + queryType: QueryType; + dvQuery: string; + getContext(): Omit | null; + setInstructions(instructions: Instruction[]): void; + getSelectedItem(): MathBlock; +} + +export abstract class MathSearchCore { + app: App; + plugin: LatexReferencer; + index: MathIndex; + scope: Scope; + + constructor(public parent: SuggestParent) { + this.plugin = parent.plugin; + this.app = this.plugin.app; + this.index = this.plugin.indexManager.index; + this.scope = parent.scope; + } + + static getCore(parent: SuggestParent): MathSearchCore | null { + const { app, range, queryType, dvQuery } = parent; + if (range === 'dataview') { + const dv = app.plugins.plugins.dataview?.api; + if (!dv) return null; + return new DataviewQuerySearchCore(parent, queryType, dv, dvQuery); + } + + if (range === 'vault') { + if (queryType === 'both') return new WholeVaultTheoremEquationSearchCore(parent); + else if (queryType === 'theorem') return new WholeVaultTheoremSearchCore(parent); + else if (queryType === 'equation') return new WholeVaultEquationSearchCore(parent); + } else if (range === 'recent') return new RecentNotesSearchCore(parent, queryType); + else if (range === 'active') return new ActiveNoteSearchCore(parent, queryType); + + return null; + } + + setScope() { + // Mod (by default) + Enter to jump to the selected item + this.scope.register([this.plugin.extraSettings.modifierToJump], "Enter", () => { + const context = this.parent.getContext(); + if (context) { + const { editor, start, end } = context; + editor.replaceRange("", start, end); + } + const item = this.parent.getSelectedItem(); + const file = this.app.vault.getAbstractFileByPath(item.$file); // the file containing the selected item + if (!(file instanceof TFile)) return; + openFileAndSelectPosition(this.app, file, item.$pos, ...LEAF_OPTION_TO_ARGS[this.plugin.extraSettings.suggestLeafOption]); + if (this.parent instanceof MathSearchModal) this.parent.close(); + return false; + }); + + // Shift (by default) + Enter to insert a link to the note containing the selected item + this.scope.register([this.plugin.extraSettings.modifierToNoteLink], "Enter", () => { + const item = this.parent.getSelectedItem(); + this.selectSuggestionImpl(item, true); + if (this.parent instanceof MathSearchModal) this.parent.close(); + return false; + }); + + if (this.plugin.extraSettings.showModifierInstruction) { + this.parent.setInstructions([ + { command: "↑↓", purpose: "to navigate" }, + { command: "↵", purpose: "to insert link" }, + { command: `${getModifierNameInPlatform(this.plugin.extraSettings.modifierToNoteLink)} + ↵`, purpose: "to insert link to note" }, + { command: `${getModifierNameInPlatform(this.plugin.extraSettings.modifierToJump)} + ↵`, purpose: "to jump" }, + ]); + } + } + + async getSuggestions(query: string): Promise { + const ids = await this.getUnsortedSuggestions(); + const results = this.gradeSuggestions(ids, query); + this.postProcessResults(results); + sortSearchResults(results); + return results.map((result) => result.block); + } + + abstract getUnsortedSuggestions(): Promise> | Promise>; + + postProcessResults(results: ScoredMathBlock[]) { } + + gradeSuggestions(ids: Array | Set, query: string) { + const callback = (this.plugin.extraSettings.searchMethod == "Fuzzy" ? prepareFuzzySearch : prepareSimpleSearch)(query); + const results: ScoredMathBlock[] = []; + + for (const id of ids) { + const block = this.index.load(id) as MathBlock; + + let text = `${block.$printName} ${block.$file}}`; + + if (block.$type === "theorem") { + text += ` ${(block as TheoremCalloutBlock).$settings.type}`; + if (this.plugin.extraSettings.searchLabel) { + const file = this.app.vault.getAbstractFileByPath(block.$file); + if (file instanceof TFile) { + const resolvedSettings = resolveSettings((block as TheoremCalloutBlock).$settings, this.plugin, file); + text += ` ${formatLabel(resolvedSettings) ?? ""}` + } + } + } else if (block.$type === "equation") { + text += " " + (block as EquationBlock).$mathText; + } + + // run search + const result = callback(text); + if (result) { + results.push({ match: result, block }); + } + } + + return results; + } + + renderSuggestion(block: MathBlock, el: HTMLElement): void { + const baseEl = el.createDiv({ cls: "math-booster-search-item" }); + if (block.$printName) { + const children = renderTextWithMath(block.$printName); + baseEl.createDiv() + .replaceChildren(...children); + + } + const smallEl = baseEl.createEl( + "small", { + text: `${getFileTitle(block.$file)}, line ${block.$position.start + 1}`, + cls: "math-booster-search-item-description" + }); + if (block.$type === "equation") { + if (this.plugin.extraSettings.renderMathInSuggestion) { + const mjxContainerEl = renderMath((block as EquationBlock).$mathText, true); + baseEl.insertBefore(mjxContainerEl, smallEl); + } else { + const mathTextEl = createDiv({ text: (block as EquationBlock).$mathText }); + baseEl.insertBefore(mathTextEl, smallEl); + } + } + } + + selectSuggestion(item: MathBlock, evt: MouseEvent | KeyboardEvent): void { + this.selectSuggestionImpl(item, false); + // I don't know when to call finishRenderMath... + finishRenderMath(); + } + + async selectSuggestionImpl(block: MathBlock, insertNoteLink: boolean): Promise { + const context = this.parent.getContext(); + if (!context) return; + const fileContainingBlock = this.app.vault.getAbstractFileByPath(block.$file); + const cache = this.app.metadataCache.getCache(block.$file); + if (!(fileContainingBlock instanceof TFile) || !cache) return; + + const { editor, start, end, file } = context; + const settings = resolveSettings(undefined, this.plugin, file); + let success = false; + + const result = await insertBlockIdIfNotExist(this.plugin, fileContainingBlock, cache, block); + if (result) { + const { id, lineAdded } = result; + // We can't use FileManager.generateMarkdownLink here. + // This is because, when the user is turning off "Use [[Wikilinks]]", + // FileManager.generateMarkdownLink inserts a markdown link [](), not a wikilink [[]]. + // Markdown links are hard to deal with for the purpose of this plugin, and also + // MathLinks has some issues with markdown links (https://github.com/zhaoshenzhai/obsidian-mathlinks/issues/47). + // So we have to explicitly generate a wikilink here. + let linktext = ""; + if (fileContainingBlock != file) { + linktext += this.app.metadataCache.fileToLinktext(fileContainingBlock, file.path); + } + if (!insertNoteLink) { + linktext += `#^${id}`; + } + const link = `[[${linktext}]]` + const insertText = link + (settings.insertSpace ? " " : ""); + if (fileContainingBlock == file) { + editor.replaceRange( + insertText, + { line: start.line + lineAdded, ch: start.ch }, + { line: end.line + lineAdded, ch: end.ch } + ); + } else { + editor.replaceRange(insertText, start, end); + } + success = true; + } + + if (!success) { + new Notice(`${this.plugin.manifest.name}: Failed to read cache. Retry again later.`, 5000); + } + } +} + + +abstract class WholeVaultSearchCore extends MathSearchCore { + postProcessResults(results: ScoredMathBlock[]) { + results.forEach((result) => { + if (this.app.workspace.getLastOpenFiles().contains(result.block.$file)) { + result.match.score += this.plugin.extraSettings.upWeightRecent; + } + }); + } +} + +export class WholeVaultTheoremEquationSearchCore extends WholeVaultSearchCore { + async getUnsortedSuggestions(): Promise> { + return this.index.getByType('block-math-booster'); + } +} + +export class WholeVaultTheoremSearchCore extends WholeVaultSearchCore { + async getUnsortedSuggestions(): Promise> { + return this.index.getByType('block-theorem'); + } +} + +export class WholeVaultEquationSearchCore extends WholeVaultSearchCore { + async getUnsortedSuggestions(): Promise> { + return this.index.getByType('block-equation'); + } +} + + +/** Suggest theorems and/or equations from the given set of files. */ +export abstract class PartialSearchCore extends MathSearchCore { + constructor(parent: SuggestParent, public type: QueryType) { + super(parent); + } + + abstract getPaths(): Promise>; + + filterBlock(block: MarkdownBlock): boolean { + if (this.type === 'theorem') return TheoremCalloutBlock.isTheoremCalloutBlock(block); + if (this.type === 'equation') return EquationBlock.isEquationBlock(block); + return MathBlock.isMathBlock(block); + } + + async getUnsortedSuggestions() { + const ids: string[] = []; + const pages = (await this.getPaths()).map((path) => this.index.load(path)); + for (const page of pages) { + if (!MarkdownPage.isMarkdownPage(page)) continue; + + for (const section of page.$sections) { + for (const block of section.$blocks) { + if (this.filterBlock(block)) ids.push(block.$id); + } + } + } + return ids; + } +} + + +export class RecentNotesSearchCore extends PartialSearchCore { + async getPaths() { + return this.app.workspace.getLastOpenFiles(); + } +} + + +export class ActiveNoteSearchCore extends PartialSearchCore { + async getPaths() { + const path = this.app.workspace.getActiveFile()?.path; + return path?.endsWith('.md') ? [path] : []; + } +} + +export class DataviewQuerySearchCore extends PartialSearchCore { + public dvQuery: string; + + constructor(parent: SuggestParent, type: 'theorem' | 'equation' | 'both', public dv: any, dvQuery?: string) { + super(parent, type); + this.dvQuery = dvQuery ?? ''; + } + + async getPaths() { + const result = await this.dv.query(this.dvQuery); + if (result.successful && result.value.type === 'list') { + const links = result.value.values as { path: string }[]; + return links.map((link) => link.path); + } + return []; + } +} diff --git a/src/search/editor-suggest.ts b/src/search/editor-suggest.ts new file mode 100644 index 0000000..d3af859 --- /dev/null +++ b/src/search/editor-suggest.ts @@ -0,0 +1,120 @@ +import { Editor, EditorPosition, EditorSuggest, EditorSuggestContext, EditorSuggestTriggerInfo, Keymap, UserEvent } from "obsidian"; + +import LatexReferencer from "main"; +import { MathBlock } from "index/typings/markdown"; +import { MathSearchCore, SuggestParent, WholeVaultTheoremEquationSearchCore } from "./core"; +import { QueryType, SearchRange } from './core'; + + +export class LinkAutocomplete extends EditorSuggest implements SuggestParent { + queryType: QueryType; + range: SearchRange; + core: MathSearchCore; + triggers: Map; + + /** + * @param type The type of the block to search for. See: index/typings/markdown.ts + */ + constructor(public plugin: LatexReferencer) { + super(plugin.app); + this.setTriggers(); + this.core = new WholeVaultTheoremEquationSearchCore(this); + this.core.setScope(); + } + + get dvQuery(): string { + return this.plugin.extraSettings.autocompleteDvQuery; + } + + getContext() { + return this.context; + } + + getSelectedItem() { + // Reference: https://github.com/tadashi-aikawa/obsidian-various-complements-plugin/blob/be4a12c3f861c31f2be3c0f81809cfc5ab6bb5fd/src/ui/AutoCompleteSuggest.ts#L595-L619 + return this.suggestions.values[this.suggestions.selectedItem]; + } + + onTrigger(cursor: EditorPosition, editor: Editor): EditorSuggestTriggerInfo | null { + for (const [trigger, { range, queryType }] of this.triggers) { + const text = editor.getLine(cursor.line); + const index = text.lastIndexOf(trigger); + if (index >= 0) { + const query = text.slice(index + trigger.length); + if (query.startsWith("[[")) return null; // avoid conflict with the built-in link auto-completion + this.queryType = queryType; + this.range = range; + const core = MathSearchCore.getCore(this); + if (!core) return null; + this.core = core; + this.limit = this.plugin.extraSettings.suggestNumber; + return { + start: { line: cursor.line, ch: index }, + end: cursor, + query + }; + } + } + + return null; + } + + getSuggestions(context: EditorSuggestContext): Promise { + return this.core.getSuggestions(context.query); + } + + renderSuggestion(block: MathBlock, el: HTMLElement): void { + this.core.renderSuggestion(block, el); + } + + selectSuggestion(item: MathBlock, evt: MouseEvent | KeyboardEvent): void { + this.core.selectSuggestion(item, evt); + } + + setTriggers() { + const unsortedTriggers = {} as Record; + if (this.plugin.extraSettings.enableSuggest) { + unsortedTriggers[this.plugin.extraSettings.triggerSuggest] = { range: "vault", queryType: "both" }; + } + if (this.plugin.extraSettings.enableTheoremSuggest) { + unsortedTriggers[this.plugin.extraSettings.triggerTheoremSuggest] = { range: "vault", queryType: "theorem" }; + } + if (this.plugin.extraSettings.enableEquationSuggest) { + unsortedTriggers[this.plugin.extraSettings.triggerEquationSuggest] = { range: "vault", queryType: "equation" }; + } + if (this.plugin.extraSettings.enableSuggestRecentNotes) { + unsortedTriggers[this.plugin.extraSettings.triggerSuggestRecentNotes] = { range: "recent", queryType: "both" }; + } + if (this.plugin.extraSettings.enableTheoremSuggestRecentNotes) { + unsortedTriggers[this.plugin.extraSettings.triggerTheoremSuggestRecentNotes] = { range: "recent", queryType: "theorem" }; + } + if (this.plugin.extraSettings.enableEquationSuggestRecentNotes) { + unsortedTriggers[this.plugin.extraSettings.triggerEquationSuggestRecentNotes] = { range: "recent", queryType: "equation" }; + } + if (this.plugin.extraSettings.enableSuggestActiveNote) { + unsortedTriggers[this.plugin.extraSettings.triggerSuggestActiveNote] = { range: "active", queryType: "both" }; + } + if (this.plugin.extraSettings.enableTheoremSuggestActiveNote) { + unsortedTriggers[this.plugin.extraSettings.triggerTheoremSuggestActiveNote] = { range: "active", queryType: "theorem" }; + } + if (this.plugin.extraSettings.enableEquationSuggestActiveNote) { + unsortedTriggers[this.plugin.extraSettings.triggerEquationSuggestActiveNote] = { range: "active", queryType: "equation" }; + } + if (this.plugin.extraSettings.enableSuggestDataview) { + unsortedTriggers[this.plugin.extraSettings.triggerSuggestDataview] = { range: "dataview", queryType: "both" }; + } + if (this.plugin.extraSettings.enableTheoremSuggestDataview) { + unsortedTriggers[this.plugin.extraSettings.triggerTheoremSuggestDataview] = { range: "dataview", queryType: "theorem" }; + } + if (this.plugin.extraSettings.enableEquationSuggestDataview) { + unsortedTriggers[this.plugin.extraSettings.triggerEquationSuggestDataview] = { range: "dataview", queryType: "equation" }; + } + const sortedTriggers = new Map; + + Object.entries(unsortedTriggers) + .sort((a, b) => b[0].length - a[0].length) // sort by descending order of trigger length + .forEach(([k, v]) => sortedTriggers.set(k, v)); + + this.triggers = sortedTriggers; + } +} diff --git a/src/search/modal.ts b/src/search/modal.ts new file mode 100644 index 0000000..baa83e4 --- /dev/null +++ b/src/search/modal.ts @@ -0,0 +1,149 @@ +import { DataviewQuerySearchCore, QueryType, SearchRange, WholeVaultTheoremEquationSearchCore } from 'search/core'; + +import LatexReferencer from "main"; +import { App, EditorSuggestContext, MarkdownView, Setting, SuggestModal, TextAreaComponent } from "obsidian"; +import { MathSearchCore, SuggestParent } from "./core"; +import { MathBlock } from "index/typings/markdown"; + +export class MathSearchModal extends SuggestModal implements SuggestParent { + app: App; + core: MathSearchCore; + queryType: QueryType; + range: SearchRange; + dvQueryField: Setting; + topEl: HTMLElement; + + constructor(public plugin: LatexReferencer) { + super(plugin.app); + this.app = plugin.app; + this.core = new WholeVaultTheoremEquationSearchCore(this); + this.core.setScope(); + this.setPlaceholder('Type here...'); + + this.queryType = this.plugin.extraSettings.searchModalQueryType; + this.range = this.plugin.extraSettings.searchModalRange; + + this.topEl = this.modalEl.createDiv({ cls: 'math-booster-modal-top' }); + this.modalEl.insertBefore(this.topEl, this.modalEl.firstChild) + this.inputEl.addClass('math-booster-search-input'); + + this.limit = this.plugin.extraSettings.suggestNumber; + + new Setting(this.topEl) + .setName('Query type') + .addDropdown((dropdown) => { + dropdown.addOption('both', 'Theorems and equations') + .addOption('theorem', 'Theorems') + .addOption('equation', 'Equations'); + + // recover the last state + dropdown.setValue(this.plugin.extraSettings.searchModalQueryType) + + dropdown.onChange((value: QueryType) => { + this.queryType = value; + this.resetCore(); + // @ts-ignore + this.onInput(); + + // remember the last state + this.plugin.extraSettings.searchModalQueryType = value; + this.plugin.saveSettings(); + }) + }); + + new Setting(this.topEl) + .setName('Search range') + .addDropdown((dropdown) => { + dropdown.addOption('vault', 'Vault') + .addOption('recent', 'Recent notes') + .addOption('active', 'Active note') + .addOption('dataview', 'Dataview query'); + + // recover the last state + dropdown.setValue(this.plugin.extraSettings.searchModalRange) + dropdown.onChange((value: SearchRange) => { + this.range = value; + this.resetCore(); + // @ts-ignore + this.onInput(); + + // remember the last state + this.plugin.extraSettings.searchModalRange = value; + this.plugin.saveSettings(); + }) + }); + + this.dvQueryField = new Setting(this.topEl) + .setName('Dataview query') + .setDesc('Only LIST queries are supported.') + .then(setting => { + setting.controlEl.style.width = '60%'; + }) + .addTextArea((text) => { + text.inputEl.addClass('math-booster-dv-query') + text.inputEl.style.width = '100%'; + + text.setValue(this.plugin.extraSettings.searchModalDvQuery) // recover the last state + .setPlaceholder('LIST ...') + .onChange((dvQuery) => { + if (this.core instanceof DataviewQuerySearchCore) { + this.core.dvQuery = dvQuery; + // @ts-ignore + this.onInput(); + + // remember the last state + this.plugin.extraSettings.searchModalDvQuery = dvQuery; + this.plugin.saveSettings(); + } + }) + }); + + this.modalEl.insertBefore(this.inputEl, this.modalEl.firstChild); + + this.resetCore(); + } + + resetCore() { + const core = MathSearchCore.getCore(this); + if (core) this.core = core; + if (this.range === 'dataview') { + if (!core) { + this.dvQueryField.setDisabled(true); + this.dvQueryField.setDesc('Retry after enabling Dataview.') + .then(setting => setting.descEl.style.color = '#ea5555'); + } + this.dvQueryField.settingEl.show(); + } + else this.dvQueryField.settingEl.hide(); + } + + get dvQuery(): string { + return (this.dvQueryField.components[0] as TextAreaComponent).getValue(); + } + + getContext(): Omit | null { + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + if (!view?.file) return null; + + const start = view.editor.getCursor('from'); + const end = view.editor.getCursor('to'); + + return { file: view.file, editor: view.editor, start, end } + } + + getSelectedItem(): MathBlock { + return this.chooser.values![this.chooser.selectedItem]; + }; + + getSuggestions(query: string) { + return this.core.getSuggestions(query); + } + + renderSuggestion(value: MathBlock, el: HTMLElement) { + this.core.renderSuggestion(value, el); + } + + onChooseSuggestion(item: MathBlock, evt: MouseEvent | KeyboardEvent) { + this.core.selectSuggestion(item, evt); + } +} \ No newline at end of file diff --git a/src/settings/helper.ts b/src/settings/helper.ts new file mode 100644 index 0000000..ce86d21 --- /dev/null +++ b/src/settings/helper.ts @@ -0,0 +1,545 @@ +import { ButtonComponent, Setting, SliderComponent, TAbstractFile, TFile, TFolder, TextComponent, ToggleComponent, MarkdownRenderer, Component } from 'obsidian'; + +import LatexReferencer from 'main'; +import { THEOREM_LIKE_ENV_IDs, THEOREM_LIKE_ENVs, TheoremLikeEnvID } from 'env'; +import { DEFAULT_SETTINGS, ExtraSettings, LEAF_OPTIONS, THEOREM_REF_FORMATS, THEOREM_CALLOUT_STYLES, TheoremCalloutSettings, MathContextSettings, NUMBER_STYLES, FoldOption, DEFAULT_EXTRA_SETTINGS } from 'settings/settings'; +import { formatTheoremCalloutType } from 'utils/format'; +import { NumberKeys, BooleanKeys } from 'utils/general'; +import { DEFAULT_PROFILES, ManageProfileModal } from './profile'; + + +export class TheoremCalloutSettingsHelper { + constructor( + public contentEl: HTMLElement, + public settings: TheoremCalloutSettings, + public defaultSettings: Required & Partial, + public plugin: LatexReferencer, + public file: TFile, + ) { } + + makeSettingPane() { + const { contentEl } = this; + new Setting(contentEl) + .setName("Type") + .addDropdown((dropdown) => { + for (const id of THEOREM_LIKE_ENV_IDs) { + const envName = formatTheoremCalloutType(this.plugin, { type: id, profile: this.defaultSettings.profile }) + dropdown.addOption(id, envName); + if (this.defaultSettings.type) { + dropdown.setValue(String(this.defaultSettings.type)); + } + } + + const initType = dropdown.getValue(); + this.settings.type = initType; + + const numberSetting = new Setting(contentEl).setName("Number"); + const numberSettingDescList = numberSetting.descEl.createEl("ul"); + numberSettingDescList.createEl( + "li", + { text: '"auto" - automatically numbered' } + ); + numberSettingDescList.createEl( + "li", + { text: "blank - unnumbered" } + ); + numberSettingDescList.createEl( + "li", + { text: "otherwise - used as is" } + ); + + numberSetting.addText((text) => { + text.setValue( + this.defaultSettings.number ?? this.defaultSettings.numberDefault + ); + this.settings.number = text.getValue(); + text.onChange((value) => { + this.settings.number = value; + }); + }) + + const titlePane = new Setting(contentEl) + .setName("Title") + .setDesc("You can include inline math in the title."); + + const labelPane = this.plugin.extraSettings.setLabelInModal ? new Setting(contentEl).setName("Pandoc label") : undefined; + const labelPrefixEl = labelPane?.controlEl.createDiv({ + text: THEOREM_LIKE_ENVs[this.settings.type as TheoremLikeEnvID].prefix + ":" + (this.defaultSettings.labelPrefix ?? "") + }); + + titlePane.addText((text) => { + text.inputEl.classList.add("math-booster-title-form"); + if (this.defaultSettings.title) { + text.setValue(this.defaultSettings.title); + } + + let labelTextComp: TextComponent | undefined; + labelPane?.addText((text) => { + labelTextComp = text; + text.inputEl.classList.add("math-booster-label-form"); + if (this.defaultSettings.label) { + text.setValue(this.defaultSettings.label); + } + text.onChange((value) => { + this.settings.label = value; + }); + }); + + text + .setPlaceholder("Ex) $\\sigma$-algebra") + .onChange((value) => { + this.settings.title = value; + if (this.plugin.extraSettings.setLabelInModal) { + let labelInit = this.settings.title.replaceAll(' ', '-').replaceAll("'s", '').toLowerCase(); + labelInit = labelInit.replaceAll(/[^a-z0-1\-]/g, ''); + labelTextComp?.setValue(labelInit); + this.settings.label = labelInit; + } + }) + }); + + dropdown.onChange((value) => { + this.settings.type = value; + if (labelPrefixEl) { + labelPrefixEl.textContent = THEOREM_LIKE_ENVs[this.settings.type as TheoremLikeEnvID].prefix + ":"; + if (this.defaultSettings.labelPrefix) { + labelPrefixEl.textContent += this.defaultSettings.labelPrefix; + } + } + }); + }); + + addFoldOptionSetting(contentEl, 'Collapse', (fold) => { this.settings.fold = fold }, this.defaultSettings.fold ?? this.plugin.extraSettings.foldDefault); + } +} + + +export abstract class SettingsHelper extends Component { + settingRefs: Record; + + constructor( + public contentEl: HTMLElement, + public settings: Partial, + public defaultSettings: Required, + public plugin: LatexReferencer, + public allowUnset: boolean, + public addClear: boolean, + ) { + super(); + this.settingRefs = {} as Record; + } + + addClearButton(name: keyof SettingsType, setting: Setting, additionalCallback: () => void) { + setting.addButton((button) => { + button.setButtonText("Clear").onClick(async () => { + delete this.settings[name]; + additionalCallback(); + }) + }); + } + + addDropdownSetting(name: keyof SettingsType, options: readonly string[], prettyName: string, description?: string, defaultValue?: string, additionalOnChange?: () => void) { + const setting = new Setting(this.contentEl).setName(prettyName); + if (description) { + setting.setDesc(description); + } + setting.addDropdown((dropdown) => { + if (this.allowUnset) { + dropdown.addOption("", ""); + } + for (const option of options) { + dropdown.addOption(option, option); + } + dropdown.setValue( + defaultValue ?? ( + this.allowUnset + ? (this.settings[name] ? this.settings[name] as unknown as string : "") + : (this.settings[name] ?? this.defaultSettings[name]) as unknown as string + ) + ); + dropdown.onChange(async (value: string): Promise => { + if (this.allowUnset && !value) { + delete this.settings[name]; + } else { + Object.assign(this.settings, { [name]: value }); + } + additionalOnChange?.(); + }) + }); + this.settingRefs[name] = setting; + return setting; + } + + addTextSetting(name: keyof SettingsType, prettyName: string, description?: string, number: boolean = false): Setting { + const setting = new Setting(this.contentEl).setName(prettyName); + if (description) { + setting.setDesc(description); + } + let textComponent: TextComponent; + setting.addText((text) => { + textComponent = text; + text.setPlaceholder(String(this.defaultSettings[name] ?? "")) + .setValue(String(this.settings[name] ?? "")) + .onChange((value: string) => { + if (number) { + Object.assign(this.settings, { [name]: +value }); + } else { + Object.assign(this.settings, { [name]: value }); + } + }) + }); + if (this.addClear) { + this.addClearButton(name, setting, () => { + textComponent.setPlaceholder(String(this.defaultSettings[name] ?? "")).setValue("") + }); + } + this.settingRefs[name] = setting; + return setting; + } + + addToggleSetting(name: BooleanKeys, prettyName: string, description?: string, additionalOnChange?: () => void): Setting { + const setting = new Setting(this.contentEl).setName(prettyName); + if (description) { + setting.setDesc(description); + } + let toggleComponent: ToggleComponent; + setting.addToggle((toggle) => { + toggleComponent = toggle; + toggle.setValue(this.defaultSettings[name] as unknown as boolean); + if (typeof this.settings[name] == "boolean") { + toggle.setValue(this.settings[name] as unknown as boolean); + } + toggle.onChange((value) => { + Object.assign(this.settings, { [name]: value }); + additionalOnChange?.(); + }); + }); + if (this.addClear) { + this.addClearButton(name, setting, () => { + toggleComponent.setValue(this.defaultSettings[name] as unknown as boolean) + }); + } + this.settingRefs[name] = setting; + return setting; + } + + addSliderSetting(name: NumberKeys, limits: { min: number, max: number, step: number | 'any' }, prettyName: string, description?: string): Setting { + const setting = new Setting(this.contentEl).setName(prettyName); + if (description) { + setting.setDesc(description); + } + let sliderComponent: SliderComponent; + setting.addSlider((slider) => { + sliderComponent = slider; + slider.setLimits(limits.min, limits.max, limits.step) + .setDynamicTooltip() + .setValue(this.defaultSettings[name] as unknown as number); + if (typeof this.settings[name] == "number") { + slider.setValue(this.settings[name] as unknown as number); + } + slider.onChange((value) => { + Object.assign(this.settings, { [name]: value }); + }) + }); + if (this.addClear) { + this.addClearButton(name, setting, () => { + sliderComponent.setValue(this.defaultSettings[name] as unknown as number); + }); + } + this.settingRefs[name] = setting; + return setting; + } + + addHeading(text: string, cls?: string[]) { + const setting = new Setting(this.contentEl).setName(text).setHeading(); + if (cls) setting.settingEl.classList.add(...cls); + return setting; + } +} + + +export class MathContextSettingsHelper extends SettingsHelper { + constructor( + contentEl: HTMLElement, + settings: Partial, + defaultSettings: Required, + plugin: LatexReferencer, + public file: TAbstractFile, + ) { + const isRoot = file instanceof TFolder && file.isRoot(); + super(contentEl, settings, defaultSettings, plugin, !isRoot, !isRoot); + } + + onload() { + this.addHeading('Theorem callouts - general'); + + this.addProfileSetting(); + const styleSetting = this.addDropdownSetting("theoremCalloutStyle", THEOREM_CALLOUT_STYLES, "Style", undefined, undefined, () => this.plugin.forceRerender()); + styleSetting.descEl.replaceChildren( + "Choose between your custom style and preset styles. You might need to reopen the notes or reload the app to see the changes. See the documentation for how to customize the appearance of theorem callouts. \"Custom\" is recommended, since it will give you the most control. You can view the CSS snippets for all the preset styles in the documentation or README on GitHub. The preset styles are only for a trial purpose, and they might not work well with some non-default themes.", + ); + this.addToggleSetting("theoremCalloutFontInherit", "Don't override the app's font setting when using preset styles", "You will need to reload the note to see the changes."); + this.addTextSetting("titleSuffix", "Title suffix", "Ex) \"\" > Definition 2 (Group) / \".\" > Definition 2 (Group)."); + this.addTextSetting("labelPrefix", "Pandoc label prefix", 'Useful for ensuring no label collision. Ex) When "Pandoc label prefix" = "foo:", A theorem with "Pandoc label" = "bar" is assigned "thm:foo:bar."'); + + this.addHeading('Theorem callouts - numbering'); + + this.addToggleSetting( + "inferNumberPrefix", + "Infer prefix from note title or properties", + `Automatically infer a prefix from the note title or properties. See the documentation (Settings > Prefix inference) for an example.` + ); + this.addTextSetting("inferNumberPrefixFromProperty", "Use property as source", "If set, use this property as the source of prefix inference. If not set, the note title will be used as the source.") + this.addTextSetting("inferNumberPrefixRegExp", "Regular expression for parsing"); + this.addTextSetting("numberPrefix", "Manual prefix", "Even if \"Infer prefix from note title or properties\" is turned on, the inferred prefix will be overwritten by the value set here."); + this.addTextSetting("numberSuffix", "Suffix"); + this.addTextSetting("numberInit", "Initial count"); + this.addDropdownSetting("numberStyle", NUMBER_STYLES, "Style"); + this.addTextSetting("numberDefault", "Default value for the \"Number\" field of \"Insert theorem callout\" modal"); + + this.addHeading('Theorem callouts - referencing'); + + this.addDropdownSetting("refFormat", THEOREM_REF_FORMATS, "Format"); + this.addDropdownSetting( + "noteMathLinkFormat", + THEOREM_REF_FORMATS, + 'Format for a note that has its "main" theorem callout', + `When a theorem callout is set as main by a markdown comment "%% main %%", this format will be used for links to the note containing that theorem callout.` + ); + this.addToggleSetting('ignoreMainTheoremCalloutWithoutTitle', 'Ignore a "main" theorem callout without its own title'); + + this.addHeading('Equations - numbering', ['equation-heading']); + + this.addToggleSetting("numberOnlyReferencedEquations", "Number only referenced equations"); + this.addToggleSetting( + "inferEqNumberPrefix", + "Infer prefix from note title or properties", + `Automatically infer a prefix from the note title or properties. See the documentation (Settings > Prefix inference) for an example.` + ); + this.addTextSetting("inferEqNumberPrefixFromProperty", "Use property as source", "If set, use this property as the source of prefix inference. If not set, the note title will be used as the source.") + this.addTextSetting("inferEqNumberPrefixRegExp", "Regular expression for parsing"); + this.addTextSetting("eqNumberPrefix", "Manual prefix", "Even if \"Infer prefix from note title\" is turned on, the inferred prefix will be overwritten by the value set here."); + this.addTextSetting("eqNumberSuffix", "Suffix"); + this.addTextSetting("eqNumberInit", "Initial count"); + this.addDropdownSetting("eqNumberStyle", NUMBER_STYLES, "Style"); + this.addToggleSetting("lineByLine", "Number line by line in align"); + + this.addHeading('Equations - referencing'); + + this.addTextSetting("eqRefPrefix", "Prefix"); + this.addTextSetting("eqRefSuffix", "Suffix"); + + this.addHeading('Proofs (experimental)', ['proof-heading']); + + this.addTextSetting("beginProof", "Beginning of a proof"); + this.addTextSetting("endProof", "End of a proof"); + + this.addHeading('Search & link auto-completion - general') + .then(async (setting) => { + setting.descEl.addClass('math-booster-new-feature'); + await MarkdownRenderer.render(this.plugin.app, '**NOTE:** If you have the [**Quick Preview**](https://github.com/RyotaUshio/obsidian-quick-preview) plugin installed, holding down `Alt`/`Option` _(by default)_ will trigger a quick preview of the selected suggestion with the context around it.', setting.descEl, '', this); + }) + this.addToggleSetting("insertSpace", "Append whitespace after inserted link"); + } + + addProfileSetting(defaultValue?: string): Setting { + const profileSetting = this.addDropdownSetting("profile", Object.keys(this.plugin.extraSettings.profiles), "Profile", "A profile defines the displayed name of each environment.", defaultValue); + new ButtonComponent(profileSetting.controlEl) + .setButtonText("Manage profiles") + .onClick(() => { + new ManageProfileModal(this.plugin, this, profileSetting).open(); + }); + profileSetting.controlEl.classList.add("math-booster-profile-setting"); + return profileSetting; + } +} + + +export class ExtraSettingsHelper extends SettingsHelper { + onload(): void { + this.settingRefs["foldDefault"] = addFoldOptionSetting( + this.contentEl, 'Default collapsibility when using the "Insert theorem callout" command', (fold) => { + this.settings.foldDefault = fold; + }, this.defaultSettings.foldDefault); + this.addToggleSetting("noteTitleInTheoremLink", "Show the note title at the head of a link to a theorem", "If turned on, a link to \"Theorem 1\" will look like \"Note title > Theorem 1\"."); + this.addToggleSetting("noteTitleInEquationLink", "Show the note title at the head of a link to an equation", "If turned on, a link to \"Eq.(1)\" will look like \"Note title > Eq.(1)\"."); + this.addToggleSetting("excludeExampleCallout", 'Don\'t treat "> [!example]" as a theorem callout', 'If turned on, a callout of the form "> [!example]" will be treated as Obsidian\'s built-in "Example" callout, and you will need to type "> [!exm]" instead to insert a theorem callout of "Example" type.'); + this.addToggleSetting("showTheoremCalloutEditButton", "Show an edit button on a theorem callout"); + this.addToggleSetting("setOnlyTheoremAsMain", "If a note has only one theorem callout, automatically set it as main", 'Regardless of this setting, putting "%% main %%" or "%% main: true %%" in a theorem callout will set it as main one of the note, which means any link to that note will be displayed with the theorem\'s title. Enabling this option implicitly sets a theorem callout as main when it\'s the only one in the note.'); + this.addToggleSetting("setLabelInModal", "Show LaTeX/Pandoc label input form in theorem callout insert/edit modal"); + this.addToggleSetting("enableProof", "Enable proof environment", `For example, you can replace a pair of inline codes \`${DEFAULT_SETTINGS.beginProof}\` & \`${DEFAULT_SETTINGS.endProof}\` with \"${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.begin}\" & \"${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.end}\". You can style it with CSS snippets. See the documentation for the details.`, () => this.plugin.updateEditorExtensions()); + + // Suggest + + this.addSliderSetting("suggestNumber", { min: 1, max: 50, step: 1 }, "Number of suggestions", "Specify how many items are suggested at one time. Set it to a smaller value if you have a performance issue when equation suggestions with math rendering on."); + this.addToggleSetting("renderMathInSuggestion", "Render math in equation suggestions", "Turn this off if you have a performance issue and reducing the number of suggestions doesn't fix it."); + this.addDropdownSetting("searchMethod", ["Fuzzy", "Simple"], "Search method", "Fuzzy search is more flexible, but simple search is lighter-weight."); + this.addToggleSetting("searchLabel", "Include theorem callout label for search target"); + this.addSliderSetting("upWeightRecent", { min: 0, max: 0.5, step: 0.01 }, "Up-weight recently opened notes by", "It takes effect only if \"Search only recently opened notes\" is turned off."); + this.addDropdownSetting("modifierToJump", ['Mod', 'Ctrl', 'Meta', 'Shift', 'Alt'], "Modifier key for jumping to suggestion", "Press Enter and this modifier key to jump to the currently selected suggestion. Changing this option requires to reloading " + this.plugin.manifest.name + " to take effect."); + this.addDropdownSetting("modifierToNoteLink", ['Mod', 'Ctrl', 'Meta', 'Shift', 'Alt'], "Modifier key for insert link to note", "Press Enter and this modifier key to insert a link to the note containing the currently selected item. Changing this option requires to reloading " + this.plugin.manifest.name + " to take effect."); + this.addToggleSetting("showModifierInstruction", "Show modifier key instruction", "Show the instruction for the modifier key at the bottom of suggestion box. " + `Changing this option requires to reloading ${this.plugin.manifest.name} to take effect.`); + const list = this.settingRefs.modifierToJump.descEl.createEl("ul"); + list.createEl("li", { text: "Mod is Cmd on MacOS and Ctrl on other OS." }); + list.createEl("li", { text: "Meta is Cmd on MacOS and Win key on Windows." }); + this.addDropdownSetting("suggestLeafOption", LEAF_OPTIONS, "Opening option", "Specify how to open the selected suggestion.") + + this.addHeading('Enhance Obsidian\'s built-in link auto-completion (experimental)') + .setDesc('Configure how this plugin modifies the appearance of Obsidian\'s built-in link auto-completion (the one that pops up when you type "[["). This feature dives deep into Obsidian\'s internals, so it might break when Obsidian is updated. If you encounter any issue, please report it on GitHub.'); + this.addToggleSetting("showTheoremTitleinBuiltin", "Show theorem title"); + this.addToggleSetting("showTheoremContentinBuiltin", "Show theorem content", "Only effective when \"Show theorem title\" is turned on."); + + this.addHeading('Configure this plugin\'s custom editor link auto-completion') + .setDesc(`It is recommended to turn off unnecessary auto-completions to improve performance.`); + + this.addTextSetting("autocompleteDvQuery", "Dataview query for editor link auto-completion", "Only LIST queries are supported."); + + this.addHeading('Theorem & equation suggestion') + this.addHeading("From entire vault", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableSuggest", "Enable"); + this.addTextSetting("triggerSuggest", "Trigger"); + this.addHeading("From recently opened notes", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableSuggestRecentNotes", "Enable"); + this.addTextSetting("triggerSuggestRecentNotes", "Trigger"); + this.addHeading("From active note", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableSuggestActiveNote", "Enable"); + this.addTextSetting("triggerSuggestActiveNote", "Trigger"); + this.addHeading("From Dataview query", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableSuggestDataview", "Enable"); + this.addTextSetting("triggerSuggestDataview", "Trigger"); + + this.addHeading('Theorem suggestion', ['editor-suggest-setting-heading']); + this.addHeading("From entire vault", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableTheoremSuggest", "Enable"); + this.addTextSetting("triggerTheoremSuggest", "Trigger"); + this.addHeading("From recently opened notes", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableTheoremSuggestRecentNotes", "Enable"); + this.addTextSetting("triggerTheoremSuggestRecentNotes", "Trigger"); + this.addHeading("From active note", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableTheoremSuggestActiveNote", "Enable"); + this.addTextSetting("triggerTheoremSuggestActiveNote", "Trigger"); + this.addHeading("From Dataview query", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableTheoremSuggestDataview", "Enable"); + this.addTextSetting("triggerTheoremSuggestDataview", "Trigger"); + + this.addHeading('Equation suggestion', ['editor-suggest-setting-heading']) + this.addHeading("From entire vault", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableEquationSuggest", "Enable"); + this.addTextSetting("triggerEquationSuggest", "Trigger"); + this.addHeading("From recently opened notes", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableEquationSuggestRecentNotes", "Enable"); + this.addTextSetting("triggerEquationSuggestRecentNotes", "Trigger"); + this.addHeading("From active note", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableEquationSuggestActiveNote", "Enable"); + this.addTextSetting("triggerEquationSuggestActiveNote", "Trigger"); + this.addHeading("From Dataview query", ['editor-suggest-setting-indented-heading']); + this.addToggleSetting("enableEquationSuggestDataview", "Enable"); + this.addTextSetting("triggerEquationSuggestDataview", "Trigger"); + + // projects + // this.addTextSetting("projectInfix", "Link infix", "Specify the infix to connect a project name and a theorem title or an equation number."); + // this.addTextSetting("projectSep", "Separator for nested projects"); + + // indexer/importer + // this.contentEl.createEl("h3", { text: "Indexing" }); + this.addHeading('Indexing'); + + this.addSliderSetting('importerNumThreads', { min: 1, max: 10, step: 1 }, "Indexer threads", "The maximum number of thread used for indexing."); + this.addSliderSetting('importerUtilization', { min: 0.1, max: 1.0, step: 0.01 }, 'Indexer CPU utilization', "The CPU utilization that indexer threads should use."); + } +} + + +// export class ProjectSettingsHelper { +// plugin: LatexReferencer; +// file: TAbstractFile; + +// constructor(public contentEl: HTMLElement, public parent: ContextSettingModal) { +// this.plugin = parent.plugin; +// this.file = parent.file; +// } + +// makeSettingPane() { +// const project = this.plugin.projectManager.getProject(this.file); +// const noteOrFolder = this.file instanceof TFile ? "note" : "folder"; +// let status = ""; +// if (project) { +// if (project.root == this.file) { +// status = `This ${noteOrFolder} is a project's root.`; +// } else { +// status = `This ${noteOrFolder} belongs to the project "${project.name}" (root: ${project.root.path}).`; +// } +// } else { +// status = `This ${noteOrFolder} doesn't belong to any project.`; +// } + +// this.contentEl.createEl("h4", {text: "Project (experimental)"}) + +// this.contentEl.createDiv({ +// text: PROJECT_DESCRIPTION + " " + status, +// cls: ["setting-item-description", "math-booster-setting-item-description"] +// }); +// this.addRootSetting(); +// if (project) { +// this.addNameSetting(project); +// } +// } + +// addRootSetting(): Setting | undefined { +// const prettyName = "Set as project root"; +// const description = this.file instanceof TFile +// ? "If turned on, this file itself will be treated as a project." +// : "If turned on, all the files under this folder will be treated as a single project."; +// let index: AbstractFileIndex | undefined +// if (this.file instanceof TFile) { +// index = this.plugin.index.getNoteIndex(this.file) +// } else if (this.file instanceof TFolder) { +// index = this.plugin.index.getFolderIndex(this.file) +// } +// if (index) { +// const setting = new Setting(this.contentEl).setName(prettyName).setDesc(description); +// setting.addToggle((toggle) => { +// toggle.setValue(index!.isProjectRoot) +// .onChange((value) => { +// if (value) { +// this.plugin.projectManager.add(this.file); +// } else { +// this.plugin.projectManager.delete(this.file); +// } +// this.parent.close(); +// this.parent.open(); +// }); +// }); +// return setting; +// } +// } + +// addNameSetting(project: Project): Setting { +// const prettyName = "Project name"; +// const description = "A project name can contain inline math and doesn't have to be unique."; +// const setting = new Setting(this.contentEl).setName(prettyName).setDesc(description); + +// setting.addText((text) => { +// text.setValue(project.name) +// .onChange((value) => { +// project.name = value; +// }) +// }); +// return setting; +// } +// } + + +function addFoldOptionSetting(el: HTMLElement, name: string, onChange: (fold: FoldOption) => any, defaultValue?: FoldOption) { + return new Setting(el) + .setName(name) + .addDropdown((dropdown) => { + dropdown.addOption('', 'Unfoldable'); + dropdown.addOption('+', 'Foldable & expanded by default'); + dropdown.addOption('-', 'Foldable & folded by default'); + + dropdown.setValue(defaultValue ?? DEFAULT_EXTRA_SETTINGS.foldDefault); + + dropdown.onChange(onChange) + }); +} \ No newline at end of file diff --git a/src/settings/modals.ts b/src/settings/modals.ts new file mode 100644 index 0000000..56c58a1 --- /dev/null +++ b/src/settings/modals.ts @@ -0,0 +1,292 @@ +import { TAbstractFile, TFile, App, Modal, Setting, FuzzySuggestModal, TFolder, Component } from 'obsidian'; + +import LatexReferencer from 'main'; +import { MathSettings, MathContextSettings, DEFAULT_SETTINGS, MinimalTheoremCalloutSettings } from 'settings/settings'; +import { MathSettingTab } from "settings/tab"; +import { TheoremCalloutSettingsHelper, MathContextSettingsHelper } from "settings/helper"; +import { isEqualToOrChildOf } from 'utils/obsidian'; +import { resolveSettings } from 'utils/plugin'; + + +abstract class MathSettingModal extends Modal { + settings: SettingsType; + + constructor( + app: App, + public plugin: LatexReferencer, + public callback?: (settings: SettingsType) => void, + ) { + super(app); + } + + onClose(): void { + this.contentEl.empty(); + } + + addButton(buttonText: string) { + const { contentEl } = this; + new Setting(contentEl) + .addButton((btn) => { + btn.setButtonText(buttonText) + .setCta() + .onClick(() => { + this.close(); + if (this.callback) { + this.callback(this.settings); + } + }); + btn.buttonEl.classList.add("insert-math-item-button"); + }); + + const button = contentEl.querySelector(".insert-math-item-button"); + const settingTextboxes = contentEl.querySelectorAll("input"); + if (button) { + settingTextboxes.forEach((textbox) => { + /** + * 'keypress' is deprecated, but here we need to use it. + * 'keydown' causes a serious inconvenience at least for Japanese users. + * See https://qiita.com/ledsun/items/31e43a97413dd3c8e38e + */ + this.plugin.registerDomEvent(textbox, "keypress", (event) => { + if (event.key === "Enter") { + (button as HTMLElement).click(); + } + }); + }); + } + } +} + + +export class TheoremCalloutModal extends MathSettingModal { + defaultSettings: Required; + + constructor( + app: App, + plugin: LatexReferencer, + public file: TFile, + callback: (settings: MathSettings) => void, + public buttonText: string, + public headerText: string, + public currentCalloutSettings?: MinimalTheoremCalloutSettings, + ) { + super(app, plugin, callback); + } + + resolveDefaultSettings(currentFile: TAbstractFile) { + // The if statement is redundant, but probably necessary for the Typescript compiler to work + if (this.currentCalloutSettings === undefined) { + this.defaultSettings = resolveSettings(this.currentCalloutSettings, this.plugin, currentFile) + } else { + this.defaultSettings = resolveSettings(this.currentCalloutSettings, this.plugin, currentFile) + } + } + + onOpen(): void { + this.settings = this.currentCalloutSettings ?? {} as MathSettings; + const { contentEl } = this; + contentEl.empty(); + + this.resolveDefaultSettings(this.file); + + if (this.headerText) { + // contentEl.createEl("h4", { text: this.headerText }); + this.titleEl.setText(this.headerText); + } + + const helper = new TheoremCalloutSettingsHelper(contentEl, this.settings, this.defaultSettings, this.plugin, this.file); + helper.makeSettingPane(); + + new Setting(contentEl) + .setName('Open local settings for the current note') + .addButton((button) => { + button.setButtonText("Open") + .onClick(() => { + const modal = new ContextSettingModal( + this.app, + this.plugin, + this.file, + undefined, + this + ); + modal.open(); + }) + }); + + this.addButton(this.buttonText); + } +} + + +export class ContextSettingModal extends MathSettingModal { + component: Component; + + constructor( + app: App, + plugin: LatexReferencer, + public file: TAbstractFile, + callback?: (settings: MathContextSettings) => void, + public parent?: TheoremCalloutModal | undefined + ) { + super(app, plugin, callback); + this.component = new Component(); + } + + onOpen(): void { + const { contentEl } = this; + contentEl.empty(); + this.component.load(); + + // contentEl.createEl('h4', { text: 'Local settings for ' + this.file.path }); + this.titleEl.setText('Local settings for ' + this.file.path); + + contentEl.createDiv({ + text: "If you want the change to apply to the entire vault, go to the plugin settings.", + cls: ["setting-item-description", "math-booster-setting-item-description"], + }); + + if (this.plugin.settings[this.file.path] === undefined) { + this.plugin.settings[this.file.path] = {} as MathContextSettings; + } + + const defaultSettings = this.file.parent ? resolveSettings(undefined, this.plugin, this.file.parent) : DEFAULT_SETTINGS; + + const contextSettingsHelper = new MathContextSettingsHelper(contentEl, this.plugin.settings[this.file.path], defaultSettings, this.plugin, this.file); + this.component.addChild(contextSettingsHelper); + + // if (!(this.file instanceof TFolder && this.file.isRoot())) { + // new ProjectSettingsHelper(contentEl, this).makeSettingPane(); + // } + + this.addButton('Save'); + } + + onClose(): void { + super.onClose(); + + this.plugin.saveSettings(); + this.plugin.indexManager.trigger('local-settings-updated', this.file); + + if (this.parent) { + this.parent.open(); + } + + this.component.unload(); + } +} + + +abstract class FileSuggestModal extends FuzzySuggestModal { + + constructor(app: App, public plugin: LatexReferencer) { + super(app); + } + + getItems(): TAbstractFile[] { + return this.app.vault + .getAllLoadedFiles() + .filter(this.filterCallback.bind(this)); + } + + getItemText(file: TAbstractFile): string { + return file.path; + } + + filterCallback(abstractFile: TAbstractFile): boolean { + if (abstractFile instanceof TFile && abstractFile.extension != 'md') { + return false; + } + if (abstractFile instanceof TFolder && abstractFile.isRoot()) { + return false; + } + for (const path of this.plugin.excludedFiles) { + const file = this.app.vault.getAbstractFileByPath(path) + if (file && isEqualToOrChildOf(abstractFile, file)) { + return false + } + } + return true; + } +} + + + +export class LocalContextSettingsSuggestModal extends FileSuggestModal { + constructor(app: App, plugin: LatexReferencer, public settingTab: MathSettingTab) { + super(app, plugin); + } + + onChooseItem(file: TAbstractFile, evt: MouseEvent | KeyboardEvent) { + const modal = new ContextSettingModal(this.app, this.plugin, file); + modal.open(); + } +} + + + +export class FileExcludeSuggestModal extends FileSuggestModal { + constructor(app: App, plugin: LatexReferencer, public manageModal: ExcludedFileManageModal) { + super(app, plugin); + } + + onChooseItem(file: TAbstractFile, evt: MouseEvent | KeyboardEvent) { + this.plugin.excludedFiles.push(file.path); + this.manageModal.newDisplay(); + } + + filterCallback(abstractFile: TAbstractFile): boolean { + for (const path in this.plugin.settings) { + if (path == abstractFile.path) { + return false; + } + } + return super.filterCallback(abstractFile); + } +} + + +export class ExcludedFileManageModal extends Modal { + constructor(app: App, public plugin: LatexReferencer) { + super(app); + } + + onOpen() { + this.newDisplay(); + } + + async newDisplay() { + await this.plugin.saveSettings(); + const { contentEl } = this; + contentEl.empty(); + contentEl.createEl('h3', { text: 'Excluded files/folders' }); + + new Setting(contentEl) + .setName('The files/folders in this list and their descendants will be excluded from suggestion for local settings.') + .addButton((btn) => { + btn.setIcon("plus") + .onClick((event) => { + new FileExcludeSuggestModal(this.app, this.plugin, this).open(); + }); + }); + + if (this.plugin.excludedFiles.length) { + const list = contentEl.createEl('ul'); + for (const path of this.plugin.excludedFiles) { + const item = list.createEl('li').createDiv(); + new Setting(item).setName(path).addExtraButton((btn) => { + btn.setIcon('x').onClick(async () => { + this.plugin.excludedFiles.remove(path); + this.newDisplay(); + await this.plugin.saveSettings(); + }); + }); + } + } + + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} diff --git a/src/settings/profile.ts b/src/settings/profile.ts new file mode 100644 index 0000000..bf60a13 --- /dev/null +++ b/src/settings/profile.ts @@ -0,0 +1,446 @@ +import { ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian'; + +import LatexReferencer, { VAULT_ROOT } from '../main'; +import { THEOREM_LIKE_ENV_IDs, TheoremLikeEnvID } from '../env'; +import { MathContextSettingsHelper } from '../settings/helper'; +import { DEFAULT_SETTINGS } from './settings'; + + +export type ProfileMeta = { tags: string[] }; +export type TheoremLinkEnvDisplay = { [k in TheoremLikeEnvID]: string }; +export const PROOF_SETTING_KEYS = [ + "begin", + "end", + "linkedBeginPrefix", + "linkedBeginSuffix", +] as const; +export type ProofSettingKey = typeof PROOF_SETTING_KEYS[number]; +export type ProofDisplay = { [k in ProofSettingKey]: string }; +export type ProfileBody = { + theorem: TheoremLinkEnvDisplay; + proof: ProofDisplay; +}; +export type Profile = { + id: string; + meta: ProfileMeta; + body: ProfileBody; +}; + +export const DEFAULT_PROFILES: Record = { + "English": { + id: "English", + meta: { + tags: ["en"], + }, + body: { + theorem: { + "axiom": "Axiom", + "definition": "Definition", + "lemma": "Lemma", + "proposition": "Proposition", + "theorem": "Theorem", + "corollary": "Corollary", + "claim": "Claim", + "assumption": "Assumption", + "example": "Example", + "exercise": "Exercise", + "conjecture": "Conjecture", + "hypothesis": "Hypothesis", + "remark": "Remark", + }, + proof: { + begin: "Proof.", + end: "□", + linkedBeginPrefix: "Proof of ", + linkedBeginSuffix: ".", + }, + }, + }, + "日本語": { + id: "日本語", + meta: { + tags: ["ja"], + }, + body: { + theorem: { + "axiom": "公理", + "definition": "定義", + "lemma": "補題", + "proposition": "命題", + "theorem": "定理", + "corollary": "系", + "claim": "主張", + "assumption": "仮定", + "example": "例", + "exercise": "演習問題", + "conjecture": "予想", + "hypothesis": "仮説", + "remark": "注", + }, + proof: { + begin: "証明.", + end: "□", + linkedBeginPrefix: "", + linkedBeginSuffix: "の証明.", + }, + }, + }, +}; + + +export class ManageProfileModal extends Modal { + constructor(public plugin: LatexReferencer, public helper: MathContextSettingsHelper, public profileSetting: Setting) { + super(plugin.app); + } + + onOpen() { + let { contentEl } = this; + + contentEl.empty(); + // contentEl.createEl("h4", { text: "Manage profiles" }); + this.titleEl.setText("Manage profiles"); + + new Setting(contentEl) + .setName("Add profile") + .addButton((button) => { + button.setIcon("plus").onClick(() => { + new AddProfileModal(this).open() + }); + }); + + for (const id in this.plugin.extraSettings.profiles) { + new Setting(contentEl) + .setName(id) + .addButton((editButton) => { + editButton.setIcon("pencil") + .setTooltip("Edit") + .setCta() + .onClick(() => { + new EditProfileModal( + this.plugin.extraSettings.profiles[id], + this + ).open(); + }); + }).addButton((editButton) => { + editButton.setIcon("copy") + .setTooltip("Copy") + .onClick(() => { + const copied = JSON.parse(JSON.stringify(this.plugin.extraSettings.profiles[id])); + copied.id = makeIdOfCopy(id, this.plugin.extraSettings.profiles); + this.plugin.extraSettings.profiles[copied.id] = copied; + this.open(); + }); + }).addButton((deleteButton) => { + deleteButton.setIcon("trash-2") + .setTooltip("Delete") + .onClick(() => { + new ConfirmProfileDeletionModal(id, this).open(); + }); + }); + } + } + + async onClose() { + let { contentEl } = this; + contentEl.empty(); + + await this.plugin.saveSettings(); + this.plugin.indexManager.trigger('global-settings-updated'); + + this.profileSetting.settingEl.replaceWith( + this.helper.addProfileSetting( + this.plugin.settings[this.helper.file.path].profile + ).settingEl + ); + } +} + + +class EditProfileModal extends Modal { + settingRefs: Record; + constructor(public profile: Profile, public parent: ManageProfileModal) { + super(parent.app); + this.settingRefs = {} as Record; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + // contentEl.createEl("h4", { text: `Edit profile` }); + this.titleEl.setText(`Edit profile`); + + new Setting(contentEl) + .setName("Name") + .addText((text) => { + text.setValue(this.profile.id) + .onChange((value) => { + this.profile.id = value; + }); + }); + + new Setting(contentEl) + .setName("Tags") + .setDesc("Comma-separated list of tags. Only lower-case alphabets or hyphens are allowed. Each tag is converted into a CSS class \".theorem-callout-\".") + .addText((text) => { + text.setValue(this.profile.meta.tags.join(", ")) + .onChange((value) => { + const tags = value.split(",").map((item) => item.trim()); + if (tags.every((tag) => tag.match(/^[a-z\-]+$/) ?? !tag)) { + this.profile.meta.tags = tags; + } else { + new Notice("A tag can only contain lower-case alphabets or hyphens.", 5000); + } + }); + }); + + // contentEl.createEl("h5", { text: "Theorem-like environments" }); + new Setting(contentEl).setName("Theorem-like environments").setHeading(); + + for (const envID of THEOREM_LIKE_ENV_IDs) { + this.settingRefs[envID] = new Setting(contentEl).setName(envID).addText((text) => { + text.setValue(this.profile.body.theorem[envID] ?? "") + .onChange((value) => { + this.profile.body.theorem[envID] = value; + }) + }); + } + + // contentEl.createEl("h5", { text: "Proofs" }); + new Setting(contentEl).setName("Proofs").setHeading(); + + const prettyNames = [ + "Beginning of proof", + "Ending of proof", + "Prefix", + "Suffix", + ]; + for (let i = 0; i < PROOF_SETTING_KEYS.length; i++) { + const key = PROOF_SETTING_KEYS[i]; + const name = prettyNames[i]; + this.settingRefs[key] = new Setting(contentEl).setName(name).addText((text) => { + text.setValue(this.profile.body.proof[key] ?? "") + .onChange((value) => { + this.profile.body.proof[key] = value; + }) + }); + } + + // const linkedProofHeading = contentEl.createEl("h6", {text: "Linked proofs"}); + const linkedProofHeading = new Setting(contentEl) + .setName("Linked proofs") + .setDesc(`For example, you can render \`${DEFAULT_SETTINGS.beginProof}\`@[[link to Theorem 1]] as "${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.linkedBeginPrefix}Theorem 1${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.linkedBeginSuffix}".`) + .setHeading().settingEl; + // const linkedProofDesc = contentEl.createDiv({ + // text: `For example, you can render \`${DEFAULT_SETTINGS.beginProof}\`@[[link to Theorem 1]] as "${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.linkedBeginPrefix}Theorem 1${DEFAULT_PROFILES[DEFAULT_SETTINGS.profile].body.proof.linkedBeginSuffix}".`, + // cls: ["setting-item-description", "math-booster-setting-item-description"], + // }); + contentEl.insertBefore(linkedProofHeading, this.settingRefs.linkedBeginPrefix.settingEl); + // contentEl.insertBefore(linkedProofDesc, this.settingRefs.linkedBeginPrefix.settingEl); + } + + onClose() { + const profiles = this.parent.plugin.extraSettings.profiles; + for (const oldID in profiles) { + const newID = profiles[oldID].id; + if (newID != oldID) { + profiles[newID] = profiles[oldID]; + delete profiles[oldID]; + const affected = getAffectedFiles(this.parent.plugin, oldID); + updateProfile(this.parent.plugin, affected, newID); + } + } + + let { contentEl } = this; + contentEl.empty(); + this.parent.open(); + } +} + + +class ConfirmProfileDeletionModal extends Modal { + constructor(public id: string, public parent: ManageProfileModal) { + super(parent.app); + } + + onOpen() { + let { contentEl } = this; + contentEl.empty(); + + // contentEl.createEl("h3", { text: "Delete profile" }); + this.titleEl.setText("Delete profile"); + + contentEl.createDiv({ text: `Are you sure you want to delete the profile "${this.id}"?` }); + const buttonContainerEl = contentEl.createDiv({ cls: "math-booster-button-container" }); + new ButtonComponent(buttonContainerEl) + .setButtonText("Delete") + .setCta() + .onClick(() => { + const affected = getAffectedFiles(this.parent.plugin, this.id); + if (affected.length) { + new UpdateProfileModal(this, this.id, affected).open(); + } else { + delete this.parent.plugin.extraSettings.profiles[this.id]; + this.close(); + } + }); + new ButtonComponent(buttonContainerEl) + .setButtonText("Cancel") + .onClick(() => { + this.close(); + }); + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + this.parent.open(); + } +} + + +class AddProfileModal extends Modal { + constructor(public parent: ManageProfileModal) { + super(parent.app); + } + + onOpen() { + let { contentEl } = this; + contentEl.empty(); + + let id: string; + + // contentEl.createEl("h3", { text: "Add profile" }); + this.titleEl.setText("Add profile"); + + const addProfileEl = contentEl.createDiv({ cls: "math-booster-add-profile" }); + + new TextComponent(addProfileEl) + .setPlaceholder("Enter name...") + .onChange((value) => { + id = value; + }); + + const buttonContainerEl = addProfileEl.createDiv({ cls: "math-booster-button-container" }); + new ButtonComponent(buttonContainerEl) + .setButtonText("Add") + .setCta() + .onClick(() => { + const newBody = {theorem: {}} as ProfileBody; + for (const envID of THEOREM_LIKE_ENV_IDs) { + newBody.theorem[envID] = ""; + } + this.parent.plugin.extraSettings.profiles[id] = { + id, + meta: { tags: [] }, + body: newBody + }; + new EditProfileModal(this.parent.plugin.extraSettings.profiles[id], this.parent).open(); + this.close(); + }); + new ButtonComponent(buttonContainerEl) + .setButtonText("Cancel") + .onClick(() => { + this.close(); + }); + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + this.parent.open(); + } +} + + +class UpdateProfileModal extends Modal { + constructor(public parent: ConfirmProfileDeletionModal, public deletedID: string, public affected: string[]) { + super(parent.app); + } + + onOpen() { + let { contentEl } = this; + contentEl.empty(); + + // contentEl.createEl("h3", { text: "Update profiles" }); + this.titleEl.setText("Update profiles"); + + contentEl.createDiv({ text: `The following ${this.affected.length > 1 ? this.affected.length : ""} local setting${this.affected.length > 1 ? "s are" : " is"} affected by the deletion of profile "${this.deletedID}." Select a new profile to be applied for them.` }); + + const profiles = this.parent.parent.plugin.extraSettings.profiles; + const ids = Object.keys(profiles); + let newProfileID: string | undefined; + + const buttonContainerEl = contentEl.createDiv({ cls: "math-booster-button-container" }); + const dropdown = new DropdownComponent(buttonContainerEl); + dropdown.addOption("", ""); + for (const id of ids) { + if (id != this.deletedID) { + dropdown.addOption(id, id); + } + } + newProfileID = dropdown.getValue(); + dropdown.onChange((value) => { + newProfileID = value; + }); + new ButtonComponent(buttonContainerEl) + .setButtonText("Confirm") + .setCta() + .onClick(async () => { + updateProfile(this.parent.parent.plugin, this.affected, newProfileID); + this.close(); + }) + new ButtonComponent(buttonContainerEl) + .setButtonText("Cancel") + .onClick(() => { + this.close(); + }); + + const listEl = contentEl.createEl("ul"); + for (const path of this.affected) { + listEl.createEl("li", { text: path == VAULT_ROOT ? "(Vault root)" : path }); + } + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + delete this.parent.parent.plugin.extraSettings.profiles[this.parent.id]; + this.parent.close(); + } +} + + +function getAffectedFiles(plugin: LatexReferencer, oldProfileId: string) { + const affected: string[] = []; + for (const path in plugin.settings) { + const localSettings = plugin.settings[path]; + if (localSettings.profile == oldProfileId) { + affected.push(path); + } + } + return affected; +} + + +function makeIdOfCopy(oldID: string, profiles: Record) { + let newId = `Copy of ${oldID}`; + if (!(newId in profiles)) { + return newId; + } + const ids = Object.keys(profiles); + const numbers: number[] = ids.map((id) => id.slice(oldID.length + 1).match(/\(([1-9][0-9]*)\)/)?.[1] ?? "0").map((numStr: string): number => +numStr); + const max = Math.max(...numbers); + return `${newId} (${max + 1})`; +} + + +function updateProfile(plugin: LatexReferencer, paths: string[], newID?: string) { + for (const path of paths) { + const localSettings = plugin.settings[path]; + if (newID) { + localSettings.profile = newID; + } else { + delete localSettings.profile; + } + } +} diff --git a/src/settings/settings.ts b/src/settings/settings.ts new file mode 100644 index 0000000..663d50c --- /dev/null +++ b/src/settings/settings.ts @@ -0,0 +1,277 @@ +import { Modifier } from "obsidian"; + +import { DEFAULT_PROFILES, Profile } from "./profile"; +import { LeafArgs } from "../typings/type"; +import { QueryType, SearchRange } from "search/core"; + +// Types + +export const NUMBER_STYLES = [ + "arabic", + "alph", + "Alph", + "roman", + "Roman" +] as const; +export type NumberStyle = typeof NUMBER_STYLES[number]; + +export const THEOREM_CALLOUT_STYLES = [ + "Custom", + "Plain", + "Framed", + "MathWiki", + "Vivid", +] as const; +export type TheoremCalloutStyle = typeof THEOREM_CALLOUT_STYLES[number]; + +export const THEOREM_REF_FORMATS = [ + "[type] [number] ([title])", + "[type] [number]", + "[title] ([type] [number]) if title exists, [type] [number] otherwise", + "[title] if title exists, [type] [number] otherwise", +] as const; +export type TheoremRefFormat = typeof THEOREM_REF_FORMATS[number]; + +export const LEAF_OPTIONS = [ + "Current tab", + "Split right", + "Split down", + "New tab", + "New window", +] as const; +export type LeafOption = typeof LEAF_OPTIONS[number]; +export const LEAF_OPTION_TO_ARGS: Record = { + "Current tab": [false], + "Split right": ["split", "vertical"], + "Split down": ["split", "horizontal"], + "New tab": ["tab"], + "New window": ["window"], +} + +export const SEARCH_METHODS = [ + "Fuzzy", + "Simple" +] as const; +export type SearchMethod = typeof SEARCH_METHODS[number]; + + +// Context settings are called "local settings" in the documentation and UI. +// Sorry for confusion, this is due to a historical reason. I'll fix it later. +export interface MathContextSettings { + profile: string; + titleSuffix: string; + inferNumberPrefix: boolean; + inferNumberPrefixFromProperty: string; + inferNumberPrefixRegExp: string; + numberPrefix: string; + numberSuffix: string; + numberInit: number; + numberStyle: NumberStyle; + numberDefault: string; + refFormat: TheoremRefFormat; + noteMathLinkFormat: TheoremRefFormat; + ignoreMainTheoremCalloutWithoutTitle: boolean; + numberOnlyReferencedEquations: boolean; + inferEqNumberPrefix: boolean; + inferEqNumberPrefixFromProperty: string; + inferEqNumberPrefixRegExp: string; + eqNumberPrefix: string; + eqNumberSuffix: string; + eqNumberInit: number; + eqNumberStyle: NumberStyle; + eqRefPrefix: string; + eqRefSuffix: string; + labelPrefix: string; + lineByLine: boolean; + theoremCalloutStyle: TheoremCalloutStyle; + theoremCalloutFontInherit: boolean; + beginProof: string; + endProof: string; + insertSpace: boolean; +} + +export const UNION_TYPE_MATH_CONTEXT_SETTING_KEYS: {[k in keyof Partial]: readonly string[]} = { + "numberStyle": NUMBER_STYLES, + "refFormat": THEOREM_REF_FORMATS, + "noteMathLinkFormat": THEOREM_REF_FORMATS, + "eqNumberStyle": NUMBER_STYLES, + "theoremCalloutStyle": THEOREM_CALLOUT_STYLES, +}; + +export type FoldOption = '' | '+' | '-'; + +export type MinimalTheoremCalloutSettings = { + type: string; + number: string; + title?: string; + fold?: FoldOption; +} + +export type TheoremCalloutSettings = MinimalTheoremCalloutSettings & { + // type: string; + // number: string; + // title?: string; + label?: string; + // setAsNoteMathLink: boolean; +} + +// legacy interface; currently only used temporarily when computing formatted theorem titles +// TODO: refactor +export interface TheoremCalloutPrivateFields { + _index?: number; +} + +export interface ImporterSettings { + importerNumThreads: number; + importerUtilization: number; +} + +export type ExtraSettings = ImporterSettings & { + foldDefault: FoldOption; + noteTitleInTheoremLink: boolean; + noteTitleInEquationLink: boolean; + profiles: Record; + showTheoremTitleinBuiltin: boolean; + showTheoremContentinBuiltin: boolean; + triggerSuggest: string; + triggerTheoremSuggest: string; + triggerEquationSuggest: string; + triggerSuggestActiveNote: string; + triggerTheoremSuggestActiveNote: string; + triggerEquationSuggestActiveNote: string; + triggerSuggestRecentNotes: string; + triggerTheoremSuggestRecentNotes: string; + triggerEquationSuggestRecentNotes: string; + triggerSuggestDataview: string; + triggerTheoremSuggestDataview: string; + triggerEquationSuggestDataview: string; + enableSuggest: boolean; + enableTheoremSuggest: boolean; + enableEquationSuggest: boolean; + enableSuggestActiveNote: boolean; + enableTheoremSuggestActiveNote: boolean; + enableEquationSuggestActiveNote: boolean; + enableSuggestRecentNotes: boolean; + enableTheoremSuggestRecentNotes: boolean; + enableEquationSuggestRecentNotes: boolean; + enableSuggestDataview: boolean; + enableTheoremSuggestDataview: boolean; + enableEquationSuggestDataview: boolean; + renderMathInSuggestion: boolean; + suggestNumber: number; + searchMethod: SearchMethod; + upWeightRecent: number; + searchLabel: boolean; + modifierToJump: Modifier; + modifierToNoteLink: Modifier; + showModifierInstruction: boolean; + suggestLeafOption: LeafOption; + // projectInfix: string; + // projectSep: string; + showTheoremCalloutEditButton: boolean; + setOnlyTheoremAsMain: boolean; + setLabelInModal: boolean; + excludeExampleCallout: boolean; + enableProof: boolean; + autocompleteDvQuery: string; + // searchModal*: not congigurable from the setting tab, just remenbers the last state + searchModalQueryType: QueryType; + searchModalRange: SearchRange; + searchModalDvQuery: string; +} + +export const UNION_TYPE_EXTRA_SETTING_KEYS: {[k in keyof Partial]: readonly string[]} = { + "searchMethod": SEARCH_METHODS, + "suggestLeafOption": LEAF_OPTIONS, +}; + +export type MathSettings = Partial & TheoremCalloutSettings & TheoremCalloutPrivateFields; +export type ResolvedMathSettings = Required & TheoremCalloutSettings & TheoremCalloutPrivateFields; + +export const DEFAULT_SETTINGS: Required = { + profile: Object.keys(DEFAULT_PROFILES)[0], + titleSuffix: ".", + inferNumberPrefix: true, + inferNumberPrefixFromProperty: "", + inferNumberPrefixRegExp: "^[0-9]+(\\.[0-9]+)*", + numberPrefix: "", + numberSuffix: "", + numberInit: 1, + numberStyle: "arabic", + numberDefault: "auto", + refFormat: "[type] [number] ([title])", + noteMathLinkFormat: "[title] if title exists, [type] [number] otherwise", + ignoreMainTheoremCalloutWithoutTitle: false, + numberOnlyReferencedEquations: true, + inferEqNumberPrefix: true, + inferEqNumberPrefixFromProperty: "", + inferEqNumberPrefixRegExp: "^[0-9]+(\\.[0-9]+)*", + eqNumberPrefix: "", + eqNumberSuffix: "", + eqNumberInit: 1, + eqNumberStyle: "arabic", + eqRefPrefix: "", + eqRefSuffix: "", + labelPrefix: "", + lineByLine: true, + theoremCalloutStyle: "Framed", + theoremCalloutFontInherit: false, + beginProof: "\\begin{proof}", + endProof: "\\end{proof}", + insertSpace: true, +} + +export const DEFAULT_EXTRA_SETTINGS: Required = { + foldDefault: '', + noteTitleInTheoremLink: true, + noteTitleInEquationLink: true, + profiles: DEFAULT_PROFILES, + showTheoremTitleinBuiltin: true, + showTheoremContentinBuiltin: false, + triggerSuggest: "\\ref", + triggerTheoremSuggest: "\\tref", + triggerEquationSuggest: "\\eqref", + triggerSuggestActiveNote: "\\ref:a", + triggerTheoremSuggestActiveNote: "\\tref:a", + triggerEquationSuggestActiveNote: "\\eqref:a", + triggerSuggestRecentNotes: "\\ref:r", + triggerTheoremSuggestRecentNotes: "\\tref:r", + triggerEquationSuggestRecentNotes: "\\eqref:r", + triggerSuggestDataview: "\\ref:d", + triggerTheoremSuggestDataview: "\\tref:d", + triggerEquationSuggestDataview: "\\eqref:d", + enableSuggest: true, + enableTheoremSuggest: true, + enableEquationSuggest: true, + enableSuggestActiveNote: true, + enableTheoremSuggestActiveNote: true, + enableEquationSuggestActiveNote: true, + enableSuggestRecentNotes: true, + enableTheoremSuggestRecentNotes: true, + enableEquationSuggestRecentNotes: true, + enableSuggestDataview: true, + enableTheoremSuggestDataview: true, + enableEquationSuggestDataview: true, + renderMathInSuggestion: true, + suggestNumber: 20, + searchMethod: "Fuzzy", + upWeightRecent: 0.1, + searchLabel: false, + modifierToJump: "Mod", + modifierToNoteLink: "Shift", + showModifierInstruction: true, + suggestLeafOption: "Current tab", + // projectInfix: " > ", + // projectSep: "/", + importerNumThreads: 2, + importerUtilization: 0.75, + showTheoremCalloutEditButton: false, + setOnlyTheoremAsMain: false, + setLabelInModal: false, + excludeExampleCallout: false, + enableProof: true, + autocompleteDvQuery: '', + searchModalQueryType: 'both', + searchModalRange: 'recent', + searchModalDvQuery: '', +}; diff --git a/src/settings/tab.ts b/src/settings/tab.ts new file mode 100644 index 0000000..69271ca --- /dev/null +++ b/src/settings/tab.ts @@ -0,0 +1,146 @@ +import { App, Component, PluginSettingTab, Setting } from "obsidian"; + +import LatexReferencer, { VAULT_ROOT } from "../main"; +import { DEFAULT_EXTRA_SETTINGS, DEFAULT_SETTINGS } from "./settings"; +import { ExtraSettingsHelper, MathContextSettingsHelper } from "./helper"; +import { ExcludedFileManageModal, LocalContextSettingsSuggestModal } from "settings/modals"; +// import { PROJECT_DESCRIPTION } from "project"; + + +export class MathSettingTab extends PluginSettingTab { + component: Component; + + constructor(app: App, public plugin: LatexReferencer) { + super(app, plugin); + this.component = new Component(); + } + + addRestoreDefaultsButton() { + new Setting(this.containerEl) + .addButton((btn) => { + btn.setButtonText("Restore defaults"); + btn.onClick(async () => { + Object.assign(this.plugin.settings[VAULT_ROOT], DEFAULT_SETTINGS); + Object.assign(this.plugin.extraSettings, DEFAULT_EXTRA_SETTINGS); + this.display(); + }) + }); + } + + display() { + const { containerEl } = this; + containerEl.empty(); + this.component.load(); + + containerEl.createEl("h4", { text: "Global" }); + + const root = this.app.vault.getRoot(); + const globalHelper = new MathContextSettingsHelper( + this.containerEl, + this.plugin.settings[VAULT_ROOT], + DEFAULT_SETTINGS, + this.plugin, + root + ); + this.component.addChild(globalHelper); + + const extraHelper = new ExtraSettingsHelper( + this.containerEl, + this.plugin.extraSettings, + this.plugin.extraSettings, + this.plugin, + false, + false + ); + this.component.addChild(extraHelper); + + const heading = extraHelper.addHeading('Equations - general'); + const numberingHeading = this.containerEl.querySelector('.equation-heading')!; + this.containerEl.insertBefore( + heading.settingEl, + numberingHeading + ); + this.containerEl.insertAfter( + extraHelper.settingRefs.enableProof.settingEl, + this.containerEl.querySelector('.proof-heading')! + ); + this.containerEl.insertAfter( + extraHelper.settingRefs.showTheoremCalloutEditButton.settingEl, + globalHelper.settingRefs.profile.settingEl + ); + this.containerEl.insertAfter( + extraHelper.settingRefs.excludeExampleCallout.settingEl, + globalHelper.settingRefs.profile.settingEl + ); + this.containerEl.insertBefore( + extraHelper.settingRefs.foldDefault.settingEl, + globalHelper.settingRefs.labelPrefix.settingEl + ); + this.containerEl.insertBefore( + extraHelper.settingRefs.setOnlyTheoremAsMain.settingEl, + globalHelper.settingRefs.labelPrefix.settingEl + ); + this.containerEl.insertBefore( + extraHelper.settingRefs.setLabelInModal.settingEl, + globalHelper.settingRefs.labelPrefix.settingEl + ); + this.containerEl.insertAfter( + extraHelper.settingRefs.noteTitleInTheoremLink.settingEl, + globalHelper.settingRefs.refFormat.settingEl + ); + this.containerEl.insertAfter( + extraHelper.settingRefs.noteTitleInEquationLink.settingEl, + globalHelper.settingRefs.eqRefSuffix.settingEl + ); + + this.containerEl.insertBefore( + globalHelper.settingRefs.insertSpace.settingEl, + extraHelper.settingRefs.searchMethod.settingEl, + ); + + // const projectHeading = containerEl.createEl("h3", { text: "Projects (experimental)" }); + // const projectDesc = containerEl.createDiv({ + // text: PROJECT_DESCRIPTION, + // cls: ["setting-item-description", "math-booster-setting-item-description"] + // }); + + // this.containerEl.insertBefore( + // projectHeading, + // extraHelper.settingRefs.projectInfix.settingEl + // ); + // this.containerEl.insertAfter( + // projectDesc, + // projectHeading, + // ); + + this.addRestoreDefaultsButton(); + + containerEl.createEl("h4", { text: "Local" }); + new Setting(containerEl).setName("Local settings") + .setDesc("You can set up local (i.e. file-specific or folder-specific) settings, which have more precedence than the global settings. Local settings can be configured in various ways; here in the plugin settings, right-clicking in the file explorer, the \"Open local settings for the current file\" command, and the \"Open local settings for the current file\" button in the theorem callout settings pop-ups.") + .addButton((btn) => { + btn.setButtonText("Search files & folders") + .onClick(() => { + new LocalContextSettingsSuggestModal(this.app, this.plugin, this).open(); + }); + }); + + new Setting(containerEl) + .setName("Excluded files") + .setDesc("You can make your search results more visible by excluding certain files or folders.") + .addButton((btn) => { + btn.setButtonText("Manage") + .onClick(() => { + new ExcludedFileManageModal(this.app, this.plugin).open(); + }); + }); + } + + async hide() { + super.hide(); + await this.plugin.saveSettings(); + this.plugin.indexManager.trigger('global-settings-updated'); + this.plugin.updateLinkAutocomplete(); + this.component.unload(); + } +} diff --git a/src/theorem-callouts/renderer.ts b/src/theorem-callouts/renderer.ts new file mode 100644 index 0000000..71b1294 --- /dev/null +++ b/src/theorem-callouts/renderer.ts @@ -0,0 +1,479 @@ +import { App, ExtraButtonComponent, MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownView, Notice, TFile, editorInfoField } from "obsidian"; + +import LatexReferencer from 'main'; +import { TheoremCalloutModal } from 'settings/modals'; +import { TheoremCalloutSettings, TheoremCalloutPrivateFields } from 'settings/settings'; +import { generateTheoremCalloutFirstLine, isTheoremCallout, resolveSettings } from 'utils/plugin'; +import { isEditingView } from 'utils/editor'; +import { capitalize } from 'utils/general'; +import { formatTitleWithoutSubtitle } from "utils/format"; +import { renderTextWithMath } from "utils/render"; +import { MarkdownPage, TheoremCalloutBlock } from "index/typings/markdown"; +import { MathIndex } from 'index/math-index'; +import { parseTheoremCalloutMetadata, readTheoremCalloutSettings } from 'utils/parse'; +import { THEOREM_LIKE_ENV_ID_PREFIX_MAP, THEOREM_LIKE_ENV_PREFIX_ID_MAP, TheoremLikeEnvID, TheoremLikeEnvPrefix } from 'env'; +import { getIO } from 'file-io'; +import { MutationObservingChild, getSectionCacheFromMouseEvent, getSectionCacheOfDOM, isPdfExport, resolveLinktext } from 'utils/obsidian'; + + +export const createTheoremCalloutPostProcessor = (plugin: LatexReferencer) => async (element: HTMLElement, context: MarkdownPostProcessorContext) => { + const file = plugin.app.vault.getAbstractFileByPath(context.sourcePath) ?? plugin.app.workspace.getActiveFile(); + if (!(file instanceof TFile)) return null; + + const pdf = isPdfExport(element); + let index = 0; // for numbering theorems in PDf export + + for (const calloutEl of element.querySelectorAll(`.callout`)) { + const type = calloutEl.getAttribute('data-callout')!.toLowerCase(); + + if (isTheoremCallout(plugin, type)) { + if (pdf) { // preprocess for theorem numbering in PDF export + const settings = readSettingsFromEl(calloutEl); + if (settings?.number === 'auto') calloutEl.setAttribute('data-theorem-index', String(index++)); + } + + const theoremCallout = new TheoremCalloutRenderer(calloutEl, context, file, plugin); + context.addChild(theoremCallout); + } + } +} + + +export interface TheoremCalloutInfo { + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "theorem" */ + theoremType: string; + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "Theorem 1.1" */ + theoremMainTitle: string; + /** e.g. Theorem 1.1 (Cauchy-Schwarz) -> "Cauchy-Schwarz" */ + theoremSubtitleEl: HTMLElement | null; + titleSuffix: string; +} + + +/** + * Renders theorem callouts. The rendering strategy varys depending on the given situation: + * + * Reading view: + * Use the index. Listen to the index update event. + * Live preview: + * The index might be out-of-date. Also, context.getSectionInfo() returns null. + * Thus, I'm currently taking a rather hacky or dirty approach, where I set "data-theorem-index" attribute + * indicating a 0-based index of auto-numbered theorems in a document for every editor updates + * using a CodeMirror6 view plugin called theoremCalloutNumberingViewPlugin. + * Embeds: + * Read the target note path and the block id from the `src` attribute, and + * use it to find the corresponding TheoremCalloutBlock object from the index. + * Hover popover: + * The core page preview plugin is patched (src/patches/page-preview.ts) so that the linktext is + * saved in the plugin instance. Read it then proceed as in the embed case. + */ +class TheoremCalloutRenderer extends MarkdownRenderChild { + app: App; + index: MathIndex; + observer: MutationObservingChild; + /** The info on which the last DOM update was based on. Used to reduce redundant updates. */ + info: TheoremCalloutInfo | null = null; + /** Set to the linktext when this theorem callout is inside an embed or a hover page preview. */ + linktext: string | null = null; + editButton: HTMLElement | null = null; + + constructor( + containerEl: HTMLElement, + public context: MarkdownPostProcessorContext, + public file: TFile, + public plugin: LatexReferencer + ) { + super(containerEl); + this.app = plugin.app; + this.index = plugin.indexManager.index; + + // update: for Live Preview & PDF export + this.addChild(this.observer = new MutationObservingChild(this.containerEl, (mutations) => { + for (const mutation of mutations) { + if (mutation.oldValue !== this.containerEl.getAttribute('data-theorem-index')) { + this.update(); + } + } + }, { + attributeFilter: ['data-theorem-index'], + attributeOldValue: true, + })) + + // update when the math index is updated + this.registerEvent(this.plugin.indexManager.on('index-updated', (file) => { + if (file.path === this.file.path) { + this.update(); + } + })); + + // remove the edit button when this plugin gets disabled + this.plugin.addChild(this); + this.register(() => this.removeEditButton()); + // remove the edit button when the relevent setting is disabled + this.registerEvent(this.plugin.indexManager.on('global-settings-updated', () => { + if (this.plugin.extraSettings.showTheoremCalloutEditButton) { + this.addEditButton(); + } else { + this.removeEditButton(); + } + })); + } + + getPage(): MarkdownPage | null { + const page = this.plugin.indexManager.index.load(this.context.sourcePath); + if (MarkdownPage.isMarkdownPage(page)) return page; + return null; + } + + isLivePreview(): boolean { + return this.containerEl.closest('[src]') === null && this.containerEl.closest('.markdown-source-view.is-live-preview') !== null; + } + + onload() { + this.update(); + } + + update() { + const existingMainTitleEl = this.containerEl.querySelector(".theorem-callout-main-title"); + + if (existingMainTitleEl && this.isLivePreview()) { + // only update the main title part (e.g. "Theorem 1.2") + + // Here, settings.title might be incorrect (e.g. "Theorem 1.2 (Cauchy-Schwarz)" instead of "Cauchy-Schwarz"), + // but it is not a problem because we are only updating the main title part. + const settings: (TheoremCalloutSettings & TheoremCalloutPrivateFields) | null = readSettingsFromEl(this.containerEl); + if (!settings) return null; + + const livePreviewIndex = this.containerEl.getAttribute('data-theorem-index'); + if (livePreviewIndex !== null) settings._index = +livePreviewIndex; + + const resolvedSettings = resolveSettings(settings, this.plugin, this.file); + const newMainTitle = formatTitleWithoutSubtitle(this.plugin, this.file, resolvedSettings); + + existingMainTitleEl.setText(newMainTitle); + this.info = null; + + return; + } + + let block = this.getTheoremCalloutInfoFromIndex(); + let info: TheoremCalloutInfo | null = block ?? this.getTheoremCalloutInfoFromEl(); + if (!info) { + this.info = null; + return + }; + + if (!this.info || TheoremCalloutRenderer.areDifferentInfo(info, this.info)) { + this.renderTitle(info, existingMainTitleEl); + this.addCssClasses(info); + this.addEditButton(); + + this.info = info; + } + + // In embeds or hover popover, we can get an incorrect TheoremCalloutBlock because + // MarkdownPostProcessorContext.getSectionInfo() returns incorrect line numbers. + // So we have to correct it manually. + setTimeout(() => { + // hover editor has no problem with line numbers, so there is no job to do! + if (this.containerEl.closest('.hover-popover.hover-editor')) return; + + const update = this.correctEmbedOrHoverPagePreview(block, info!); // correct line number + if (update) { + block = update.block; + this.info = info = update.info; + } + + }); + } + + correctEmbedOrHoverPagePreview(block: (TheoremCalloutInfo & { blockId?: string }) | null, info: TheoremCalloutInfo) { + // For embeds, we can get the linktext from the "src" attribute. + // In the case of hover page preview, we cannot get the linktext from the "src" attribute. + // So we patched the core page preview plugin so that it saves the linktext in the plugin instance. + // See src/patches/page-preview.ts + let linktext = this.containerEl.closest('[src]')?.getAttribute('src'); + + if (!linktext) { + const hoverEl = this.containerEl.closest('.hover-popover:not(.hover-editor)'); + if (hoverEl) { + // The current context is hover page preview; read the linktext saved in the plugin instance. + linktext = this.plugin.lastHoverLinktext; + + if (!linktext) { + // somehow failed to get the linktext; abort. + const update = this.correctHoverWithoutSrc(); + if (update) { + block = update.block; + this.info = info = update.info; + } + return; + } + } + } + + if (linktext) { + this.linktext = linktext; + const { file, subpathResult: result } = resolveLinktext(this.app, linktext, this.context.sourcePath) ?? {}; + if (!file || !result) return; + + if (result.type === 'block') { + if (result.block.id !== block?.blockId) { + + const page = this.getPage(); + if (!page) return; + + const _block = page.$blocks.get(result.block.id); + if (TheoremCalloutBlock.isTheoremCalloutBlock(_block)) { + info = block = this.blockToInfo(_block); + if (!this.info || TheoremCalloutRenderer.areDifferentInfo(info, this.info)) { + this.renderTitle(info); + this.addCssClasses(info); + } + } + } + } else if (result.type === 'heading') { + const _block = this.findTheoremCalloutBlock(result.start.line); + if (_block) { + info = block = this.blockToInfo(_block); + if (!this.info || TheoremCalloutRenderer.areDifferentInfo(info, this.info)) { + this.renderTitle(info); + this.addCssClasses(info); + } + } + } + } + + this.addEditButton(); + + return { block, info }; + } + + correctHoverWithoutSrc() { + const block = null; + const info = this.getTheoremCalloutInfoFromEl(); + if (!info) return; + + if (!this.info || TheoremCalloutRenderer.areDifferentInfo(info, this.info)) { + this.renderTitle(info); + this.addCssClasses(info); + } + + this.removeEditButton(); + + return { block, info }; + } + + /** Find the corresponding TheoremCalloutBlock object from the index. */ + findTheoremCalloutBlock(lineOffset: number = 0): TheoremCalloutBlock | null { + const page = this.getPage(); + if (!page) return null; + + const info = this.context.getSectionInfo(this.containerEl); + if (!info) return null; + + const block = page.getBlockByLineNumber(info.lineStart + lineOffset) ?? page.getBlockByLineNumber(info.lineEnd + lineOffset); + if (!TheoremCalloutBlock.isTheoremCalloutBlock(block)) return null; + + return block; + } + + blockToInfo(block: TheoremCalloutBlock): TheoremCalloutInfo & { blockId?: string } { + let theoremSubtitleEl: HTMLElement | null = null; + + if (block.$theoremSubtitle) { + theoremSubtitleEl = createSpan({ cls: "theorem-callout-subtitle" }); + const subtitle = renderTextWithMath(`(${block.$theoremSubtitle})`); + theoremSubtitleEl.replaceChildren(...subtitle); + } + + return { + theoremType: block.$theoremType, + theoremMainTitle: block.$theoremMainTitle, + theoremSubtitleEl, + titleSuffix: block.$titleSuffix, + blockId: block.$blockId, + }; + } + + getTheoremCalloutInfoFromIndex(): TheoremCalloutInfo & { blockId?: string } | null { + const block = this.findTheoremCalloutBlock(); + if (!block) return null; + return this.blockToInfo(block); + } + + // this method is expected to be called for live preview only + getTheoremCalloutInfoFromEl(): TheoremCalloutInfo | null { + const settings: (TheoremCalloutSettings & TheoremCalloutPrivateFields) | null = readSettingsFromEl(this.containerEl); + if (!settings) return null; + const livePreviewIndex = this.containerEl.getAttribute('data-theorem-index'); + if (livePreviewIndex !== null) settings._index = +livePreviewIndex; + const resolvedSettings = resolveSettings(settings, this.plugin, this.file); + + let theoremSubtitleEl = this.containerEl.querySelector('.theorem-callout-subtitle'); + if (theoremSubtitleEl === null) { + const titleInnerEl = this.containerEl.querySelector('.callout-title-inner'); + if (titleInnerEl?.childNodes.length) { + if (titleInnerEl.textContent !== capitalize(settings.type) && titleInnerEl.textContent !== capitalize(THEOREM_LIKE_ENV_ID_PREFIX_MAP[settings.type as TheoremLikeEnvID])) { + theoremSubtitleEl = createSpan({ cls: "theorem-callout-subtitle" }); + theoremSubtitleEl.replaceChildren('(', ...titleInnerEl.childNodes, ')'); + } + } + } + + return { + theoremType: settings.type, + theoremMainTitle: formatTitleWithoutSubtitle(this.plugin, this.file, resolvedSettings), + theoremSubtitleEl, + titleSuffix: resolvedSettings.titleSuffix + }; + } + + renderTitle(info: TheoremCalloutInfo, existingMainTitleEl?: HTMLElement | null) { + const titleInner = this.containerEl.querySelector('.callout-title-inner'); + if (!titleInner) throw Error(`${this.plugin.manifest.name}: Failed to find the title element of a theorem callout.`); + + const newMainTitleEl = createSpan({ + text: info.theoremMainTitle, + cls: "theorem-callout-main-title" + }); + + if (existingMainTitleEl) { + // only update the main title part + existingMainTitleEl.replaceWith(newMainTitleEl); + return; + } + + const titleElements: (HTMLElement | string)[] = [newMainTitleEl]; + + if (info.theoremSubtitleEl) { + titleElements.push(" ", info.theoremSubtitleEl); + } + if (info.titleSuffix) { + titleElements.push(info.titleSuffix); + } + + titleInner.replaceChildren(...titleElements); + } + + addCssClasses(info: TheoremCalloutInfo) { + this.containerEl.classList.forEach((cls, _, list) => { + if (cls.startsWith('theorem-callout')) list.remove(cls); + }); + this.containerEl.classList.add("theorem-callout"); + const resolvedSettings = resolveSettings(undefined, this.plugin, this.file); + const profile = this.plugin.extraSettings.profiles[resolvedSettings.profile]; + for (const tag of profile.meta.tags) { + this.containerEl.classList.add("theorem-callout-" + tag); + } + this.containerEl.classList.add("theorem-callout-" + info.theoremType); + this.containerEl.toggleClass(`theorem-callout-${resolvedSettings.theoremCalloutStyle.toLowerCase()}`, resolvedSettings.theoremCalloutStyle != "Custom"); + this.containerEl.toggleClass("theorem-callout-font-family-inherit", resolvedSettings.theoremCalloutStyle != "Custom" && resolvedSettings.theoremCalloutFontInherit); + } + + removeEditButton() { + if (this.editButton) { + this.editButton.remove(); + this.editButton = null; + } + } + + addEditButton() { + if (!this.plugin.extraSettings.showTheoremCalloutEditButton) return; + if (this.editButton) return; // already exists + + const button = new ExtraButtonComponent(this.containerEl) + .setIcon("settings-2") + .setTooltip("Edit theorem callout settings"); + + this.editButton = button.extraSettingsEl; + this.editButton.addClass("theorem-callout-setting-button"); + + button.extraSettingsEl.addEventListener("click", async (ev) => { + ev.stopPropagation(); + const io = getIO(this.plugin, this.file); + + // Make sure to get the line number BEFORE opening the modal!! + const lineNumber = this.getLineNumber(ev); + if (lineNumber === null) return; + const line = await io.getLine(lineNumber); + + new TheoremCalloutModal(this.app, this.plugin, this.file, async (settings) => { + if (lineNumber !== undefined) { + await io.setLine(lineNumber, generateTheoremCalloutFirstLine(settings)); + } else { + new Notice( + `${this.plugin.manifest.name}: Could not find the line number to overwrite. Retry later.`, + 5000 + ) + } + }, + "Confirm", + "Edit theorem callout settings", + readTheoremCalloutSettings(line, this.plugin.extraSettings.excludeExampleCallout) + ).open(); + }); + } + + getLineNumber(event: MouseEvent): number | null { + let lineOffset = 0; + + // handle the embed case + if (this.linktext !== null) { + const { subpathResult } = resolveLinktext(this.app, this.linktext, this.context.sourcePath) ?? {}; + if (subpathResult) lineOffset = subpathResult.start.line; + } + + const info = this.context.getSectionInfo(this.containerEl); + if (info) return lineOffset + info.lineStart; + + const cache = this.app.metadataCache.getFileCache(this.file); + if (!cache) return null; + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + if (!view) return null; + + if (isEditingView(view) && this.file.path === view.file?.path && view.editor.cm) { + let sec = getSectionCacheOfDOM(this.containerEl, "callout", view.editor.cm, cache) + ?? getSectionCacheFromMouseEvent(event, "callout", view.editor.cm, cache) + if (sec) return sec.position.start.line; + } + + // What can I do in reading view?? + + return null; + } + + static areDifferentInfo(info1: TheoremCalloutInfo, info2: TheoremCalloutInfo) { + return info1.theoremMainTitle !== info2.theoremMainTitle; + } +} + + +/** Read TheoremCalloutSettings from the element's attribute. */ +function readSettingsFromEl(calloutEl: HTMLElement): TheoremCalloutSettings | null { + let type = calloutEl.getAttribute('data-callout')?.trim().toLowerCase(); + if (type === undefined) return null; + + const metadata = calloutEl.getAttribute('data-callout-metadata'); + if (metadata === null) return null; + + if (type === 'math') { + // legacy format + const settings = JSON.parse(metadata) as TheoremCalloutSettings; + // @ts-ignore + delete settings['_index']; // do not use the legacy "_index" value + return settings; + } + + // new format + if (type.length <= 4) { // use length to avoid iterating over all the prefixes + // convert a prefix to an ID (e.g. "thm" -> "theorem") + type = THEOREM_LIKE_ENV_PREFIX_ID_MAP[type as TheoremLikeEnvPrefix]; + } + + const number = parseTheoremCalloutMetadata(metadata); + + const title = '' // calloutEl.querySelector('.callout-title-inner')?.textContent?.trim(); + + return { type, number, title } +} diff --git a/src/theorem-callouts/state-field.ts b/src/theorem-callouts/state-field.ts new file mode 100644 index 0000000..282a25c --- /dev/null +++ b/src/theorem-callouts/state-field.ts @@ -0,0 +1,96 @@ +import { StateField, EditorState, Transaction, RangeSet, RangeValue, Range, Text } from '@codemirror/state'; +import { ensureSyntaxTree, syntaxTree } from '@codemirror/language'; + +import LatexReferencer from 'main'; +import { readTheoremCalloutSettings } from 'utils/parse'; +import { editorInfoField } from 'obsidian'; + +export const CALLOUT = /HyperMD-callout_HyperMD-quote_HyperMD-quote-([1-9][0-9]*)/; + +export class TheoremCalloutInfo extends RangeValue { + constructor(public index: number | null) { + super(); + } +} + + +export const createTheoremCalloutsField = (plugin: LatexReferencer) => StateField.define>({ + create(state: EditorState) { + // Since because canvas files cannot be indexed currently, + // do not number theorems in canvas to make live preview consistent with reading view + if (!state.field(editorInfoField).file) return RangeSet.empty; + + const ranges = getTheoremCalloutInfos(plugin, state, state.doc, 0, 0); + return RangeSet.of(ranges); + }, + update(value: RangeSet, tr: Transaction) { + // Since because canvas files cannot be indexed currently, + // do not number theorems in canvas to make live preview consistent with reading view + if (!tr.state.field(editorInfoField).file) return RangeSet.empty; + + // Because the field can be perfectly determined by the document content, + // we don't need to update it when the document is not changed + if (!tr.docChanged) return value; + + // In order to make the updates efficient, we only update the theorem callout infos that are affected by the changes, + // that is, theorem callouts after the insertion point. + + // Here, we use tr.newDoc instead of tr.state.doc because "Contrary to .state.doc, accessing this won't force the entire new state to be computed right away" (from CM6 docs) + + let minChangedPosition = tr.newDoc.length - 1; + const changeDesc = tr.changes.desc; + changeDesc.iterChangedRanges((fromA, toA, fromB, toB) => { + if (fromB < minChangedPosition) { + minChangedPosition = fromB; + } + }); + + value = value.map(changeDesc); + + let init = 0; + value.between(0, minChangedPosition, (from, to, info) => { + if (to < minChangedPosition && info.index !== null) init = info.index + 1; + }); + + const updatedRanges = getTheoremCalloutInfos(plugin, tr.state, tr.newDoc, minChangedPosition, init); + return value.update({ + add: updatedRanges, + filter: () => false, + filterFrom: minChangedPosition, + }); + } +}); + + +function getTheoremCalloutInfos(plugin: LatexReferencer, state: EditorState, doc: Text, from: number, init: number): Range[] { + const ranges: Range[] = []; + // syntaxTree returns a potentially imcomplete tree (limited by viewport), so we need to ensure it's complete + const tree = ensureSyntaxTree(state, doc.length) ?? syntaxTree(state); + + let theoremIndex = init; // incremented when a auto-numbered theorem is found + + tree.iterate({ + from, to: doc.length, + enter(node) { + if (node.name === 'Document') return; // skip the node for the entire document + + if (node.node.parent?.name !== 'Document') return false; // skip sub-nodes of a line + + const text = doc.sliceString(node.from, node.to); + + const match = node.name.match(CALLOUT); + if (!match) return false; + + const settings = readTheoremCalloutSettings(text, plugin.extraSettings.excludeExampleCallout); + if (!settings) return false; + + const value = new TheoremCalloutInfo(settings.number === 'auto' ? theoremIndex++ : null); + const range = value.range(node.from, node.to); + ranges.push(range); + + return false; + } + }); + + return ranges; +} diff --git a/src/theorem-callouts/view-plugin.ts b/src/theorem-callouts/view-plugin.ts new file mode 100644 index 0000000..473d101 --- /dev/null +++ b/src/theorem-callouts/view-plugin.ts @@ -0,0 +1,34 @@ +import { PluginValue, EditorView, ViewUpdate, ViewPlugin } from '@codemirror/view'; + +import LatexReferencer from 'main'; +import { MathIndex } from 'index/math-index'; + + +export const createTheoremCalloutNumberingViewPlugin = (plugin: LatexReferencer) => ViewPlugin.fromClass( + class implements PluginValue { + index: MathIndex = plugin.indexManager.index; + + constructor(public view: EditorView) { + // Wait until the initial rendering is done so that we can find the callout elements using qeurySelectorAll(). + setTimeout(() => this._update(view)); + } + + update(update: ViewUpdate) { + this._update(update.view); + } + + _update(view: EditorView) { + const infos = view.state.field(plugin.theoremCalloutsField); + + for (const calloutEl of view.contentDOM.querySelectorAll('.callout.theorem-callout')) { + const pos = view.posAtDOM(calloutEl); + const iter = infos.iter(pos); + if (iter.from !== pos) continue; // insertion or deletion occured before this callout, and the posAtDom is out-dated for some reasons: do not update the theorem number + + const index = iter.value?.index; + if (typeof index === 'number') calloutEl.setAttribute('data-theorem-index', String(index)); + else calloutEl.removeAttribute('data-theorem-index'); + } + } + } +); diff --git a/src/typings/type.d.ts b/src/typings/type.d.ts new file mode 100644 index 0000000..8edf1bd --- /dev/null +++ b/src/typings/type.d.ts @@ -0,0 +1,43 @@ +import { PaneType, SplitDirection } from "obsidian"; +import { EditorView } from "@codemirror/view"; + +declare module "obsidian" { + interface App { + plugins: { + enabledPlugins: Set; + plugins: { + [id: string]: any; + }; + getPlugin: (id: string) => Plugin | null; + }; + } + interface Editor { + cm?: EditorView; + } + // Reference: https://github.com/tadashi-aikawa/obsidian-various-complements-plugin/blob/be4a12c3f861c31f2be3c0f81809cfc5ab6bb5fd/src/ui/AutoCompleteSuggest.ts#L595-L619 + interface EditorSuggest { + scope: Scope; + suggestions: { + selectedItem: number; + values: T[]; + containerEl: HTMLElement; + }; + suggestEl: HTMLElement; + } + + // Reference: https://github.com/tadashi-aikawa/obsidian-another-quick-switcher/blob/6aa40a46fe817d25c11847a46ec6c765c742d629/src/ui/UnsafeModalInterface.ts#L5 + interface SuggestModal { + chooser: { + values: T[] | null; + selectedItem: number; + setSelectedItem( + item: number, + event?: KeyboardEvent, + ): void; + useSelectedItem(ev: Partial): void; + suggestions: Element[]; + }; + } +} + +export type LeafArgs = [newLeaf?: PaneType | boolean] | [newLeaf?: 'split', direction?: SplitDirection]; diff --git a/src/typings/workers.d.ts b/src/typings/workers.d.ts new file mode 100644 index 0000000..8350b48 --- /dev/null +++ b/src/typings/workers.d.ts @@ -0,0 +1,4 @@ +declare module "index/web-worker/importer.worker" { + const WorkerFactory: new () => Worker; + export default WorkerFactory; +} diff --git a/src/utils/editor.ts b/src/utils/editor.ts new file mode 100644 index 0000000..5350fef --- /dev/null +++ b/src/utils/editor.ts @@ -0,0 +1,77 @@ +import { EditorState, ChangeSet, RangeValue, RangeSet, SelectionRange } from '@codemirror/state'; +import { SyntaxNodeRef } from '@lezer/common'; +import { EditorPosition, Loc, MarkdownView, editorLivePreviewField } from "obsidian"; + +export function locToEditorPosition(loc: Loc): EditorPosition { + return { ch: loc.col, line: loc.line }; +} + +export function isLivePreview(state: EditorState) { + return state.field(editorLivePreviewField); +} + +export function isSourceMode(state: EditorState) { + return !isLivePreview(state); +} + +export function isReadingView(markdownView: MarkdownView) { + return markdownView.getMode() === "preview"; +} + +export function isEditingView(markdownView: MarkdownView) { + return markdownView.getMode() === "source"; +} + +/** CodeMirror/Lezer utilities */ + +export function nodeText(node: SyntaxNodeRef, state: EditorState): string { + return state.sliceDoc(node.from, node.to); +} + +export function printNode(node: SyntaxNodeRef, state: EditorState) { + // Debugging utility + console.log( + `${node.from}-${node.to}: "${nodeText(node, state)}" (${node.name})` + ); +} + +export function nodeTextQuoteSymbolTrimmed(node: SyntaxNodeRef, state: EditorState, quoteLevel: number): string | undefined { + const quoteSymbolPattern = new RegExp(`((>\\s*){${quoteLevel}})(.*)`); + const quoteSymbolMatch = nodeText(node, state).match(quoteSymbolPattern); + if (quoteSymbolMatch) { + return quoteSymbolMatch.slice(-1)[0]; + } +} + +export function printChangeSet(changes: ChangeSet) { + changes.iterChanges( + (fromA, toA, fromB, toB, inserted) => { + console.log(`${fromA}-${toA}: "${inserted.toString()}" inserted (${fromB}-${toB} in new state)`); + } + ); +} + +export function rangeSetSome(set: RangeSet, predicate: (value: T, index: number, set: RangeSet) => unknown) { + const cursor = set.iter(); + let index = 0; + while (cursor.value) { + if (predicate(cursor.value, index, set)) { + return true; + } + cursor.next(); + index++; + } + return false; +} + +export function hasOverlap(range1: { from: number, to: number }, range2: { from: number, to: number }): boolean { + return range1.from <= range2.to && range2.from <= range1.to; +} + +export function rangesHaveOverlap(ranges: readonly SelectionRange[], from: number, to: number) { + for (const range of ranges) { + if (range.from <= to && range.to >= from) + return true; + } + return false; +} diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..e8e8a63 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,171 @@ +import { App, TFile } from "obsidian"; + +import LatexReferencer from "main"; +import { getPropertyOrLinkTextInProperty } from "utils/obsidian"; +import { DEFAULT_SETTINGS, MathContextSettings, NumberStyle, ResolvedMathSettings } from "settings/settings"; +import { THEOREM_LIKE_ENVs, TheoremLikeEnvID } from "env"; + + +const ROMAN = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", + "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", + "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]; + +export function toRomanUpper(num: number): string { + // https://stackoverflow.com/a/9083076/13613783 + const digits = String(num).split(""); + let roman = ""; + let i = 3; + while (i--) { + // @ts-ignore + roman = (ROMAN[+digits.pop() + (i * 10)] ?? "") + roman; + } + return Array(+digits.join("") + 1).join("M") + roman; +} + +export function toRomanLower(num: number): string { + return toRomanUpper(num).toLowerCase(); +} + +export const ALPH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +export function toAlphUpper(num: number): string { + return (num - 1).toString(26).split("").map(str => ALPH[parseInt(str, 26)]).join(""); +} + +export function toAlphLower(num: number): string { + return toAlphUpper(num).toLowerCase(); +} + +export const CONVERTER = { + "arabic": String, + "alph": toAlphLower, + "Alph": toAlphUpper, + "roman": toRomanLower, + "Roman": toRomanUpper, +} + +export function formatTheoremCalloutType(plugin: LatexReferencer, settings: { type: string, profile: string }): string { + const profile = plugin.extraSettings.profiles[settings.profile]; + return profile.body.theorem[settings.type as TheoremLikeEnvID]; +} + +export function formatTitleWithoutSubtitle(plugin: LatexReferencer, file: TFile, settings: ResolvedMathSettings): string { + let title = formatTheoremCalloutType(plugin, settings); + + if (settings.number) { + if (settings.number == 'auto') { + if (settings._index !== undefined) { + settings.numberInit = settings.numberInit ?? 1; + const num = +settings._index + +settings.numberInit; + const style = settings.numberStyle ?? DEFAULT_SETTINGS.numberStyle as NumberStyle; + title += ` ${getNumberPrefix(plugin.app, file, settings)}${CONVERTER[style](num)}${settings.numberSuffix}`; + } + } else { + title += ` ${settings.number}`; + } + } + return title; +} + +export function formatTitle(plugin: LatexReferencer, file: TFile, settings: ResolvedMathSettings, noTitleSuffix: boolean = false): string { + let title = formatTitleWithoutSubtitle(plugin, file, settings); + return addSubTitle(title, settings, noTitleSuffix); +} + +export function addSubTitle(mainTitle: string, settings: ResolvedMathSettings, noTitleSuffix: boolean = false) { + let title = mainTitle; + if (settings.title) { + title += ` (${settings.title})`; + } + if (!noTitleSuffix && settings.titleSuffix) { + title += settings.titleSuffix; + } + return title; +} + +export function inferNumberPrefix(source: string, regExp: string): string | undefined { + const pattern = new RegExp(regExp); + const match = source.match(pattern); + if (match) { + let prefix = match[0].trim(); + if (!prefix.endsWith('.')) prefix += '.'; + return prefix; + } +} + +// /** +// * "A note about calculus" => The "A" at the head shouldn't be used as a prefix (indefinite article) +// * "A. note about calculus" => The "A" at the head IS a prefix +// */ +// export function areValidLabels(labels: string[]): boolean { +// function isValidLabel(label: string): boolean { // true if every label is an arabic or roman numeral +// if (label.match(/^[0-9]+$/)) { +// // Arabic numerals +// return true; +// } +// if (label.match(/^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i)) { +// // Roman numerals +// // Reference: https://stackoverflow.com/a/267405/13613783 +// return true; +// } +// if (label.match(/^[a-z]$/i)) { +// return true; +// } +// return false; +// } +// const blankRemoved = labels.filter((label) => label); +// if (blankRemoved.length >= 2) { +// return blankRemoved.every((label) => isValidLabel(label)); +// } +// if (blankRemoved.length == 1) { +// return labels.length == 2 && (isValidLabel(labels[0])); +// } +// return false; +// } + +/** + * Get an appropriate prefix for theorem callout numbering. + * @param file + * @param settings + * @returns + */ +export function getNumberPrefix(app: App, file: TFile, settings: Required): string { + if (settings.numberPrefix) { + return settings.numberPrefix; + } + const source = settings.inferNumberPrefixFromProperty ? getPropertyOrLinkTextInProperty(app, file, settings.inferNumberPrefixFromProperty) : file.basename; + if (settings.inferNumberPrefix && source) { + return inferNumberPrefix( + source, + settings.inferNumberPrefixRegExp + ) ?? ""; + } + return ""; +} + +/** + * Get an appropriate prefix for equation numbering. + * @param file + * @param settings + * @returns + */ +export function getEqNumberPrefix(app: App, file: TFile, settings: Required): string { + if (settings.eqNumberPrefix) { + return settings.eqNumberPrefix; + } + const source = settings.inferEqNumberPrefixFromProperty ? getPropertyOrLinkTextInProperty(app, file, settings.inferEqNumberPrefixFromProperty) : file.basename; + if (settings.inferEqNumberPrefix && source) { + const prefix = inferNumberPrefix( + source, + settings.inferEqNumberPrefixRegExp + ) ?? ""; + return prefix; + } + return ""; +} + +export function formatLabel(settings: ResolvedMathSettings): string | undefined { + if (settings.label) { + return settings.labelPrefix + THEOREM_LIKE_ENVs[settings.type as TheoremLikeEnvID].prefix + ":" + settings.label; + } +} diff --git a/src/utils/general.ts b/src/utils/general.ts new file mode 100644 index 0000000..3b1ca18 --- /dev/null +++ b/src/utils/general.ts @@ -0,0 +1,33 @@ +export function splitIntoLines(text: string): string[] { + // https://stackoverflow.com/a/5035005/13613783 + return text.split(/\r?\n/); +} + +export function capitalize(text: string): string { + return text.charAt(0).toUpperCase() + text.slice(1); +} + +export function removeFrom(item: Type, array: Array) { + return array.splice(array.indexOf(item), 1); +} + +export function insertAt(array: Array, item: Type, index: number) { + array.splice(index, 0, item); +} + +export function pathToName(path: string): string { + return path.slice(path.lastIndexOf('/') + 1); +} + +export function pathToBaseName(path: string): string { + const name = pathToName(path); + const index = name.lastIndexOf('.'); + if (index >= 0) { + return name.slice(0, index); + } + return name; +} + +// https://stackoverflow.com/a/50851710/13613783 +export type BooleanKeys = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T]; +export type NumberKeys = { [k in keyof T]: T[k] extends number ? k : never }[keyof T]; diff --git a/src/utils/obsidian.ts b/src/utils/obsidian.ts new file mode 100644 index 0000000..39638db --- /dev/null +++ b/src/utils/obsidian.ts @@ -0,0 +1,235 @@ +import { EditorView } from '@codemirror/view'; +import { BlockSubpathResult, CachedMetadata, Component, HeadingSubpathResult, MarkdownPostProcessorContext, MarkdownView, Modifier, Platform, Plugin, Pos, SectionCache, parseLinktext, resolveSubpath } from "obsidian"; +import { App, TAbstractFile, TFile, TFolder } from "obsidian"; +import { locToEditorPosition } from 'utils/editor'; +import { LeafArgs } from 'typings/type'; + +//////////////////// +// File utilities // +//////////////////// + +/** + * Similar to Vault.recurseChildren, but this function can be also called for TFile, not just TFolder. + * Also, the callback is only called for TFile. + */ +export function iterDescendantFiles(file: TAbstractFile, callback: (descendantFile: TFile) => any) { + if (file instanceof TFile) { + callback(file); + } else if (file instanceof TFolder) { + for (const child of file.children) { + iterDescendantFiles(child, callback); + } + } +} + +export function getAncestors(file: TAbstractFile): TAbstractFile[] { + const ancestors: TAbstractFile[] = []; + let ancestor: TAbstractFile | null = file; + while (ancestor) { + ancestors.push(ancestor); + if (file instanceof TFolder && file.isRoot()) { + break; + } + ancestor = ancestor.parent; + } + ancestors.reverse(); + return ancestors; +} + +export function isEqualToOrChildOf(file1: TAbstractFile, file2: TAbstractFile): boolean { + if (file1 == file2) { + return true; + } + if (file2 instanceof TFolder && file2.isRoot()) { + return true; + } + let ancestor = file1.parent; + while (true) { + if (ancestor == file2) { + return true; + } + if (ancestor) { + if (ancestor.isRoot()) { + return false; + } + ancestor = ancestor.parent + } + } +} + +export function getFile(app: App): TAbstractFile { + return app.workspace.getActiveFile() ?? app.vault.getRoot(); +} + +////////////////////// +// Cache & metadata // +////////////////////// + +export function getSectionCacheFromPos(cache: CachedMetadata, pos: number, type: string): SectionCache | undefined { + // pos: CodeMirror offset units + if (cache.sections) { + const sectionCache = Object.values(cache.sections).find((sectionCache) => + sectionCache.type == type + && (sectionCache.position.start.offset == pos || sectionCache.position.end.offset == pos) + ); + return sectionCache; + } +} + +export function getSectionCacheOfDOM(el: HTMLElement, type: string, view: EditorView, cache: CachedMetadata) { + const pos = view.posAtDOM(el); + return getSectionCacheFromPos(cache, pos, type); +} + +export function getSectionCacheFromMouseEvent(event: MouseEvent, type: string, view: EditorView, cache: CachedMetadata) { + const pos = view.posAtCoords(event) ?? view.posAtCoords(event, false); + return getSectionCacheFromPos(cache, pos, type); +} + +export function getProperty(app: App, file: TFile, name: string) { + return app.metadataCache.getFileCache(file)?.frontmatter?.[name]; +} + +export function getPropertyLink(app: App, file: TFile, name: string) { + const cache = app.metadataCache.getFileCache(file); + if (cache?.frontmatterLinks) { + for (const link of cache.frontmatterLinks) { + if (link.key == name) { + return link; + } + } + } +} + +export function getPropertyOrLinkTextInProperty(app: App, file: TFile, name: string) { + return getPropertyLink(app, file, name)?.link ?? getProperty(app, file, name); +} + +export function generateBlockID(cache: CachedMetadata, length: number = 6): string { + let id = ''; + + while (true) { + // Reference: https://stackoverflow.com/a/58326357/13613783 + id = [...Array(length)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''); + if (cache?.blocks && id in cache.blocks) { + continue; + } else { + break; + } + } + return id; +} + +export function resolveLinktext(app: App, linktext: string, sourcePath: string): { file: TFile, subpathResult: HeadingSubpathResult | BlockSubpathResult | null } | null { + const { path, subpath } = parseLinktext(linktext); + const targetFile = app.metadataCache.getFirstLinkpathDest(path, sourcePath); + if (!targetFile) return null; + const targetCache = app.metadataCache.getFileCache(targetFile); + if (!targetCache) return null; + const result = resolveSubpath(targetCache, subpath); + return { file: targetFile, subpathResult: result }; +} + + +/////////////////// +// Markdown view // +/////////////////// + +export function getMarkdownPreviewViewEl(view: MarkdownView) { + return Array.from(view.previewMode.containerEl.children).find((child) => child.matches(".markdown-preview-view")); +} + +export function getMarkdownSourceViewEl(view: MarkdownView) { + const firstCandidate = view.editor.cm?.dom.parentElement; + if (firstCandidate) return firstCandidate; + const secondCandidate = view.previewMode.containerEl.previousSibling; + if (secondCandidate instanceof HTMLElement && secondCandidate.matches(".markdown-source-view")) { + return secondCandidate; + } +} + +export async function openFileAndSelectPosition(app: App, file: TFile, position: Pos, ...leafArgs: LeafArgs) { + // @ts-ignore + const leaf = app.workspace.getLeaf(...leafArgs); + await leaf.openFile(file); + if (leaf.view instanceof MarkdownView) { + // Editing view + const editor = leaf.view.editor; + const from = locToEditorPosition(position.start); + const to = locToEditorPosition(position.end); + + editor.setSelection(from, to); + editor.scrollIntoView({ from, to }, true); + + // Reading view: thank you NothingIsLost (https://discord.com/channels/686053708261228577/840286264964022302/952218718711189554) + leaf.view.setEphemeralState({ line: position.start.line }); + } +} + +export function findBlockFromReadingViewDom(sizerEl: HTMLElement, cb: (div: HTMLElement, index: number) => boolean): HTMLElement | undefined { + let index = 0; + for (const div of sizerEl.querySelectorAll(':scope > div')) { + if (div.classList.contains('markdown-preview-pusher')) continue; + if (div.classList.contains('mod-header')) continue; + if (div.classList.contains('mod-footer')) continue; + + if (cb(div, index++)) return div; + } +} + +/** + * Given a HTMLElement passed to a MarkdownPostProcessor, check if the current context is PDF export of not. + */ +export function isPdfExport(el: HTMLElement): boolean { + // el.classList.contains('markdown-rendered') is true not only for PDf export + // but also CM6 decorations in Live Preview whose widgets are rendered by MarkdownRenderer. + // So we need to check '.print', too. + // el.closest('[src]') === null is necessary to exclude embeds inside a note exported to PDF. + // return el.closest('.print') !== null && el.closest('[src]') === null && el.classList.contains('markdown-rendered'); + + // Come to think about it, just the following would suffice: + return (el.parentElement?.classList.contains('print') ?? false) && el.matches('.markdown-preview-view.markdown-rendered'); +} + +//////////// +// Others // +//////////// + +// compare the version of given plugin and the required version +export function isPluginOlderThan(plugin: Plugin, version: string): boolean { + return plugin.manifest.version.localeCompare(version, undefined, { numeric: true }) < 0; +} + +export function getModifierNameInPlatform(mod: Modifier): string { + if (mod == "Mod") { + return Platform.isMacOS || Platform.isIosApp ? "⌘" : "ctrl"; + } + if (mod == "Shift") { + return "shift"; + } + if (mod == "Alt") { + return Platform.isMacOS || Platform.isIosApp ? "⌥" : "alt"; + } + if (mod == "Meta") { + return Platform.isMacOS || Platform.isIosApp ? "⌘" : Platform.isWin ? "win" : "meta"; + } + return "ctrl"; +} + + +export class MutationObservingChild extends Component { + observer: MutationObserver; + + constructor(public targetEl: HTMLElement, public callback: MutationCallback, public options: MutationObserverInit) { + super(); + this.observer = new MutationObserver(callback); + } + + onload() { + this.observer.observe(this.targetEl, this.options); + } + + onunload() { + this.observer.disconnect(); + } +} diff --git a/src/utils/parse.ts b/src/utils/parse.ts new file mode 100644 index 0000000..e6b19f4 --- /dev/null +++ b/src/utils/parse.ts @@ -0,0 +1,115 @@ +import { THEOREM_LIKE_ENV_IDs, THEOREM_LIKE_ENV_PREFIXES, THEOREM_LIKE_ENV_PREFIX_ID_MAP, TheoremLikeEnvID, TheoremLikeEnvPrefix } from "env"; +import { FoldOption, MinimalTheoremCalloutSettings } from "settings/settings"; + +export const THEOREM_CALLOUT_PATTERN = new RegExp( + `> *\\[\\! *(?${THEOREM_LIKE_ENV_IDs.join('|')}|${THEOREM_LIKE_ENV_PREFIXES.join('|')}|math) *(\\|(?.*?))?\\](?[+-])?(? .*)?`, + 'i' +); + +export function matchTheoremCallout(line: string): RegExpExecArray | null { + return THEOREM_CALLOUT_PATTERN.exec(line) +} + +/** > [!type|HERE IS METADATA] + * a.k.a the "data-callout-metadata" attribute + */ +export function parseTheoremCalloutMetadata(metadata: string) { + let number = metadata.trim(); + if (!number) number = 'auto'; + else if (number === '*') number = ''; + return number; +} + +/** + * + * @param line + * @param excludeExample if true, then ""> [!example]" will not be treated as a theorem callout but as the built-in example callout. + * @returns + */ +export function readTheoremCalloutSettings(line: string, excludeExample: boolean = false): MinimalTheoremCalloutSettings & { legacy: boolean } | undefined { + const rawSettings = line.match(THEOREM_CALLOUT_PATTERN)?.groups as { type: string, number?: string, title?: string, fold?: string } | undefined; + if (!rawSettings) return; + + let type = rawSettings.type.trim().toLowerCase(); + + // TODO: rewrite using _readTheoremCalloutSettings + + if (type === 'math' && rawSettings.number) { + // legacy format + const settings = JSON.parse(rawSettings.number) as MinimalTheoremCalloutSettings & { legacy: boolean }; + settings.legacy = true; + return settings; + } + + // new format + if (excludeExample && type === 'example') return undefined; + + if (type.length <= 4) { // use length to avoid iterating over all the prefixes + // convert a prefix to an ID (e.g. "thm" -> "theorem") + type = THEOREM_LIKE_ENV_PREFIX_ID_MAP[type as TheoremLikeEnvPrefix]; + } + const number = parseTheoremCalloutMetadata(rawSettings.number ?? ''); + + let title: string | undefined = rawSettings.title?.trim(); + if (title === '') title = undefined; + + const fold = (rawSettings.fold?.trim() ?? '') as FoldOption; + + return { type, number, title, fold, legacy: false }; +} + +export function _readTheoremCalloutSettings(callout: {type: string, metadata: string}, excludeExample: boolean = false): { type: string, number: string, legacy: boolean } | undefined { + let type = callout.type; + + if (callout.type === 'math' && callout.metadata) { + // legacy format + const settings = JSON.parse(callout.metadata) as MinimalTheoremCalloutSettings & { legacy: boolean }; + settings.legacy = true; + return settings; + } + + // new format + if (excludeExample && callout.type === 'example') return undefined; + + if (callout.type.length <= 4) { // use length to avoid iterating over all the prefixes + // convert a prefix to an ID (e.g. "thm" -> "theorem") + type = THEOREM_LIKE_ENV_PREFIX_ID_MAP[callout.type as TheoremLikeEnvPrefix]; + } + const number = parseTheoremCalloutMetadata(callout.metadata); + + return { type, number, legacy: false }; +} + + +export function trimMathText(text: string) { + return text.match(/\$\$([\s\S]*)\$\$/)?.[1].trim() ?? text; +} + +export function parseLatexComment(line: string): { nonComment: string, comment: string } { + const match = line.match(/(?<!\\)%/); + if (match?.index !== undefined) { + return { nonComment: line.substring(0, match.index), comment: line.substring(match.index + 1) } + } + return { nonComment: line, comment: '' }; +} + +/** Parse the given markdown text and returns all comments in it as an array of lines. */ +export function parseMarkdownComment(markdown: string): string[] { + const comments: string[] = []; + const pattern = /%%([\s\S]*?)%%/g; + let result; + while (result = pattern.exec(markdown)) { + for (let line of result[1].split('\n')) { + line = line.trim(); + if (line) comments.push(line); + } + } + return comments; +} + +/** Parse an one-line YAML-like string into a key-value pair. */ +export function parseYamlLike(line: string): Record<string, string | undefined> | null { + const result = line.match(/^(?<key>.*?):(?<value>.*)$/)?.groups; + if (!result) return null; + return { [result.key.trim()]: result.value.trim() }; +} diff --git a/src/utils/plugin.ts b/src/utils/plugin.ts new file mode 100644 index 0000000..1493f0a --- /dev/null +++ b/src/utils/plugin.ts @@ -0,0 +1,168 @@ +import LatexReferencer from "main"; +import { CachedMetadata, Editor, MarkdownFileInfo, MarkdownView, Notice, TAbstractFile, TFile } from "obsidian"; +import { DEFAULT_SETTINGS, MathContextSettings, MinimalTheoremCalloutSettings, ResolvedMathSettings, TheoremCalloutSettings } from "settings/settings"; +import { generateBlockID, getAncestors, getFile } from "./obsidian"; +import { EquationBlock, MarkdownBlock, MarkdownPage, TheoremCalloutBlock } from "index/typings/markdown"; +import { getIO } from "file-io"; +import { splitIntoLines } from "./general"; +import { THEOREM_LIKE_ENV_IDs, THEOREM_LIKE_ENV_PREFIXES } from "env"; + + +export function resolveSettings(settings: MinimalTheoremCalloutSettings, plugin: LatexReferencer, currentFile: TAbstractFile): ResolvedMathSettings; +export function resolveSettings(settings: undefined, plugin: LatexReferencer, currentFile: TAbstractFile): Required<MathContextSettings>; +export function resolveSettings(settings: MinimalTheoremCalloutSettings | undefined, plugin: LatexReferencer, currentFile: TAbstractFile): Required<MathContextSettings> { + /** Resolves settings. Does not overwride, but returns a new settings object. + * Returned settings can be either + * - ResolvedMathContextSettings or + * - Required<MathContextSettings> & Partial<TheoremCalloutSettings>. + * */ + const resolvedSettings = Object.assign({}, DEFAULT_SETTINGS); + const ancestors = getAncestors(currentFile); + for (const ancestor of ancestors) { + Object.assign(resolvedSettings, plugin.settings[ancestor.path]); + } + Object.assign(resolvedSettings, settings); + return resolvedSettings; +} + +export function getProfile(plugin: LatexReferencer, file: TFile) { + const settings = resolveSettings(undefined, plugin, file); + const profile = plugin.extraSettings.profiles[settings.profile]; + return profile; +} + +export function getProfileByID(plugin: LatexReferencer, profileID: string) { + const profile = plugin.extraSettings.profiles[profileID]; + return profile; +} + +export function staticifyEqNumber(plugin: LatexReferencer, file: TFile) { + const page = plugin.indexManager.index.load(file.path); + if (!MarkdownPage.isMarkdownPage(page)) { + new Notice(`Failed to fetch the metadata of file ${file.path}.`); + return; + } + const io = getIO(plugin, file); + for (const block of page.$blocks.values()) { + if (block instanceof EquationBlock && block.$printName !== null) { + io.setRange( + block.$pos, + `$$\n${block.$mathText} \\tag{${block.$printName.slice(1, -1)}}\n$$` + ); + } + } +} + +export async function insertBlockIdIfNotExist(plugin: LatexReferencer, targetFile: TFile, cache: CachedMetadata, block: MarkdownBlock, length: number = 6): Promise<{ id: string, lineAdded: number } | undefined> { + // Make sure the section cache is fresh enough! + if (!(cache?.sections)) return; + + if (block.$blockId) return { id: block.$blockId, lineAdded: 0 }; + + // The section has no block ID, so let's create a new one + const id = generateBlockID(cache, length); + // and insert it + const io = getIO(plugin, targetFile); + await io.insertLine(block.$position.end + 1, "^" + id); + await io.insertLine(block.$position.end + 1, "") + return { id, lineAdded: 2 }; +} + +export function increaseQuoteLevel(content: string): string { + let lines = content.split("\n"); + lines = lines.map((line) => "> " + line); + return lines.join("\n"); +} + +/** + * Correctly insert a display math even inside callouts or quotes. + */ +export function insertDisplayMath(editor: Editor) { + const cursorPos = editor.getCursor(); + const line = editor.getLine(cursorPos.line).trimStart(); + const nonQuoteMatch = line.match(/[^>\s]/); + + const head = nonQuoteMatch?.index ?? line.length; + const quoteLevel = line.slice(0, head).match(/>\s*/g)?.length ?? 0; + let insert = "$$\n" + "> ".repeat(quoteLevel) + "\n" + "> ".repeat(quoteLevel) + "$$"; + + editor.replaceRange(insert, cursorPos); + cursorPos.line += 1; + cursorPos.ch = quoteLevel * 2; + editor.setCursor(cursorPos); +} + +export async function rewriteTheoremCalloutFromV1ToV2(plugin: LatexReferencer, file: TFile) { + const { app, indexManager } = plugin; + + const page = await indexManager.reload(file); + await app.vault.process(file, (data) => convertTheoremCalloutFromV1ToV2(data, page)); +} + + +export const convertTheoremCalloutFromV1ToV2 = (data: string, page: MarkdownPage) => { + const lines = data.split('\n'); + const newLines = [...lines]; + let lineAdded = 0; + + for (const section of page.$sections) { + for (const block of section.$blocks) { + if (!TheoremCalloutBlock.isTheoremCalloutBlock(block)) continue; + if (!block.$v1) continue + + const newHeadLines = [generateTheoremCalloutFirstLine({ + type: block.$settings.type, + number: block.$settings.number, + title: block.$settings.title + })]; + const legacySettings = block.$settings as any; + if (legacySettings.label) newHeadLines.push(`> %% label: ${legacySettings.label} %%`); + if (legacySettings.setAsNoteMathLink) newHeadLines.push(`> %% main %%`); + + newLines.splice(block.$position.start + lineAdded, 1, ...newHeadLines); + + lineAdded += newHeadLines.length - 1; + } + } + + return newLines.join('\n'); +} + +export function generateTheoremCalloutFirstLine(config: TheoremCalloutSettings): string { + const metadata = config.number === 'auto' ? '' : config.number === '' ? '|*' : `|${config.number}`; + let firstLine = `> [!${config.type}${metadata}]${config.fold ?? ''}${config.title ? ' ' + config.title : ''}` + if (config.label) firstLine += `\n> %% label: ${config.label} %%`; + return firstLine; +} + +export function insertTheoremCallout(editor: Editor, config: TheoremCalloutSettings): void { + const selection = editor.getSelection(); + const cursorPos = editor.getCursor(); + + const firstLine = generateTheoremCalloutFirstLine(config); + + if (selection) { + const nLines = splitIntoLines(selection).length; + editor.replaceSelection(firstLine + '\n' + increaseQuoteLevel(selection)); + cursorPos.line += nLines; + } else { + editor.replaceRange(firstLine + '\n> ', cursorPos) + cursorPos.line += 1; + } + + if (config.label) cursorPos.line += 1; + cursorPos.ch = 2; + editor.setCursor(cursorPos); +} + +export function isTheoremCallout(plugin: LatexReferencer, type: string) { + if (plugin.extraSettings.excludeExampleCallout && type === 'example') return false; + return (THEOREM_LIKE_ENV_IDs as unknown as string[]).includes(type) || (THEOREM_LIKE_ENV_PREFIXES as unknown as string[]).includes(type) || type === 'math' +} + +export function insertProof(plugin: LatexReferencer, editor: Editor, context: MarkdownView | MarkdownFileInfo) { + const settings = resolveSettings(undefined, plugin, context.file ?? getFile(plugin.app)); + const cursor = editor.getCursor(); + editor.replaceRange(`\`${settings.beginProof}\`\n\n\`${settings.endProof}\``, cursor); + editor.setCursor({ line: cursor.line + 1, ch: 0 }); +} diff --git a/src/utils/render.ts b/src/utils/render.ts new file mode 100644 index 0000000..c900d84 --- /dev/null +++ b/src/utils/render.ts @@ -0,0 +1,50 @@ +import { Component, MarkdownRenderer, renderMath } from "obsidian"; + +export function renderTextWithMath(source: string): (HTMLElement | string)[] { + // Obsidian API's renderMath only can render math itself, but not a text with math in it. + // e.g., it can handle "\\sqrt{x}", but cannot "$\\sqrt{x}$ is a square root" + + const elements: (HTMLElement | string)[] = []; + + const mathPattern = /\$(.*?[^\s])\$/g; + let result; + let textFrom = 0; + let textTo = 0; + while ((result = mathPattern.exec(source)) !== null) { + const mathString = result[1]; + textTo = result.index; + if (textTo > textFrom) { + elements.push(source.slice(textFrom, textTo)); + } + textFrom = mathPattern.lastIndex; + + const mathJaxEl = renderMath(mathString, false); + + const mathSpan = createSpan({ cls: ["math", "math-inline", "is-loaded"] }); + mathSpan.replaceChildren(mathJaxEl); + elements.push(mathSpan); + } + + if (textFrom < source.length) { + elements.push(source.slice(textFrom)); + } + + return elements; +} + +/** + * Easy-to-use version of MarkdownRenderer.renderMarkdown. + * @param markdown + * @param sourcePath + * @param component - Typically you can just pass the plugin instance. + * @returns + */ +export async function renderMarkdown(markdown: string, sourcePath: string, component: Component): Promise<NodeList | undefined> { + const el = createSpan(); + await MarkdownRenderer.renderMarkdown(markdown, el, sourcePath, component); + for (const child of el.children) { + if (child.tagName == "P") { + return child.childNodes; + } + } +} diff --git a/styles.scss b/styles.scss new file mode 100644 index 0000000..2fec833 --- /dev/null +++ b/styles.scss @@ -0,0 +1,21 @@ +@use 'styles/main'; +@use 'styles/framed'; +@use 'styles/plain'; +@use 'styles/mathwiki'; +@use 'styles/vivid'; + +:has(> .theorem-callout-framed) { + @include framed.framed(); +} + +:has(> .theorem-callout-plain) { + @include plain.plain(); +} + +:has(> .theorem-callout-mathwiki) { + @include mathwiki.mathwiki(); +} + +:has(> .theorem-callout-vivid) { + @include vivid.vivid(); +} diff --git a/styles/framed.scss b/styles/framed.scss new file mode 100644 index 0000000..e13479f --- /dev/null +++ b/styles/framed.scss @@ -0,0 +1,32 @@ +@mixin framed { + /* + If you're going to use this file as a CSS snippet, only include the code between + the START and END comments to your snippet file. + */ + /* START */ + .theorem-callout { + --callout-color: var(--text-normal); + background-color: rgb(0, 0, 0, 0); + border: solid var(--border-width); + border-radius: var(--size-2-3); + font-family: CMU Serif, Times, Noto Serif JP; + } + + .theorem-callout .callout-icon { + display: none; + } + + .theorem-callout-main-title { + font-family: CMU Serif, Times, Noto Sans JP; + font-weight: bolder; + } + + .theorem-callout-subtitle { + font-weight: normal; + } + + :not(.theorem-callout-axiom):not(.theorem-callout-definition):not(.theorem-callout-remark).theorem-callout-en .callout-content { + font-style: italic; + } + /* END */ +} \ No newline at end of file diff --git a/styles/main.css b/styles/main.css new file mode 100644 index 0000000..b678f45 --- /dev/null +++ b/styles/main.css @@ -0,0 +1,156 @@ +.math-booster-new-feature > p { + color: var(--text-warning); + margin: 0; +} + +.math-booster-version-2-release-note-modal { + --table-border-width: 2px; +} + +.editor-suggest-setting-indented-heading { + margin-left: var(--size-4-1); +} +.editor-suggest-setting-indented-heading + .setting-item, +.editor-suggest-setting-indented-heading + .setting-item + .setting-item { + margin-left: var(--size-4-3); +} + +.math-booster-modal-top { + padding: var(--size-4-3); +} + +.math-booster-modal-top .setting-item { + border-top: none; + border-bottom: 1px solid var(--background-modifier-border); +} + +.math-booster-setting-item-description { + padding-bottom: 0.75em; +} + +.math-booster-search-item-description { + color: var(--text-faint); +} + +.math-booster-backlink-modal { + width: var(--file-line-width); +} + +.math-booster-backlink-preview { + border: var(--border-width) solid var(--background-modifier-border); + border-radius: var(--radius-s); + padding: var(--size-4-6); +} + +.math-booster-begin-proof { + padding-right: 10px; + font-family: CMU Serif, Times, Noto Serif JP; + font-weight: bold; +} + +.math-booster-begin-proof-en { + font-style: italic; +} + + +.math-booster-end-proof { + float: right; +} + +.math-booster-add-profile { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.math-booster-add-profile > input { + width: 200px; +} + +.math-booster-button-container { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: var(--size-4-2); + padding: var(--size-4-2); +} + +.theorem-callout { + position: relative; +} + +.theorem-callout-setting-button { + padding-bottom: var(--size-2-2); + padding-right: var(--size-2-3); + position: absolute; + right: var(--size-2-2); + bottom: var(--size-2-2); + opacity: 0; +} + +.theorem-callout:hover .theorem-callout-setting-button { + transition: 0s; + opacity: 1 +} + +.theorem-callout-font-family-inherit { + font-family: inherit !important; +} + +.math-booster-title-form, +.math-booster-label-form { + width: 300px; +} + + +/* The code below was taken from the Latex Suite plugin (https://github.com/artisticat1/obsidian-latex-suite/blob/a5914c70c16d5763a182ec51d9716110b40965cf/styles.css) and adapted. + +MIT License + +Copyright (c) 2022 artisticat1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +.math-booster-dependency-validation { + color: white; + display: inline-block; + border-radius: 1em; + margin-right: var(--size-3); + cursor: default; + pointer-events: none; +} + +.math-booster-dependency-validation svg { + width: 16px !important; + height: 16px !important; +} + +.math-booster-dependency-validation.valid { + background-color: #7dc535; + visibility: visible; +} + +.theme-dark .math-booster-dependency-validation.valid { + background-color: #588b24; +} + +.math-booster-dependency-validation.invalid { + background-color: #ea5555; + visibility: visible; +} diff --git a/styles/mathwiki.scss b/styles/mathwiki.scss new file mode 100644 index 0000000..535dccd --- /dev/null +++ b/styles/mathwiki.scss @@ -0,0 +1,49 @@ +@mixin mathwiki { + /* + If you're going to use this file as a CSS snippet, only include the code between + the START and END comments to your snippet file. + */ + /* START */ + .theorem-callout { + --callout-color: 248, 248, 255; + font-family: CMU Serif, Times, Noto Serif JP; + } + + .theorem-callout .callout-title-inner { + padding-left: 5px; + } + + .theorem-callout-subtitle { + font-weight: normal; + } + + .theorem-callout-en .callout-content { + font-style: italic; + } + + .theorem-callout-axiom { + /* Font Awesome: lock */ + --callout-icon: '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock" class="svg-inline--fa fa-lock fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"></path></svg>'; + } + + .theorem-callout-definition { + /* Font Awesome: book */ + --callout-icon: '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"></path></svg>'; + } + + .theorem-callout-theorem { + /* Font Awesome: magic */ + --callout-icon: '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magic" class="svg-inline--fa fa-magic fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M224 96l16-32 32-16-32-16-16-32-16 32-32 16 32 16 16 32zM80 160l26.66-53.33L160 80l-53.34-26.67L80 0 53.34 53.33 0 80l53.34 26.67L80 160zm352 128l-26.66 53.33L352 368l53.34 26.67L432 448l26.66-53.33L512 368l-53.34-26.67L432 288zm70.62-193.77L417.77 9.38C411.53 3.12 403.34 0 395.15 0c-8.19 0-16.38 3.12-22.63 9.38L9.38 372.52c-12.5 12.5-12.5 32.76 0 45.25l84.85 84.85c6.25 6.25 14.44 9.37 22.62 9.37 8.19 0 16.38-3.12 22.63-9.37l363.14-363.15c12.5-12.48 12.5-32.75 0-45.24zM359.45 203.46l-50.91-50.91 86.6-86.6 50.91 50.91-86.6 86.6z"></path></svg>'; + } + + .theorem-callout-proposition { + /* Font Awesome: calculator */ + --callout-icon: '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="calculator" class="svg-inline--fa fa-calculator fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 0H48C22.4 0 0 22.4 0 48v416c0 25.6 22.4 48 48 48h352c25.6 0 48-22.4 48-48V48c0-25.6-22.4-48-48-48zM128 435.2c0 6.4-6.4 12.8-12.8 12.8H76.8c-6.4 0-12.8-6.4-12.8-12.8v-38.4c0-6.4 6.4-12.8 12.8-12.8h38.4c6.4 0 12.8 6.4 12.8 12.8v38.4zm0-128c0 6.4-6.4 12.8-12.8 12.8H76.8c-6.4 0-12.8-6.4-12.8-12.8v-38.4c0-6.4 6.4-12.8 12.8-12.8h38.4c6.4 0 12.8 6.4 12.8 12.8v38.4zm128 128c0 6.4-6.4 12.8-12.8 12.8h-38.4c-6.4 0-12.8-6.4-12.8-12.8v-38.4c0-6.4 6.4-12.8 12.8-12.8h38.4c6.4 0 12.8 6.4 12.8 12.8v38.4zm0-128c0 6.4-6.4 12.8-12.8 12.8h-38.4c-6.4 0-12.8-6.4-12.8-12.8v-38.4c0-6.4 6.4-12.8 12.8-12.8h38.4c6.4 0 12.8 6.4 12.8 12.8v38.4zm128 128c0 6.4-6.4 12.8-12.8 12.8h-38.4c-6.4 0-12.8-6.4-12.8-12.8V268.8c0-6.4 6.4-12.8 12.8-12.8h38.4c6.4 0 12.8 6.4 12.8 12.8v166.4zm0-256c0 6.4-6.4 12.8-12.8 12.8H76.8c-6.4 0-12.8-6.4-12.8-12.8V76.8C64 70.4 70.4 64 76.8 64h294.4c6.4 0 12.8 6.4 12.8 12.8v102.4z"></path></svg>'; + } + + .theorem-callout-example { + /* Font Awesome: anchor */ + --callout-icon: '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="anchor" class="svg-inline--fa fa-anchor fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M12.971 352h32.394C67.172 454.735 181.944 512 288 512c106.229 0 220.853-57.38 242.635-160h32.394c10.691 0 16.045-12.926 8.485-20.485l-67.029-67.029c-4.686-4.686-12.284-4.686-16.971 0l-67.029 67.029c-7.56 7.56-2.206 20.485 8.485 20.485h35.146c-20.29 54.317-84.963 86.588-144.117 94.015V256h52c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-52v-5.47c37.281-13.178 63.995-48.725 64-90.518C384.005 43.772 341.605.738 289.37.01 235.723-.739 192 42.525 192 96c0 41.798 26.716 77.35 64 90.53V192h-52c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h52v190.015c-58.936-7.399-123.82-39.679-144.117-94.015h35.146c10.691 0 16.045-12.926 8.485-20.485l-67.029-67.029c-4.686-4.686-12.284-4.686-16.971 0L4.485 331.515C-3.074 339.074 2.28 352 12.971 352zM288 64c17.645 0 32 14.355 32 32s-14.355 32-32 32-32-14.355-32-32 14.355-32 32-32z"></path></svg>'; + } + /* END */ +} \ No newline at end of file diff --git a/styles/plain.scss b/styles/plain.scss new file mode 100644 index 0000000..b493c12 --- /dev/null +++ b/styles/plain.scss @@ -0,0 +1,34 @@ +@mixin plain { + /* + If you're going to use this file as a CSS snippet, only include the code between + the START and END comments to your snippet file. + */ + /* START */ + .theorem-callout { + --callout-color: var(--text-normal); + background-color: rgb(0, 0, 0, 0); + padding-left: 0; + padding-right: 0; + border: none; + box-shadow: none; + font-family: CMU Serif, Times, Noto Serif JP; + } + + .theorem-callout .callout-icon { + display: none; + } + + .theorem-callout-main-title { + font-family: CMU Serif, Times, Noto Sans JP; + font-weight: bolder; + } + + .theorem-callout-subtitle { + font-weight: normal; + } + + :not(.theorem-callout-axiom):not(.theorem-callout-definition):not(.theorem-callout-remark).theorem-callout-en .callout-content { + font-style: italic; + } + /* END */ +} \ No newline at end of file diff --git a/styles/vivid.scss b/styles/vivid.scss new file mode 100644 index 0000000..1eba1b1 --- /dev/null +++ b/styles/vivid.scss @@ -0,0 +1,44 @@ +@mixin vivid { + + /* + If you're going to use this file as a CSS snippet, only include the code between + the START and END comments to your snippet file. + */ + /* START */ + .theorem-callout { + --callout-color: 238, 15, 149; + border-top: none; + border-bottom: none; + border-left: var(--size-2-2) solid rgb(var(--callout-color)); + border-right: none; + border-radius: 0px; + box-shadow: none; + padding: 0px; + font-family: CMU Serif, Times, Noto Serif JP; + } + + .theorem-callout .callout-title { + padding: var(--size-2-3); + padding-left: var(--size-4-3); + } + + .theorem-callout .callout-icon { + display: none; + } + + .theorem-callout .callout-title-inner { + font-family: Inter; + font-weight: normal; + color: rgb(var(--callout-color)); + } + + .theorem-callout-subtitle { + font-weight: lighter; + } + + .theorem-callout .callout-content { + background-color: var(--background-primary); + padding: 1px 20px 2px 20px; + } + /* END */ +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..723fd8c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": [ + "DOM", + "ES5", + "ES6", + "ES7", + "ES2021.String", + "DOM.Iterable", + "es2022" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "src/legacy/*.ts", + ] +} diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..d409fa0 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,14 @@ +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read minAppVersion from manifest.json and bump version to target version +let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json with target version and minAppVersion from manifest.json +let versions = JSON.parse(readFileSync("versions.json", "utf8")); +versions[targetVersion] = minAppVersion; +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..96c280b --- /dev/null +++ b/versions.json @@ -0,0 +1,5 @@ +{ + "0.3.0": "1.3.5", + "0.3.1": "1.3.5", + "0.3.2": "1.3.5" +} \ No newline at end of file