├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
└── workflows
│ └── release.yml
├── .gitignore
├── .npmrc
├── README.md
├── assets
└── Collapse-Node.gif
├── esbuild.config.mjs
├── manifest.json
├── package.json
├── pnpm-lock.yaml
├── src
├── ControlHeader.ts
├── index.ts
├── patchUtils.ts
├── types
│ ├── canvas.d.ts
│ ├── custom.d.ts
│ ├── event.d.ts
│ └── obsidian.d.ts
└── utils.ts
├── styles.css
├── tsconfig.json
├── version-bump.mjs
└── versions.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = tab
9 | indent_size = 4
10 | tab_width = 4
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | npm node_modules
2 | build
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "env": { "node": true },
5 | "plugins": [
6 | "@typescript-eslint"
7 | ],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended"
12 | ],
13 | "parserOptions": {
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "no-unused-vars": "off",
18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
19 | "@typescript-eslint/ban-ts-comment": "off",
20 | "no-prototype-builtins": "off",
21 | "@typescript-eslint/no-empty-function": "off"
22 | }
23 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: "[Bug]: "
4 | labels: [ "bug" ]
5 | body:
6 | - type: textarea
7 | id: bug-description
8 | attributes:
9 | label: Bug Description
10 | description: A clear and concise description of the bug.
11 | validations:
12 | required: true
13 | - type: textarea
14 | id: screenshot
15 | attributes:
16 | label: Relevant Screenshot
17 | description: If applicable, add screenshots or a screen recording to help explain your problem.
18 | - type: textarea
19 | id: reproduction-steps
20 | attributes:
21 | label: To Reproduce
22 | description: Steps to reproduce the problem
23 | placeholder: |
24 | For example:
25 | 1. Go to '...'
26 | 2. Click on '...'
27 | 3. Scroll down to '...'
28 | - type: input
29 | id: obsi-version
30 | attributes:
31 | label: Obsidian Version
32 | description: You can find the version in the *About* Tab of the settings.
33 | placeholder: 0.13.19
34 | validations:
35 | required: true
36 | - type: checkboxes
37 | id: checklist
38 | attributes:
39 | label: Checklist
40 | options:
41 | - label: I updated to the latest version of the plugin.
42 | required: true
43 |
44 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea
3 | title: "Feature Request: "
4 | labels: [ "feature request" ]
5 | body:
6 | - type: textarea
7 | id: feature-requested
8 | attributes:
9 | label: Feature Requested
10 | description: A clear and concise description of the feature.
11 | validations:
12 | required: true
13 | - type: textarea
14 | id: screenshot
15 | attributes:
16 | label: Relevant Screenshot
17 | description: If applicable, add screenshots or a screen recording to help explain the request.
18 | - type: checkboxes
19 | id: checklist
20 | attributes:
21 | label: Checklist
22 | options:
23 | - label: The feature would be useful to more users than just me.
24 | required: true
25 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Obsidian plugin
2 |
3 | on:
4 | release:
5 | types: [ created ]
6 |
7 | env:
8 | PLUGIN_NAME: obsidian-collapse-node
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Use Node.js
16 | uses: actions/setup-node@v3
17 | with:
18 | node-version: 16
19 | - name: Build
20 | id: build
21 | run: |
22 | npm install
23 | npm run build
24 | mkdir ${{ env.PLUGIN_NAME }}
25 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
26 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
27 | ls
28 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
29 | - name: Upload zip file
30 | id: upload-zip
31 | uses: actions/upload-release-asset@v1
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | with:
35 | upload_url: ${{ github.event.release.upload_url }}
36 | asset_path: ./${{ env.PLUGIN_NAME }}.zip
37 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
38 | asset_content_type: application/zip
39 |
40 | - name: Upload main.js
41 | id: upload-main
42 | uses: actions/upload-release-asset@v1
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 | with:
46 | upload_url: ${{ github.event.release.upload_url }}
47 | asset_path: ./main.js
48 | asset_name: main.js
49 | asset_content_type: text/javascript
50 |
51 | - name: Upload manifest.json
52 | id: upload-manifest
53 | uses: actions/upload-release-asset@v1
54 | env:
55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56 | with:
57 | upload_url: ${{ github.event.release.upload_url }}
58 | asset_path: ./manifest.json
59 | asset_name: manifest.json
60 | asset_content_type: application/json
61 |
62 | - name: Upload styles.css
63 | id: upload-css
64 | uses: actions/upload-release-asset@v1
65 | env:
66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67 | with:
68 | upload_url: ${{ github.event.release.upload_url }}
69 | asset_path: ./styles.css
70 | asset_name: styles.css
71 | asset_content_type: text/css
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # vscode
2 | .vscode
3 |
4 | # Intellij
5 | *.iml
6 | .idea
7 |
8 | # npm
9 | node_modules
10 |
11 | # Don't include the compiled main.js file in the repo.
12 | # They should be uploaded to GitHub releases instead.
13 | main.js
14 |
15 | # Exclude sourcemaps
16 | *.map
17 |
18 | # obsidian
19 | data.json
20 |
21 | # Exclude macOS Finder (System Explorer) View States
22 | .DS_Store
23 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Obsidian Collapse Node
2 |
3 | A plugin for [Obsidian](https://obsidian.md) that helps you collapse node in canvas.
4 |
5 | 
6 |
7 | ## Usage
8 |
9 | - Commands
10 | - `Fold All Nodes` - Collapses all nodes in the workspace
11 | - `Expand All Nodes` - Expands all nodes in the workspace
12 | - `Fold Selected Nodes` - Collapses only the selected nodes
13 | - `Expand Selected Nodes` - Expands only the selected nodes
14 | - Context menu in canvas
15 | - `Selection Menu` - Shows the context menu for selected nodes
16 | - `Node Menu` - Shows the context menu for a specific node or nodes
17 | - `Set Node Alias` - Set a custom alias for the node (shown in collapsed state)
18 | - `Set Node Thumbnail` - Set a custom thumbnail image for the node (shown in collapsed state)
19 | - `Remove Node Customizations` - Remove alias and thumbnail from the node
20 | - Direct Click on the Header
21 | - `Click` - Collapse or expand the node
22 | - Thumbnails and Aliases in Collapsed State
23 | - Enable in settings to show thumbnails and/or aliases when nodes are collapsed
24 | - For thumbnails: Add `thumbnail: path/to/image.jpg` or `thumbnail: https://example.com/image.jpg` to node metadata, or use the context menu
25 | - For aliases: Add `alias: Your Alias Text` to node metadata, use frontmatter aliases for file nodes, or use the context menu
26 |
27 | ## Installation
28 |
29 | - Not ready for market yet
30 | - Can be installed via the [Brat](https://github.com/TfTHacker/obsidian42-brat) plugin
31 | - Manual installation
32 |
33 | 1. Find the release page on this github page and click
34 | 2. Download the latest release zip file
35 | 3. Unzip it, copy the unzipped folder to the obsidian plugin folder, make sure there are main.js and manifest.json files
36 | in the folder
37 | 4. Restart obsidian (do not restart also, you have to refresh plugin list), in the settings interface to enable the
38 | plugin
39 | 5. Done!
40 |
41 | ## Support
42 |
43 | If you are enjoying this plugin then please support my work and enthusiasm by buying me a coffee
44 | on [https://www.buymeacoffee.com/boninall](https://www.buymeacoffee.com/boninall).
45 | .
46 |
47 |
48 |
--------------------------------------------------------------------------------
/assets/Collapse-Node.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quorafind/Obsidian-Collapse-Node/60a78514cd1481e35afafbc25beebe7e1d04d5cc/assets/Collapse-Node.gif
--------------------------------------------------------------------------------
/esbuild.config.mjs:
--------------------------------------------------------------------------------
1 | import esbuild from "esbuild";
2 | import process from "process";
3 | import builtins from "builtin-modules";
4 |
5 | const banner = `/*
6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
7 | if you want to view the source, please visit the github repository of this plugin
8 | */
9 | `;
10 |
11 | const prod = process.argv[2] === "production";
12 |
13 | esbuild
14 | .build({
15 | banner: {
16 | js: banner,
17 | },
18 | entryPoints: ["src/index.ts"],
19 | bundle: true,
20 | external: [
21 | "obsidian",
22 | "electron",
23 | "@codemirror/autocomplete",
24 | "@codemirror/collab",
25 | "@codemirror/commands",
26 | "@codemirror/language",
27 | "@codemirror/lint",
28 | "@codemirror/search",
29 | "@codemirror/state",
30 | "@codemirror/view",
31 | "@lezer/common",
32 | "@lezer/highlight",
33 | "@lezer/lr",
34 | ...builtins,
35 | ],
36 | format: "cjs",
37 | watch: !prod,
38 | target: "es2018",
39 | logLevel: "info",
40 | sourcemap: prod ? false : "inline",
41 | treeShaking: true,
42 | outfile: "main.js",
43 | })
44 | .catch(() => process.exit(1));
45 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "collapse-node",
3 | "name": "Collapse Node",
4 | "version": "2.0.0",
5 | "minAppVersion": "1.1.0",
6 | "description": "Collapse node in canvas.",
7 | "author": "Boninall",
8 | "authorUrl": "https://github.com/Quorafind",
9 | "fundingUrl": {
10 | "Buy Me a Coffee": "https://www.buymeacoffee.com/boninall",
11 | "爱发电": "https://afdian.net/a/boninall",
12 | "支付宝": "https://cdn.jsdelivr.net/gh/Quorafind/.github@main/IMAGE/%E6%94%AF%E4%BB%98%E5%AE%9D%E4%BB%98%E6%AC%BE%E7%A0%81.jpg"
13 | },
14 | "isDesktopOnly": false
15 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "collapse-node",
3 | "version": "2.0.0",
4 | "description": "A plugin for collapse node in canvas",
5 | "main": "main.js",
6 | "scripts": {
7 | "dev": "node esbuild.config.mjs",
8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
9 | "version": "node version-bump.mjs && git add manifest.json versions.json"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "MIT",
14 | "devDependencies": {
15 | "@types/node": "^16.11.6",
16 | "@typescript-eslint/eslint-plugin": "5.29.0",
17 | "@typescript-eslint/parser": "5.29.0",
18 | "builtin-modules": "3.3.0",
19 | "esbuild": "0.14.47",
20 | "obsidian": "^1.8.7",
21 | "tslib": "2.4.0",
22 | "typescript": "4.7.4"
23 | },
24 | "dependencies": {
25 | "@codemirror/state": "^6.4.1",
26 | "@codemirror/view": "^6.28.1",
27 | "monkey-around": "^2.3.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | '@codemirror/state':
12 | specifier: ^6.4.1
13 | version: 6.4.1
14 | '@codemirror/view':
15 | specifier: ^6.28.1
16 | version: 6.28.1
17 | monkey-around:
18 | specifier: ^2.3.0
19 | version: 2.3.0
20 | devDependencies:
21 | '@types/node':
22 | specifier: ^16.11.6
23 | version: 16.18.46
24 | '@typescript-eslint/eslint-plugin':
25 | specifier: 5.29.0
26 | version: 5.29.0(@typescript-eslint/parser@5.29.0(eslint@8.48.0)(typescript@4.7.4))(eslint@8.48.0)(typescript@4.7.4)
27 | '@typescript-eslint/parser':
28 | specifier: 5.29.0
29 | version: 5.29.0(eslint@8.48.0)(typescript@4.7.4)
30 | builtin-modules:
31 | specifier: 3.3.0
32 | version: 3.3.0
33 | esbuild:
34 | specifier: 0.14.47
35 | version: 0.14.47
36 | obsidian:
37 | specifier: ^1.8.7
38 | version: 1.8.7(@codemirror/state@6.4.1)(@codemirror/view@6.28.1)
39 | tslib:
40 | specifier: 2.4.0
41 | version: 2.4.0
42 | typescript:
43 | specifier: 4.7.4
44 | version: 4.7.4
45 |
46 | packages:
47 |
48 | '@aashutoshrathi/word-wrap@1.2.6':
49 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
50 | engines: {node: '>=0.10.0'}
51 |
52 | '@codemirror/state@6.4.1':
53 | resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
54 |
55 | '@codemirror/view@6.28.1':
56 | resolution: {integrity: sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==}
57 |
58 | '@eslint-community/eslint-utils@4.4.0':
59 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
60 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
61 | peerDependencies:
62 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
63 |
64 | '@eslint-community/regexpp@4.8.0':
65 | resolution: {integrity: sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==}
66 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
67 |
68 | '@eslint/eslintrc@2.1.2':
69 | resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==}
70 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
71 |
72 | '@eslint/js@8.48.0':
73 | resolution: {integrity: sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==}
74 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
75 |
76 | '@humanwhocodes/config-array@0.11.10':
77 | resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
78 | engines: {node: '>=10.10.0'}
79 | deprecated: Use @eslint/config-array instead
80 |
81 | '@humanwhocodes/module-importer@1.0.1':
82 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
83 | engines: {node: '>=12.22'}
84 |
85 | '@humanwhocodes/object-schema@1.2.1':
86 | resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
87 | deprecated: Use @eslint/object-schema instead
88 |
89 | '@nodelib/fs.scandir@2.1.5':
90 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
91 | engines: {node: '>= 8'}
92 |
93 | '@nodelib/fs.stat@2.0.5':
94 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
95 | engines: {node: '>= 8'}
96 |
97 | '@nodelib/fs.walk@1.2.8':
98 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
99 | engines: {node: '>= 8'}
100 |
101 | '@types/codemirror@5.60.8':
102 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==}
103 |
104 | '@types/estree@1.0.1':
105 | resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
106 |
107 | '@types/json-schema@7.0.12':
108 | resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
109 |
110 | '@types/node@16.18.46':
111 | resolution: {integrity: sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==}
112 |
113 | '@types/tern@0.23.4':
114 | resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==}
115 |
116 | '@typescript-eslint/eslint-plugin@5.29.0':
117 | resolution: {integrity: sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==}
118 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
119 | peerDependencies:
120 | '@typescript-eslint/parser': ^5.0.0
121 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
122 | typescript: '*'
123 | peerDependenciesMeta:
124 | typescript:
125 | optional: true
126 |
127 | '@typescript-eslint/parser@5.29.0':
128 | resolution: {integrity: sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==}
129 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
130 | peerDependencies:
131 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
132 | typescript: '*'
133 | peerDependenciesMeta:
134 | typescript:
135 | optional: true
136 |
137 | '@typescript-eslint/scope-manager@5.29.0':
138 | resolution: {integrity: sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==}
139 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
140 |
141 | '@typescript-eslint/type-utils@5.29.0':
142 | resolution: {integrity: sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==}
143 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
144 | peerDependencies:
145 | eslint: '*'
146 | typescript: '*'
147 | peerDependenciesMeta:
148 | typescript:
149 | optional: true
150 |
151 | '@typescript-eslint/types@5.29.0':
152 | resolution: {integrity: sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==}
153 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
154 |
155 | '@typescript-eslint/typescript-estree@5.29.0':
156 | resolution: {integrity: sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==}
157 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
158 | peerDependencies:
159 | typescript: '*'
160 | peerDependenciesMeta:
161 | typescript:
162 | optional: true
163 |
164 | '@typescript-eslint/utils@5.29.0':
165 | resolution: {integrity: sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==}
166 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
167 | peerDependencies:
168 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
169 |
170 | '@typescript-eslint/visitor-keys@5.29.0':
171 | resolution: {integrity: sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==}
172 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
173 |
174 | acorn-jsx@5.3.2:
175 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
176 | peerDependencies:
177 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
178 |
179 | acorn@8.10.0:
180 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
181 | engines: {node: '>=0.4.0'}
182 | hasBin: true
183 |
184 | ajv@6.12.6:
185 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
186 |
187 | ansi-regex@5.0.1:
188 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
189 | engines: {node: '>=8'}
190 |
191 | ansi-styles@4.3.0:
192 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
193 | engines: {node: '>=8'}
194 |
195 | argparse@2.0.1:
196 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
197 |
198 | array-union@2.1.0:
199 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
200 | engines: {node: '>=8'}
201 |
202 | balanced-match@1.0.2:
203 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
204 |
205 | brace-expansion@1.1.11:
206 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
207 |
208 | braces@3.0.2:
209 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
210 | engines: {node: '>=8'}
211 |
212 | builtin-modules@3.3.0:
213 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
214 | engines: {node: '>=6'}
215 |
216 | callsites@3.1.0:
217 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
218 | engines: {node: '>=6'}
219 |
220 | chalk@4.1.2:
221 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
222 | engines: {node: '>=10'}
223 |
224 | color-convert@2.0.1:
225 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
226 | engines: {node: '>=7.0.0'}
227 |
228 | color-name@1.1.4:
229 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
230 |
231 | concat-map@0.0.1:
232 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
233 |
234 | cross-spawn@7.0.3:
235 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
236 | engines: {node: '>= 8'}
237 |
238 | debug@4.3.4:
239 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
240 | engines: {node: '>=6.0'}
241 | peerDependencies:
242 | supports-color: '*'
243 | peerDependenciesMeta:
244 | supports-color:
245 | optional: true
246 |
247 | deep-is@0.1.4:
248 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
249 |
250 | dir-glob@3.0.1:
251 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
252 | engines: {node: '>=8'}
253 |
254 | doctrine@3.0.0:
255 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
256 | engines: {node: '>=6.0.0'}
257 |
258 | esbuild-android-64@0.14.47:
259 | resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==}
260 | engines: {node: '>=12'}
261 | cpu: [x64]
262 | os: [android]
263 |
264 | esbuild-android-arm64@0.14.47:
265 | resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==}
266 | engines: {node: '>=12'}
267 | cpu: [arm64]
268 | os: [android]
269 |
270 | esbuild-darwin-64@0.14.47:
271 | resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==}
272 | engines: {node: '>=12'}
273 | cpu: [x64]
274 | os: [darwin]
275 |
276 | esbuild-darwin-arm64@0.14.47:
277 | resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==}
278 | engines: {node: '>=12'}
279 | cpu: [arm64]
280 | os: [darwin]
281 |
282 | esbuild-freebsd-64@0.14.47:
283 | resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==}
284 | engines: {node: '>=12'}
285 | cpu: [x64]
286 | os: [freebsd]
287 |
288 | esbuild-freebsd-arm64@0.14.47:
289 | resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==}
290 | engines: {node: '>=12'}
291 | cpu: [arm64]
292 | os: [freebsd]
293 |
294 | esbuild-linux-32@0.14.47:
295 | resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==}
296 | engines: {node: '>=12'}
297 | cpu: [ia32]
298 | os: [linux]
299 |
300 | esbuild-linux-64@0.14.47:
301 | resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==}
302 | engines: {node: '>=12'}
303 | cpu: [x64]
304 | os: [linux]
305 |
306 | esbuild-linux-arm64@0.14.47:
307 | resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==}
308 | engines: {node: '>=12'}
309 | cpu: [arm64]
310 | os: [linux]
311 |
312 | esbuild-linux-arm@0.14.47:
313 | resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==}
314 | engines: {node: '>=12'}
315 | cpu: [arm]
316 | os: [linux]
317 |
318 | esbuild-linux-mips64le@0.14.47:
319 | resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==}
320 | engines: {node: '>=12'}
321 | cpu: [mips64el]
322 | os: [linux]
323 |
324 | esbuild-linux-ppc64le@0.14.47:
325 | resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==}
326 | engines: {node: '>=12'}
327 | cpu: [ppc64]
328 | os: [linux]
329 |
330 | esbuild-linux-riscv64@0.14.47:
331 | resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==}
332 | engines: {node: '>=12'}
333 | cpu: [riscv64]
334 | os: [linux]
335 |
336 | esbuild-linux-s390x@0.14.47:
337 | resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==}
338 | engines: {node: '>=12'}
339 | cpu: [s390x]
340 | os: [linux]
341 |
342 | esbuild-netbsd-64@0.14.47:
343 | resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==}
344 | engines: {node: '>=12'}
345 | cpu: [x64]
346 | os: [netbsd]
347 |
348 | esbuild-openbsd-64@0.14.47:
349 | resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==}
350 | engines: {node: '>=12'}
351 | cpu: [x64]
352 | os: [openbsd]
353 |
354 | esbuild-sunos-64@0.14.47:
355 | resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==}
356 | engines: {node: '>=12'}
357 | cpu: [x64]
358 | os: [sunos]
359 |
360 | esbuild-windows-32@0.14.47:
361 | resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==}
362 | engines: {node: '>=12'}
363 | cpu: [ia32]
364 | os: [win32]
365 |
366 | esbuild-windows-64@0.14.47:
367 | resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==}
368 | engines: {node: '>=12'}
369 | cpu: [x64]
370 | os: [win32]
371 |
372 | esbuild-windows-arm64@0.14.47:
373 | resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==}
374 | engines: {node: '>=12'}
375 | cpu: [arm64]
376 | os: [win32]
377 |
378 | esbuild@0.14.47:
379 | resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==}
380 | engines: {node: '>=12'}
381 | hasBin: true
382 |
383 | escape-string-regexp@4.0.0:
384 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
385 | engines: {node: '>=10'}
386 |
387 | eslint-scope@5.1.1:
388 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
389 | engines: {node: '>=8.0.0'}
390 |
391 | eslint-scope@7.2.2:
392 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
393 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
394 |
395 | eslint-utils@3.0.0:
396 | resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
397 | engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
398 | peerDependencies:
399 | eslint: '>=5'
400 |
401 | eslint-visitor-keys@2.1.0:
402 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
403 | engines: {node: '>=10'}
404 |
405 | eslint-visitor-keys@3.4.3:
406 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
407 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
408 |
409 | eslint@8.48.0:
410 | resolution: {integrity: sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==}
411 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
412 | deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
413 | hasBin: true
414 |
415 | espree@9.6.1:
416 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
417 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
418 |
419 | esquery@1.5.0:
420 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
421 | engines: {node: '>=0.10'}
422 |
423 | esrecurse@4.3.0:
424 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
425 | engines: {node: '>=4.0'}
426 |
427 | estraverse@4.3.0:
428 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
429 | engines: {node: '>=4.0'}
430 |
431 | estraverse@5.3.0:
432 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
433 | engines: {node: '>=4.0'}
434 |
435 | esutils@2.0.3:
436 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
437 | engines: {node: '>=0.10.0'}
438 |
439 | fast-deep-equal@3.1.3:
440 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
441 |
442 | fast-glob@3.3.1:
443 | resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
444 | engines: {node: '>=8.6.0'}
445 |
446 | fast-json-stable-stringify@2.1.0:
447 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
448 |
449 | fast-levenshtein@2.0.6:
450 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
451 |
452 | fastq@1.15.0:
453 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
454 |
455 | file-entry-cache@6.0.1:
456 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
457 | engines: {node: ^10.12.0 || >=12.0.0}
458 |
459 | fill-range@7.0.1:
460 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
461 | engines: {node: '>=8'}
462 |
463 | find-up@5.0.0:
464 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
465 | engines: {node: '>=10'}
466 |
467 | flat-cache@3.1.0:
468 | resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==}
469 | engines: {node: '>=12.0.0'}
470 |
471 | flatted@3.2.7:
472 | resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
473 |
474 | fs.realpath@1.0.0:
475 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
476 |
477 | functional-red-black-tree@1.0.1:
478 | resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
479 |
480 | glob-parent@5.1.2:
481 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
482 | engines: {node: '>= 6'}
483 |
484 | glob-parent@6.0.2:
485 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
486 | engines: {node: '>=10.13.0'}
487 |
488 | glob@7.2.3:
489 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
490 | deprecated: Glob versions prior to v9 are no longer supported
491 |
492 | globals@13.21.0:
493 | resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==}
494 | engines: {node: '>=8'}
495 |
496 | globby@11.1.0:
497 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
498 | engines: {node: '>=10'}
499 |
500 | graphemer@1.4.0:
501 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
502 |
503 | has-flag@4.0.0:
504 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
505 | engines: {node: '>=8'}
506 |
507 | ignore@5.2.4:
508 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
509 | engines: {node: '>= 4'}
510 |
511 | import-fresh@3.3.0:
512 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
513 | engines: {node: '>=6'}
514 |
515 | imurmurhash@0.1.4:
516 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
517 | engines: {node: '>=0.8.19'}
518 |
519 | inflight@1.0.6:
520 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
521 | deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
522 |
523 | inherits@2.0.4:
524 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
525 |
526 | is-extglob@2.1.1:
527 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
528 | engines: {node: '>=0.10.0'}
529 |
530 | is-glob@4.0.3:
531 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
532 | engines: {node: '>=0.10.0'}
533 |
534 | is-number@7.0.0:
535 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
536 | engines: {node: '>=0.12.0'}
537 |
538 | is-path-inside@3.0.3:
539 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
540 | engines: {node: '>=8'}
541 |
542 | isexe@2.0.0:
543 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
544 |
545 | js-yaml@4.1.0:
546 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
547 | hasBin: true
548 |
549 | json-buffer@3.0.1:
550 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
551 |
552 | json-schema-traverse@0.4.1:
553 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
554 |
555 | json-stable-stringify-without-jsonify@1.0.1:
556 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
557 |
558 | keyv@4.5.3:
559 | resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==}
560 |
561 | levn@0.4.1:
562 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
563 | engines: {node: '>= 0.8.0'}
564 |
565 | locate-path@6.0.0:
566 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
567 | engines: {node: '>=10'}
568 |
569 | lodash.merge@4.6.2:
570 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
571 |
572 | lru-cache@6.0.0:
573 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
574 | engines: {node: '>=10'}
575 |
576 | merge2@1.4.1:
577 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
578 | engines: {node: '>= 8'}
579 |
580 | micromatch@4.0.5:
581 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
582 | engines: {node: '>=8.6'}
583 |
584 | minimatch@3.1.2:
585 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
586 |
587 | moment@2.29.4:
588 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
589 |
590 | monkey-around@2.3.0:
591 | resolution: {integrity: sha512-QWcCUWjqE/MCk9cXlSKZ1Qc486LD439xw/Ak8Nt6l2PuL9+yrc9TJakt7OHDuOqPRYY4nTWBAEFKn32PE/SfXA==}
592 |
593 | ms@2.1.2:
594 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
595 |
596 | natural-compare@1.4.0:
597 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
598 |
599 | obsidian@1.8.7:
600 | resolution: {integrity: sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==}
601 | peerDependencies:
602 | '@codemirror/state': ^6.0.0
603 | '@codemirror/view': ^6.0.0
604 |
605 | once@1.4.0:
606 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
607 |
608 | optionator@0.9.3:
609 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
610 | engines: {node: '>= 0.8.0'}
611 |
612 | p-limit@3.1.0:
613 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
614 | engines: {node: '>=10'}
615 |
616 | p-locate@5.0.0:
617 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
618 | engines: {node: '>=10'}
619 |
620 | parent-module@1.0.1:
621 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
622 | engines: {node: '>=6'}
623 |
624 | path-exists@4.0.0:
625 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
626 | engines: {node: '>=8'}
627 |
628 | path-is-absolute@1.0.1:
629 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
630 | engines: {node: '>=0.10.0'}
631 |
632 | path-key@3.1.1:
633 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
634 | engines: {node: '>=8'}
635 |
636 | path-type@4.0.0:
637 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
638 | engines: {node: '>=8'}
639 |
640 | picomatch@2.3.1:
641 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
642 | engines: {node: '>=8.6'}
643 |
644 | prelude-ls@1.2.1:
645 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
646 | engines: {node: '>= 0.8.0'}
647 |
648 | punycode@2.3.0:
649 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
650 | engines: {node: '>=6'}
651 |
652 | queue-microtask@1.2.3:
653 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
654 |
655 | regexpp@3.2.0:
656 | resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
657 | engines: {node: '>=8'}
658 |
659 | resolve-from@4.0.0:
660 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
661 | engines: {node: '>=4'}
662 |
663 | reusify@1.0.4:
664 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
665 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
666 |
667 | rimraf@3.0.2:
668 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
669 | deprecated: Rimraf versions prior to v4 are no longer supported
670 | hasBin: true
671 |
672 | run-parallel@1.2.0:
673 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
674 |
675 | semver@7.5.4:
676 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
677 | engines: {node: '>=10'}
678 | hasBin: true
679 |
680 | shebang-command@2.0.0:
681 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
682 | engines: {node: '>=8'}
683 |
684 | shebang-regex@3.0.0:
685 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
686 | engines: {node: '>=8'}
687 |
688 | slash@3.0.0:
689 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
690 | engines: {node: '>=8'}
691 |
692 | strip-ansi@6.0.1:
693 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
694 | engines: {node: '>=8'}
695 |
696 | strip-json-comments@3.1.1:
697 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
698 | engines: {node: '>=8'}
699 |
700 | style-mod@4.1.0:
701 | resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==}
702 |
703 | supports-color@7.2.0:
704 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
705 | engines: {node: '>=8'}
706 |
707 | text-table@0.2.0:
708 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
709 |
710 | to-regex-range@5.0.1:
711 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
712 | engines: {node: '>=8.0'}
713 |
714 | tslib@1.14.1:
715 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
716 |
717 | tslib@2.4.0:
718 | resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
719 |
720 | tsutils@3.21.0:
721 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
722 | engines: {node: '>= 6'}
723 | peerDependencies:
724 | 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'
725 |
726 | type-check@0.4.0:
727 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
728 | engines: {node: '>= 0.8.0'}
729 |
730 | type-fest@0.20.2:
731 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
732 | engines: {node: '>=10'}
733 |
734 | typescript@4.7.4:
735 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
736 | engines: {node: '>=4.2.0'}
737 | hasBin: true
738 |
739 | uri-js@4.4.1:
740 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
741 |
742 | w3c-keyname@2.2.8:
743 | resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
744 |
745 | which@2.0.2:
746 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
747 | engines: {node: '>= 8'}
748 | hasBin: true
749 |
750 | wrappy@1.0.2:
751 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
752 |
753 | yallist@4.0.0:
754 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
755 |
756 | yocto-queue@0.1.0:
757 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
758 | engines: {node: '>=10'}
759 |
760 | snapshots:
761 |
762 | '@aashutoshrathi/word-wrap@1.2.6': {}
763 |
764 | '@codemirror/state@6.4.1': {}
765 |
766 | '@codemirror/view@6.28.1':
767 | dependencies:
768 | '@codemirror/state': 6.4.1
769 | style-mod: 4.1.0
770 | w3c-keyname: 2.2.8
771 |
772 | '@eslint-community/eslint-utils@4.4.0(eslint@8.48.0)':
773 | dependencies:
774 | eslint: 8.48.0
775 | eslint-visitor-keys: 3.4.3
776 |
777 | '@eslint-community/regexpp@4.8.0': {}
778 |
779 | '@eslint/eslintrc@2.1.2':
780 | dependencies:
781 | ajv: 6.12.6
782 | debug: 4.3.4
783 | espree: 9.6.1
784 | globals: 13.21.0
785 | ignore: 5.2.4
786 | import-fresh: 3.3.0
787 | js-yaml: 4.1.0
788 | minimatch: 3.1.2
789 | strip-json-comments: 3.1.1
790 | transitivePeerDependencies:
791 | - supports-color
792 |
793 | '@eslint/js@8.48.0': {}
794 |
795 | '@humanwhocodes/config-array@0.11.10':
796 | dependencies:
797 | '@humanwhocodes/object-schema': 1.2.1
798 | debug: 4.3.4
799 | minimatch: 3.1.2
800 | transitivePeerDependencies:
801 | - supports-color
802 |
803 | '@humanwhocodes/module-importer@1.0.1': {}
804 |
805 | '@humanwhocodes/object-schema@1.2.1': {}
806 |
807 | '@nodelib/fs.scandir@2.1.5':
808 | dependencies:
809 | '@nodelib/fs.stat': 2.0.5
810 | run-parallel: 1.2.0
811 |
812 | '@nodelib/fs.stat@2.0.5': {}
813 |
814 | '@nodelib/fs.walk@1.2.8':
815 | dependencies:
816 | '@nodelib/fs.scandir': 2.1.5
817 | fastq: 1.15.0
818 |
819 | '@types/codemirror@5.60.8':
820 | dependencies:
821 | '@types/tern': 0.23.4
822 |
823 | '@types/estree@1.0.1': {}
824 |
825 | '@types/json-schema@7.0.12': {}
826 |
827 | '@types/node@16.18.46': {}
828 |
829 | '@types/tern@0.23.4':
830 | dependencies:
831 | '@types/estree': 1.0.1
832 |
833 | '@typescript-eslint/eslint-plugin@5.29.0(@typescript-eslint/parser@5.29.0(eslint@8.48.0)(typescript@4.7.4))(eslint@8.48.0)(typescript@4.7.4)':
834 | dependencies:
835 | '@typescript-eslint/parser': 5.29.0(eslint@8.48.0)(typescript@4.7.4)
836 | '@typescript-eslint/scope-manager': 5.29.0
837 | '@typescript-eslint/type-utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4)
838 | '@typescript-eslint/utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4)
839 | debug: 4.3.4
840 | eslint: 8.48.0
841 | functional-red-black-tree: 1.0.1
842 | ignore: 5.2.4
843 | regexpp: 3.2.0
844 | semver: 7.5.4
845 | tsutils: 3.21.0(typescript@4.7.4)
846 | optionalDependencies:
847 | typescript: 4.7.4
848 | transitivePeerDependencies:
849 | - supports-color
850 |
851 | '@typescript-eslint/parser@5.29.0(eslint@8.48.0)(typescript@4.7.4)':
852 | dependencies:
853 | '@typescript-eslint/scope-manager': 5.29.0
854 | '@typescript-eslint/types': 5.29.0
855 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4)
856 | debug: 4.3.4
857 | eslint: 8.48.0
858 | optionalDependencies:
859 | typescript: 4.7.4
860 | transitivePeerDependencies:
861 | - supports-color
862 |
863 | '@typescript-eslint/scope-manager@5.29.0':
864 | dependencies:
865 | '@typescript-eslint/types': 5.29.0
866 | '@typescript-eslint/visitor-keys': 5.29.0
867 |
868 | '@typescript-eslint/type-utils@5.29.0(eslint@8.48.0)(typescript@4.7.4)':
869 | dependencies:
870 | '@typescript-eslint/utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4)
871 | debug: 4.3.4
872 | eslint: 8.48.0
873 | tsutils: 3.21.0(typescript@4.7.4)
874 | optionalDependencies:
875 | typescript: 4.7.4
876 | transitivePeerDependencies:
877 | - supports-color
878 |
879 | '@typescript-eslint/types@5.29.0': {}
880 |
881 | '@typescript-eslint/typescript-estree@5.29.0(typescript@4.7.4)':
882 | dependencies:
883 | '@typescript-eslint/types': 5.29.0
884 | '@typescript-eslint/visitor-keys': 5.29.0
885 | debug: 4.3.4
886 | globby: 11.1.0
887 | is-glob: 4.0.3
888 | semver: 7.5.4
889 | tsutils: 3.21.0(typescript@4.7.4)
890 | optionalDependencies:
891 | typescript: 4.7.4
892 | transitivePeerDependencies:
893 | - supports-color
894 |
895 | '@typescript-eslint/utils@5.29.0(eslint@8.48.0)(typescript@4.7.4)':
896 | dependencies:
897 | '@types/json-schema': 7.0.12
898 | '@typescript-eslint/scope-manager': 5.29.0
899 | '@typescript-eslint/types': 5.29.0
900 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4)
901 | eslint: 8.48.0
902 | eslint-scope: 5.1.1
903 | eslint-utils: 3.0.0(eslint@8.48.0)
904 | transitivePeerDependencies:
905 | - supports-color
906 | - typescript
907 |
908 | '@typescript-eslint/visitor-keys@5.29.0':
909 | dependencies:
910 | '@typescript-eslint/types': 5.29.0
911 | eslint-visitor-keys: 3.4.3
912 |
913 | acorn-jsx@5.3.2(acorn@8.10.0):
914 | dependencies:
915 | acorn: 8.10.0
916 |
917 | acorn@8.10.0: {}
918 |
919 | ajv@6.12.6:
920 | dependencies:
921 | fast-deep-equal: 3.1.3
922 | fast-json-stable-stringify: 2.1.0
923 | json-schema-traverse: 0.4.1
924 | uri-js: 4.4.1
925 |
926 | ansi-regex@5.0.1: {}
927 |
928 | ansi-styles@4.3.0:
929 | dependencies:
930 | color-convert: 2.0.1
931 |
932 | argparse@2.0.1: {}
933 |
934 | array-union@2.1.0: {}
935 |
936 | balanced-match@1.0.2: {}
937 |
938 | brace-expansion@1.1.11:
939 | dependencies:
940 | balanced-match: 1.0.2
941 | concat-map: 0.0.1
942 |
943 | braces@3.0.2:
944 | dependencies:
945 | fill-range: 7.0.1
946 |
947 | builtin-modules@3.3.0: {}
948 |
949 | callsites@3.1.0: {}
950 |
951 | chalk@4.1.2:
952 | dependencies:
953 | ansi-styles: 4.3.0
954 | supports-color: 7.2.0
955 |
956 | color-convert@2.0.1:
957 | dependencies:
958 | color-name: 1.1.4
959 |
960 | color-name@1.1.4: {}
961 |
962 | concat-map@0.0.1: {}
963 |
964 | cross-spawn@7.0.3:
965 | dependencies:
966 | path-key: 3.1.1
967 | shebang-command: 2.0.0
968 | which: 2.0.2
969 |
970 | debug@4.3.4:
971 | dependencies:
972 | ms: 2.1.2
973 |
974 | deep-is@0.1.4: {}
975 |
976 | dir-glob@3.0.1:
977 | dependencies:
978 | path-type: 4.0.0
979 |
980 | doctrine@3.0.0:
981 | dependencies:
982 | esutils: 2.0.3
983 |
984 | esbuild-android-64@0.14.47:
985 | optional: true
986 |
987 | esbuild-android-arm64@0.14.47:
988 | optional: true
989 |
990 | esbuild-darwin-64@0.14.47:
991 | optional: true
992 |
993 | esbuild-darwin-arm64@0.14.47:
994 | optional: true
995 |
996 | esbuild-freebsd-64@0.14.47:
997 | optional: true
998 |
999 | esbuild-freebsd-arm64@0.14.47:
1000 | optional: true
1001 |
1002 | esbuild-linux-32@0.14.47:
1003 | optional: true
1004 |
1005 | esbuild-linux-64@0.14.47:
1006 | optional: true
1007 |
1008 | esbuild-linux-arm64@0.14.47:
1009 | optional: true
1010 |
1011 | esbuild-linux-arm@0.14.47:
1012 | optional: true
1013 |
1014 | esbuild-linux-mips64le@0.14.47:
1015 | optional: true
1016 |
1017 | esbuild-linux-ppc64le@0.14.47:
1018 | optional: true
1019 |
1020 | esbuild-linux-riscv64@0.14.47:
1021 | optional: true
1022 |
1023 | esbuild-linux-s390x@0.14.47:
1024 | optional: true
1025 |
1026 | esbuild-netbsd-64@0.14.47:
1027 | optional: true
1028 |
1029 | esbuild-openbsd-64@0.14.47:
1030 | optional: true
1031 |
1032 | esbuild-sunos-64@0.14.47:
1033 | optional: true
1034 |
1035 | esbuild-windows-32@0.14.47:
1036 | optional: true
1037 |
1038 | esbuild-windows-64@0.14.47:
1039 | optional: true
1040 |
1041 | esbuild-windows-arm64@0.14.47:
1042 | optional: true
1043 |
1044 | esbuild@0.14.47:
1045 | optionalDependencies:
1046 | esbuild-android-64: 0.14.47
1047 | esbuild-android-arm64: 0.14.47
1048 | esbuild-darwin-64: 0.14.47
1049 | esbuild-darwin-arm64: 0.14.47
1050 | esbuild-freebsd-64: 0.14.47
1051 | esbuild-freebsd-arm64: 0.14.47
1052 | esbuild-linux-32: 0.14.47
1053 | esbuild-linux-64: 0.14.47
1054 | esbuild-linux-arm: 0.14.47
1055 | esbuild-linux-arm64: 0.14.47
1056 | esbuild-linux-mips64le: 0.14.47
1057 | esbuild-linux-ppc64le: 0.14.47
1058 | esbuild-linux-riscv64: 0.14.47
1059 | esbuild-linux-s390x: 0.14.47
1060 | esbuild-netbsd-64: 0.14.47
1061 | esbuild-openbsd-64: 0.14.47
1062 | esbuild-sunos-64: 0.14.47
1063 | esbuild-windows-32: 0.14.47
1064 | esbuild-windows-64: 0.14.47
1065 | esbuild-windows-arm64: 0.14.47
1066 |
1067 | escape-string-regexp@4.0.0: {}
1068 |
1069 | eslint-scope@5.1.1:
1070 | dependencies:
1071 | esrecurse: 4.3.0
1072 | estraverse: 4.3.0
1073 |
1074 | eslint-scope@7.2.2:
1075 | dependencies:
1076 | esrecurse: 4.3.0
1077 | estraverse: 5.3.0
1078 |
1079 | eslint-utils@3.0.0(eslint@8.48.0):
1080 | dependencies:
1081 | eslint: 8.48.0
1082 | eslint-visitor-keys: 2.1.0
1083 |
1084 | eslint-visitor-keys@2.1.0: {}
1085 |
1086 | eslint-visitor-keys@3.4.3: {}
1087 |
1088 | eslint@8.48.0:
1089 | dependencies:
1090 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0)
1091 | '@eslint-community/regexpp': 4.8.0
1092 | '@eslint/eslintrc': 2.1.2
1093 | '@eslint/js': 8.48.0
1094 | '@humanwhocodes/config-array': 0.11.10
1095 | '@humanwhocodes/module-importer': 1.0.1
1096 | '@nodelib/fs.walk': 1.2.8
1097 | ajv: 6.12.6
1098 | chalk: 4.1.2
1099 | cross-spawn: 7.0.3
1100 | debug: 4.3.4
1101 | doctrine: 3.0.0
1102 | escape-string-regexp: 4.0.0
1103 | eslint-scope: 7.2.2
1104 | eslint-visitor-keys: 3.4.3
1105 | espree: 9.6.1
1106 | esquery: 1.5.0
1107 | esutils: 2.0.3
1108 | fast-deep-equal: 3.1.3
1109 | file-entry-cache: 6.0.1
1110 | find-up: 5.0.0
1111 | glob-parent: 6.0.2
1112 | globals: 13.21.0
1113 | graphemer: 1.4.0
1114 | ignore: 5.2.4
1115 | imurmurhash: 0.1.4
1116 | is-glob: 4.0.3
1117 | is-path-inside: 3.0.3
1118 | js-yaml: 4.1.0
1119 | json-stable-stringify-without-jsonify: 1.0.1
1120 | levn: 0.4.1
1121 | lodash.merge: 4.6.2
1122 | minimatch: 3.1.2
1123 | natural-compare: 1.4.0
1124 | optionator: 0.9.3
1125 | strip-ansi: 6.0.1
1126 | text-table: 0.2.0
1127 | transitivePeerDependencies:
1128 | - supports-color
1129 |
1130 | espree@9.6.1:
1131 | dependencies:
1132 | acorn: 8.10.0
1133 | acorn-jsx: 5.3.2(acorn@8.10.0)
1134 | eslint-visitor-keys: 3.4.3
1135 |
1136 | esquery@1.5.0:
1137 | dependencies:
1138 | estraverse: 5.3.0
1139 |
1140 | esrecurse@4.3.0:
1141 | dependencies:
1142 | estraverse: 5.3.0
1143 |
1144 | estraverse@4.3.0: {}
1145 |
1146 | estraverse@5.3.0: {}
1147 |
1148 | esutils@2.0.3: {}
1149 |
1150 | fast-deep-equal@3.1.3: {}
1151 |
1152 | fast-glob@3.3.1:
1153 | dependencies:
1154 | '@nodelib/fs.stat': 2.0.5
1155 | '@nodelib/fs.walk': 1.2.8
1156 | glob-parent: 5.1.2
1157 | merge2: 1.4.1
1158 | micromatch: 4.0.5
1159 |
1160 | fast-json-stable-stringify@2.1.0: {}
1161 |
1162 | fast-levenshtein@2.0.6: {}
1163 |
1164 | fastq@1.15.0:
1165 | dependencies:
1166 | reusify: 1.0.4
1167 |
1168 | file-entry-cache@6.0.1:
1169 | dependencies:
1170 | flat-cache: 3.1.0
1171 |
1172 | fill-range@7.0.1:
1173 | dependencies:
1174 | to-regex-range: 5.0.1
1175 |
1176 | find-up@5.0.0:
1177 | dependencies:
1178 | locate-path: 6.0.0
1179 | path-exists: 4.0.0
1180 |
1181 | flat-cache@3.1.0:
1182 | dependencies:
1183 | flatted: 3.2.7
1184 | keyv: 4.5.3
1185 | rimraf: 3.0.2
1186 |
1187 | flatted@3.2.7: {}
1188 |
1189 | fs.realpath@1.0.0: {}
1190 |
1191 | functional-red-black-tree@1.0.1: {}
1192 |
1193 | glob-parent@5.1.2:
1194 | dependencies:
1195 | is-glob: 4.0.3
1196 |
1197 | glob-parent@6.0.2:
1198 | dependencies:
1199 | is-glob: 4.0.3
1200 |
1201 | glob@7.2.3:
1202 | dependencies:
1203 | fs.realpath: 1.0.0
1204 | inflight: 1.0.6
1205 | inherits: 2.0.4
1206 | minimatch: 3.1.2
1207 | once: 1.4.0
1208 | path-is-absolute: 1.0.1
1209 |
1210 | globals@13.21.0:
1211 | dependencies:
1212 | type-fest: 0.20.2
1213 |
1214 | globby@11.1.0:
1215 | dependencies:
1216 | array-union: 2.1.0
1217 | dir-glob: 3.0.1
1218 | fast-glob: 3.3.1
1219 | ignore: 5.2.4
1220 | merge2: 1.4.1
1221 | slash: 3.0.0
1222 |
1223 | graphemer@1.4.0: {}
1224 |
1225 | has-flag@4.0.0: {}
1226 |
1227 | ignore@5.2.4: {}
1228 |
1229 | import-fresh@3.3.0:
1230 | dependencies:
1231 | parent-module: 1.0.1
1232 | resolve-from: 4.0.0
1233 |
1234 | imurmurhash@0.1.4: {}
1235 |
1236 | inflight@1.0.6:
1237 | dependencies:
1238 | once: 1.4.0
1239 | wrappy: 1.0.2
1240 |
1241 | inherits@2.0.4: {}
1242 |
1243 | is-extglob@2.1.1: {}
1244 |
1245 | is-glob@4.0.3:
1246 | dependencies:
1247 | is-extglob: 2.1.1
1248 |
1249 | is-number@7.0.0: {}
1250 |
1251 | is-path-inside@3.0.3: {}
1252 |
1253 | isexe@2.0.0: {}
1254 |
1255 | js-yaml@4.1.0:
1256 | dependencies:
1257 | argparse: 2.0.1
1258 |
1259 | json-buffer@3.0.1: {}
1260 |
1261 | json-schema-traverse@0.4.1: {}
1262 |
1263 | json-stable-stringify-without-jsonify@1.0.1: {}
1264 |
1265 | keyv@4.5.3:
1266 | dependencies:
1267 | json-buffer: 3.0.1
1268 |
1269 | levn@0.4.1:
1270 | dependencies:
1271 | prelude-ls: 1.2.1
1272 | type-check: 0.4.0
1273 |
1274 | locate-path@6.0.0:
1275 | dependencies:
1276 | p-locate: 5.0.0
1277 |
1278 | lodash.merge@4.6.2: {}
1279 |
1280 | lru-cache@6.0.0:
1281 | dependencies:
1282 | yallist: 4.0.0
1283 |
1284 | merge2@1.4.1: {}
1285 |
1286 | micromatch@4.0.5:
1287 | dependencies:
1288 | braces: 3.0.2
1289 | picomatch: 2.3.1
1290 |
1291 | minimatch@3.1.2:
1292 | dependencies:
1293 | brace-expansion: 1.1.11
1294 |
1295 | moment@2.29.4: {}
1296 |
1297 | monkey-around@2.3.0: {}
1298 |
1299 | ms@2.1.2: {}
1300 |
1301 | natural-compare@1.4.0: {}
1302 |
1303 | obsidian@1.8.7(@codemirror/state@6.4.1)(@codemirror/view@6.28.1):
1304 | dependencies:
1305 | '@codemirror/state': 6.4.1
1306 | '@codemirror/view': 6.28.1
1307 | '@types/codemirror': 5.60.8
1308 | moment: 2.29.4
1309 |
1310 | once@1.4.0:
1311 | dependencies:
1312 | wrappy: 1.0.2
1313 |
1314 | optionator@0.9.3:
1315 | dependencies:
1316 | '@aashutoshrathi/word-wrap': 1.2.6
1317 | deep-is: 0.1.4
1318 | fast-levenshtein: 2.0.6
1319 | levn: 0.4.1
1320 | prelude-ls: 1.2.1
1321 | type-check: 0.4.0
1322 |
1323 | p-limit@3.1.0:
1324 | dependencies:
1325 | yocto-queue: 0.1.0
1326 |
1327 | p-locate@5.0.0:
1328 | dependencies:
1329 | p-limit: 3.1.0
1330 |
1331 | parent-module@1.0.1:
1332 | dependencies:
1333 | callsites: 3.1.0
1334 |
1335 | path-exists@4.0.0: {}
1336 |
1337 | path-is-absolute@1.0.1: {}
1338 |
1339 | path-key@3.1.1: {}
1340 |
1341 | path-type@4.0.0: {}
1342 |
1343 | picomatch@2.3.1: {}
1344 |
1345 | prelude-ls@1.2.1: {}
1346 |
1347 | punycode@2.3.0: {}
1348 |
1349 | queue-microtask@1.2.3: {}
1350 |
1351 | regexpp@3.2.0: {}
1352 |
1353 | resolve-from@4.0.0: {}
1354 |
1355 | reusify@1.0.4: {}
1356 |
1357 | rimraf@3.0.2:
1358 | dependencies:
1359 | glob: 7.2.3
1360 |
1361 | run-parallel@1.2.0:
1362 | dependencies:
1363 | queue-microtask: 1.2.3
1364 |
1365 | semver@7.5.4:
1366 | dependencies:
1367 | lru-cache: 6.0.0
1368 |
1369 | shebang-command@2.0.0:
1370 | dependencies:
1371 | shebang-regex: 3.0.0
1372 |
1373 | shebang-regex@3.0.0: {}
1374 |
1375 | slash@3.0.0: {}
1376 |
1377 | strip-ansi@6.0.1:
1378 | dependencies:
1379 | ansi-regex: 5.0.1
1380 |
1381 | strip-json-comments@3.1.1: {}
1382 |
1383 | style-mod@4.1.0: {}
1384 |
1385 | supports-color@7.2.0:
1386 | dependencies:
1387 | has-flag: 4.0.0
1388 |
1389 | text-table@0.2.0: {}
1390 |
1391 | to-regex-range@5.0.1:
1392 | dependencies:
1393 | is-number: 7.0.0
1394 |
1395 | tslib@1.14.1: {}
1396 |
1397 | tslib@2.4.0: {}
1398 |
1399 | tsutils@3.21.0(typescript@4.7.4):
1400 | dependencies:
1401 | tslib: 1.14.1
1402 | typescript: 4.7.4
1403 |
1404 | type-check@0.4.0:
1405 | dependencies:
1406 | prelude-ls: 1.2.1
1407 |
1408 | type-fest@0.20.2: {}
1409 |
1410 | typescript@4.7.4: {}
1411 |
1412 | uri-js@4.4.1:
1413 | dependencies:
1414 | punycode: 2.3.0
1415 |
1416 | w3c-keyname@2.2.8: {}
1417 |
1418 | which@2.0.2:
1419 | dependencies:
1420 | isexe: 2.0.0
1421 |
1422 | wrappy@1.0.2: {}
1423 |
1424 | yallist@4.0.0: {}
1425 |
1426 | yocto-queue@0.1.0: {}
1427 |
--------------------------------------------------------------------------------
/src/ControlHeader.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type CanvasFileNode,
3 | type CanvasGroupNode,
4 | type CanvasLinkNode,
5 | type CanvasNode,
6 | type CanvasTextNode,
7 | Component,
8 | parseFrontMatterAliases,
9 | setIcon,
10 | } from "obsidian";
11 | import type { HeaderComponent } from "./types/custom";
12 | import type CanvasCollapsePlugin from ".";
13 | import type { AllCanvasNodeData } from "obsidian/canvas";
14 |
15 | export default class CollapseControlHeader
16 | extends Component
17 | implements HeaderComponent
18 | {
19 | private collapsed = false;
20 | private collapsedIconEl: HTMLDivElement;
21 | private typeIconEl: HTMLDivElement;
22 | private titleEl: HTMLSpanElement;
23 | private headerEl: HTMLElement;
24 | private thumbnailEl: HTMLImageElement;
25 | private aliasEl: HTMLSpanElement;
26 |
27 | private content = "";
28 | private node: CanvasNode;
29 | private alias = "";
30 | private thumbnailUrl = "";
31 |
32 | private refreshed = false;
33 | private containingNodes: CanvasNode[] = [];
34 |
35 | plugin: CanvasCollapsePlugin;
36 | oldFilePath = "";
37 |
38 | constructor(plugin: CanvasCollapsePlugin, node: CanvasNode) {
39 | super();
40 |
41 | this.plugin = plugin;
42 | this.node = node;
43 | this.collapsed =
44 | node.unknownData.collapsed === undefined
45 | ? false
46 | : node.unknownData.collapsed;
47 | }
48 |
49 | onload() {
50 | this.initHeader();
51 | this.initContent();
52 | this.initTypeIcon();
53 | this.updateNodesInGroup();
54 | this.updateNode();
55 |
56 | this.registerEvent(
57 | this.plugin.app.vault.on("rename", (file, oldPath) => {
58 | if (oldPath === this.oldFilePath) {
59 | this.titleEl.setText(file.name.split(".")[0]);
60 | this.oldFilePath = file.path;
61 | }
62 | })
63 | );
64 |
65 | return this.headerEl;
66 | }
67 |
68 | onunload() {
69 | super.onunload();
70 | }
71 |
72 | unload() {
73 | super.unload();
74 |
75 | this.headerEl.empty();
76 | this.headerEl.detach();
77 | }
78 |
79 | initHeader() {
80 | this.headerEl = createEl("div", {
81 | cls: "canvas-node-collapse-control",
82 | });
83 | this.registerDomEvent(this.headerEl, "click", async (evt) => {
84 | evt.preventDefault();
85 | evt.stopPropagation();
86 | await this.toggleCollapsed();
87 | });
88 |
89 | this.collapsedIconEl = this.headerEl.createEl("div", {
90 | cls: "canvas-node-collapse-control-icon",
91 | });
92 |
93 | this.typeIconEl = this.headerEl.createEl("div", {
94 | cls: "canvas-node-type-icon",
95 | });
96 |
97 | this.titleEl = this.headerEl.createEl("span", {
98 | cls: "canvas-node-collapse-control-title",
99 | });
100 |
101 | this.thumbnailEl = this.node.nodeEl.createEl("img", {
102 | cls: "canvas-node-collapse-control-thumbnail",
103 | });
104 |
105 | this.aliasEl = this.headerEl.createEl("span", {
106 | cls: "canvas-node-collapse-control-alias",
107 | });
108 | }
109 |
110 | checkNodeType() {
111 | return this.node.unknownData.type;
112 | }
113 |
114 | initTypeIcon() {
115 | this.setIconOrContent("setIcon");
116 | }
117 |
118 | initContent() {
119 | this.setIconOrContent("setContent");
120 | this.titleEl.setText(this.content?.replace(/^\#{1,} /g, ""));
121 | this.initAlias();
122 | this.initThumbnail();
123 | }
124 |
125 | initAlias() {
126 | // Try to get alias from node metadata
127 | if (this.node.unknownData?.alias) {
128 | this.alias = this.node.unknownData.alias;
129 | } else {
130 | // For file nodes, try to get alias from frontmatter
131 | const fileNode = this.node as CanvasFileNode;
132 | if (fileNode.file && this.plugin.app.metadataCache) {
133 | try {
134 | const meta = this.plugin.app.metadataCache.getFileCache(
135 | fileNode.file
136 | );
137 | if (meta?.frontmatter) {
138 | const aliases = parseFrontMatterAliases(
139 | meta.frontmatter
140 | );
141 | if (aliases && aliases.length > 0) {
142 | this.alias = aliases[0];
143 | }
144 | }
145 | } catch (e) {
146 | console.debug("Error getting alias:", e);
147 | }
148 | }
149 | }
150 |
151 | if (this.alias) {
152 | this.aliasEl.setText(this.alias);
153 | } else {
154 | this.aliasEl.detach();
155 | }
156 | }
157 |
158 | updateNodeAlias(newAlias: string) {
159 | this.alias = newAlias;
160 |
161 | if (this.alias) {
162 | // Re-attach if it was detached
163 | if (!this.aliasEl.parentElement) {
164 | this.headerEl.appendChild(this.aliasEl);
165 | }
166 | this.aliasEl.setText(this.alias);
167 | } else {
168 | this.aliasEl.detach();
169 | }
170 | }
171 |
172 | initThumbnail() {
173 | // Try to get thumbnail from node metadata
174 | if (this.node.unknownData?.thumbnail) {
175 | this.thumbnailUrl = this.node.unknownData.thumbnail;
176 | } else {
177 | // For file nodes, try to get thumbnail from frontmatter
178 | const fileNode = this.node as CanvasFileNode;
179 | if (fileNode.file && this.plugin.app.metadataCache) {
180 | try {
181 | const meta = this.plugin.app.metadataCache.getFileCache(
182 | fileNode.file
183 | );
184 | if (meta?.frontmatter?.thumbnail) {
185 | this.thumbnailUrl = meta.frontmatter.thumbnail;
186 | }
187 | } catch (e) {
188 | console.debug("Error getting thumbnail:", e);
189 | }
190 | }
191 | }
192 |
193 | if (this.thumbnailUrl) {
194 | try {
195 | // Handle both absolute and relative paths
196 | const url = this.thumbnailUrl.startsWith("http")
197 | ? this.thumbnailUrl
198 | : this.plugin.app.vault.adapter.getResourcePath(
199 | this.thumbnailUrl
200 | );
201 |
202 | this.thumbnailEl.src = url;
203 | } catch (e) {
204 | console.debug("Error setting thumbnail src:", e);
205 | this.thumbnailEl.detach();
206 | }
207 | } else {
208 | this.thumbnailEl.detach();
209 | }
210 | }
211 |
212 | updateNodeThumbnail(newThumbnailUrl: string) {
213 | this.thumbnailUrl = newThumbnailUrl;
214 |
215 | if (this.thumbnailUrl) {
216 | try {
217 | // Re-attach if it was detached
218 | if (!this.thumbnailEl.parentElement) {
219 | this.headerEl.appendChild(this.thumbnailEl);
220 | }
221 |
222 | // Handle both absolute and relative paths
223 | const url = this.thumbnailUrl.startsWith("http")
224 | ? this.thumbnailUrl
225 | : this.plugin.app.vault.adapter.getResourcePath(
226 | this.thumbnailUrl
227 | );
228 |
229 | this.thumbnailEl.src = url;
230 | } catch (e) {
231 | console.debug("Error setting thumbnail src:", e);
232 | this.thumbnailEl.detach();
233 | }
234 | } else {
235 | this.thumbnailEl.detach();
236 | }
237 | }
238 |
239 | setIconOrContent(action: "setIcon" | "setContent") {
240 | const currentType = this.checkNodeType();
241 | switch (currentType) {
242 | case "text":
243 | if (action === "setIcon")
244 | setIcon(this.typeIconEl, "sticky-note");
245 | if (action === "setContent")
246 | this.content =
247 | (this.node as CanvasTextNode).text.slice(0, 10) +
248 | ((this.node as CanvasTextNode).text.length > 10
249 | ? "..."
250 | : "");
251 | break;
252 | case "file":
253 | if (action === "setIcon") {
254 | if (
255 | (this.node as CanvasFileNode).file.name
256 | .split(".")[1]
257 | .trim() === "md"
258 | )
259 | setIcon(this.typeIconEl, "file-text");
260 | else setIcon(this.typeIconEl, "file-image");
261 | }
262 | if (action === "setContent")
263 | this.content = (
264 | this.node as CanvasFileNode
265 | ).file?.name.split(".")[0];
266 | this.oldFilePath = (this.node as CanvasFileNode).file?.path;
267 | break;
268 | case "group":
269 | if (action === "setIcon")
270 | setIcon(this.typeIconEl, "create-group");
271 | if (action === "setContent") this.content = "";
272 | break;
273 | case "link":
274 | if (action === "setIcon") setIcon(this.typeIconEl, "link");
275 | if (action === "setContent")
276 | this.content = (this.node as CanvasLinkNode).url;
277 | break;
278 | }
279 |
280 | if (action === "setIcon" && !this.node.unknownData.type) {
281 | setIcon(this.typeIconEl, "sticky-note");
282 | }
283 | }
284 |
285 | setCollapsed(collapsed: boolean) {
286 | if (this.node.canvas.readonly) return;
287 | if (this.collapsed === collapsed) return;
288 |
289 | this.collapsed = collapsed;
290 | this.node.unknownData.collapsed = collapsed;
291 |
292 | this.updateNodesInGroup();
293 | this.updateNode();
294 | this.updateEdges();
295 | }
296 |
297 | refreshHistory() {
298 | if (this.refreshed) return;
299 |
300 | const history = this.node.canvas.history;
301 | if (!history || history.data.length === 0) return;
302 |
303 | for (const data of history.data) {
304 | for (const node of data.nodes) {
305 | if (node.id === this.node.id && node?.collapsed === undefined) {
306 | node.collapsed = false;
307 | }
308 | }
309 | }
310 | this.refreshed = true;
311 | }
312 |
313 | async toggleCollapsed() {
314 | if (this.node.canvas.readonly) return;
315 | const wasCollapsed = this.collapsed;
316 | this.collapsed = !this.collapsed;
317 |
318 | this.node.unknownData.collapsed = !this.collapsed;
319 |
320 | // Update visual state first for animation
321 | this.updateNode();
322 |
323 | // Add delay to allow for animation to complete before updating other elements
324 | setTimeout(() => {
325 | // Update nodes in group after a short delay to allow animation to start
326 | this.updateNodesInGroup();
327 | this.updateEdges();
328 | }, 50);
329 |
330 | this.node.canvas.requestSave(false, true);
331 | const canvasCurrentData = this.node.canvas.getData();
332 | const nodeData = canvasCurrentData.nodes.find(
333 | (node: AllCanvasNodeData) => node.id === this.node.id
334 | );
335 |
336 | if (nodeData) {
337 | nodeData.collapsed = this.collapsed;
338 |
339 | // If this node was collapsed and is now being expanded, move it to the highest z-index
340 | if (wasCollapsed && !this.collapsed) {
341 | // Remove the node from its current position
342 | const nodeIndex = canvasCurrentData.nodes.findIndex(
343 | (node: AllCanvasNodeData) => node.id === this.node.id
344 | );
345 | if (nodeIndex !== -1) {
346 | const removedNode = canvasCurrentData.nodes.splice(
347 | nodeIndex,
348 | 1
349 | )[0];
350 | // Add it back at the end of the array (highest z-index)
351 | canvasCurrentData.nodes.push(removedNode);
352 | }
353 | }
354 |
355 | this.refreshHistory();
356 | }
357 |
358 | setTimeout(() => {
359 | this.node.canvas.setData(canvasCurrentData);
360 | this.node.canvas.requestSave(true);
361 | }, 300); // Increased timeout to match animation duration
362 | }
363 |
364 | updateNode() {
365 | this.node.nodeEl.toggleClass("collapsed", this.collapsed);
366 |
367 | setIcon(this.collapsedIconEl, "chevron-down");
368 | this.collapsedIconEl.toggleClass(
369 | ["collapsed", "collapse-handler"],
370 | this.collapsed
371 | );
372 | // Handle thumbnails visibility
373 | this.updateThumbnailVisibility();
374 |
375 | // Handle aliases visibility
376 | this.updateAliasVisibility();
377 | }
378 |
379 | updateThumbnailVisibility() {
380 | if (this.collapsed || this.plugin.settings.showThumbnailsAlways) {
381 | if (
382 | this.plugin.settings.showThumbnailsInCollapsedState &&
383 | this.thumbnailUrl &&
384 | this.thumbnailUrl !== this.node.unknownData.title
385 | ) {
386 | this.thumbnailEl.toggleClass("collapsed-node-hidden", false);
387 | // Hide the title if we're showing an alias instead
388 | this.titleEl.toggleClass("collapsed-node-hidden", true);
389 | } else {
390 | this.thumbnailEl.toggleClass("collapsed-node-hidden", true);
391 | // Make sure title is visible if alias isn't shown
392 | this.titleEl.toggleClass("collapsed-node-hidden", false);
393 | }
394 | } else {
395 | this.thumbnailEl.toggleClass("collapsed-node-hidden", true);
396 | }
397 | }
398 |
399 | updateAliasVisibility() {
400 | if (this.collapsed || this.plugin.settings.showAliasesAlways) {
401 | if (
402 | this.plugin.settings.showAliasesInCollapsedState &&
403 | this.alias &&
404 | this.alias !== this.node.unknownData.title
405 | ) {
406 | this.aliasEl.toggleClass("collapsed-node-hidden", false);
407 | // Hide the title if we're showing an alias instead
408 | this.titleEl.toggleClass("collapsed-node-hidden", true);
409 | } else {
410 | this.aliasEl.toggleClass("collapsed-node-hidden", true);
411 | // Make sure title is visible if alias isn't shown
412 | this.titleEl.toggleClass("collapsed-node-hidden", false);
413 | }
414 | } else {
415 | this.aliasEl.toggleClass("collapsed-node-hidden", true);
416 | }
417 | }
418 |
419 | updateEdges() {
420 | this.node.canvas.nodeInteractionLayer.interactionEl.detach();
421 | this.node.canvas.nodeInteractionLayer.render();
422 | const edges = this.node.canvas.getEdgesForNode(this.node);
423 | for (const edge of edges) {
424 | edge.render();
425 | }
426 | }
427 |
428 | updateNodesInGroup(expandAll?: boolean) {
429 | if (
430 | this.node.unknownData.type === "group" ||
431 | (this.node as CanvasGroupNode).label
432 | ) {
433 | const nodes = this.node.canvas.getContainingNodes(
434 | this.node.getBBox(true)
435 | );
436 |
437 | if (expandAll) {
438 | this.collapsed = false;
439 | }
440 |
441 | // Add animation class to the group node
442 | this.node.nodeEl.toggleClass("animating", true);
443 |
444 | // Remove animation class after animation completes
445 | setTimeout(() => {
446 | this.node.nodeEl.toggleClass("animating", false);
447 | }, 300);
448 |
449 | if (this.collapsed) {
450 | const filteredNodes = nodes.filter(
451 | (node: CanvasNode) => node.id !== this.node.id
452 | );
453 | for (const node of filteredNodes) {
454 | this.containingNodes.push(node);
455 |
456 | // Add transition class before changing state
457 | node.nodeEl.toggleClass("node-transitioning", true);
458 |
459 | // Apply the collapsed state
460 | node.nodeEl.toggleClass(
461 | "group-nodes-collapsed",
462 | this.collapsed
463 | );
464 |
465 | // Remove transition class after animation completes
466 | setTimeout(() => {
467 | node.nodeEl.toggleClass("node-transitioning", false);
468 | }, 300);
469 |
470 | this.updateEdgesInGroup(node);
471 | }
472 | } else {
473 | const otherGroupNodes = nodes.filter(
474 | (node: CanvasNode) =>
475 | node.id !== this.node.id &&
476 | node.unknownData.type === "group" &&
477 | node.unknownData.collapsed
478 | );
479 | // Ignore those nodes in collapsed child group
480 | const ignoreNodes: CanvasNode[] = [];
481 | for (const groupNode of otherGroupNodes) {
482 | const bbox = groupNode.getBBox(true);
483 | const nodesInGroup =
484 | this.node.canvas.getContainingNodes(bbox);
485 | for (const childNode of nodesInGroup) {
486 | if (childNode.id !== groupNode.id) {
487 | ignoreNodes.push(childNode);
488 | }
489 | }
490 | }
491 |
492 | const filteredContainingNodes = this.containingNodes.filter(
493 | (t) => !ignoreNodes.find((k) => k.id === t.id)
494 | );
495 |
496 | for (const node of filteredContainingNodes) {
497 | // Add transition class before changing state
498 | node.nodeEl.toggleClass("node-transitioning", true);
499 |
500 | // Apply the expanded state
501 | node.nodeEl.toggleClass(
502 | "group-nodes-collapsed",
503 | this.collapsed
504 | );
505 |
506 | // Remove transition class after animation completes
507 | setTimeout(() => {
508 | node.nodeEl.toggleClass("node-transitioning", false);
509 | }, 300);
510 |
511 | this.updateEdgesInGroup(node, true);
512 | }
513 |
514 | for (const node of ignoreNodes) {
515 | this.updateEdgesInGroup(node, node.unknownData.collapsed);
516 | }
517 |
518 | this.containingNodes = [];
519 | }
520 | this.updateEdges();
521 | }
522 | }
523 |
524 | updateEdgesInGroup(node: CanvasNode, triggerCollapsed?: boolean) {
525 | const edges = this.node.canvas.getEdgesForNode(node);
526 |
527 | for (const edge of edges) {
528 | edge.labelElement?.wrapperEl?.classList.toggle(
529 | "group-edges-collapsed",
530 | triggerCollapsed || this.collapsed
531 | );
532 | edge.lineGroupEl.classList.toggle(
533 | "group-edges-collapsed",
534 | triggerCollapsed || this.collapsed
535 | );
536 | edge.lineEndGroupEl?.classList.toggle(
537 | "group-edges-collapsed",
538 | triggerCollapsed || this.collapsed
539 | );
540 | edge.lineStartGroupEl?.classList.toggle(
541 | "group-edges-collapsed",
542 | triggerCollapsed || this.collapsed
543 | );
544 | }
545 | }
546 | }
547 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | addIcon,
3 | debounce,
4 | editorInfoField,
5 | Plugin,
6 | PluginSettingTab,
7 | Setting,
8 | } from "obsidian";
9 | import CollapseControlHeader from "./ControlHeader";
10 | import {
11 | handleNodeContextMenu,
12 | handleNodesViaCommands,
13 | handleSelectionContextMenu,
14 | refreshAllCanvasView,
15 | } from "./utils";
16 | import { EditorView, ViewUpdate } from "@codemirror/view";
17 | import {
18 | aroundCanvasMethods,
19 | patchCanvasInteraction,
20 | patchCanvasMenu,
21 | patchCanvasNode,
22 | } from "./patchUtils";
23 |
24 | interface CollapsableNodeSettings {
25 | collapsableFileNode: boolean;
26 | collapsableAttachmentNode: boolean;
27 | collapsableGroupNode: boolean;
28 | collapsableLinkNode: boolean;
29 | collapsableTextNode: boolean;
30 | }
31 |
32 | interface OtherSettings {
33 | minLineAmount: number;
34 | minimalControlHeader: boolean;
35 | showThumbnailsInCollapsedState: boolean;
36 | showAliasesInCollapsedState: boolean;
37 | showAliasesAlways: boolean;
38 | showThumbnailsAlways: boolean;
39 | hideDefaultNodeTitle: boolean;
40 | }
41 |
42 | type CanvasCollapseSettings = CollapsableNodeSettings & OtherSettings;
43 |
44 | const DEFAULT_SETTINGS: CanvasCollapseSettings = {
45 | collapsableFileNode: true,
46 | collapsableAttachmentNode: true,
47 | collapsableGroupNode: true,
48 | collapsableLinkNode: true,
49 | collapsableTextNode: true,
50 |
51 | minLineAmount: 0,
52 | minimalControlHeader: false,
53 | showThumbnailsInCollapsedState: false,
54 | showAliasesInCollapsedState: false,
55 | showAliasesAlways: false,
56 | showThumbnailsAlways: false,
57 | hideDefaultNodeTitle: false,
58 | };
59 |
60 | const DynamicUpdateControlHeader = (plugin: CanvasCollapsePlugin) => {
61 | return EditorView.updateListener.of((v: ViewUpdate) => {
62 | if (v.docChanged) {
63 | const editor = v.state.field(editorInfoField);
64 | const node = (editor as any).node;
65 | if (node) {
66 | if (
67 | node.unknownData.type === "text" &&
68 | !plugin.settings.collapsableTextNode
69 | )
70 | return;
71 | if (
72 | node.unknownData.type === "file" &&
73 | !plugin.settings.collapsableFileNode
74 | )
75 | return;
76 |
77 | if (
78 | node.unknownData.type === "text" ||
79 | (node.unknownData.type === "file" &&
80 | node.file.extension === "md")
81 | ) {
82 | const content = v.view.state.doc.toString();
83 | if (
84 | node.headerComponent &&
85 | plugin.settings.minLineAmount > 0 &&
86 | content.split("\n").length <
87 | plugin.settings.minLineAmount
88 | ) {
89 | node.headerComponent?.unload();
90 | node.headerComponent = undefined;
91 | return;
92 | } else if (
93 | !node.headerComponent &&
94 | plugin.settings.minLineAmount > 0 &&
95 | content.split("\n").length >=
96 | plugin.settings.minLineAmount
97 | ) {
98 | node.headerComponent = new CollapseControlHeader(
99 | plugin,
100 | node
101 | );
102 | (node.containerEl as HTMLDivElement).prepend(
103 | node.headerComponent.onload()
104 | );
105 |
106 | node.headerComponent.updateNode();
107 | }
108 | }
109 | }
110 | }
111 | });
112 | };
113 |
114 | export default class CanvasCollapsePlugin extends Plugin {
115 | triggerByPlugin: boolean = false;
116 | patchSucceed: boolean = false;
117 |
118 | settings: CanvasCollapseSettings;
119 |
120 | headerComponents: { [key: string]: CollapseControlHeader[] } = {};
121 |
122 | async onload() {
123 | await this.loadSettings();
124 | this.addSettingTab(new CollapseSettingTab(this.app, this));
125 |
126 | this.registerCommands();
127 | this.registerCanvasEvents();
128 | this.registerCustomIcons();
129 |
130 | this.registerEditorExtension([DynamicUpdateControlHeader(this)]);
131 |
132 | this.initGlobalCss();
133 |
134 | this.app.workspace.onLayoutReady(() => {
135 | aroundCanvasMethods(this);
136 | patchCanvasMenu(this);
137 | patchCanvasInteraction(this);
138 | patchCanvasNode(this);
139 | });
140 | }
141 |
142 | onunload() {
143 | console.log("unloading plugin");
144 | refreshAllCanvasView(this.app);
145 | }
146 |
147 | registerCommands() {
148 | this.addCommand({
149 | id: "fold-all-nodes",
150 | name: "Fold all nodes",
151 | checkCallback: (checking: boolean) =>
152 | handleNodesViaCommands(this, checking, true, true),
153 | });
154 |
155 | this.addCommand({
156 | id: "expand-all-nodes",
157 | name: "Expand all nodes",
158 | checkCallback: (checking: boolean) =>
159 | handleNodesViaCommands(this, checking, true, false),
160 | });
161 |
162 | this.addCommand({
163 | id: "fold-selected-nodes",
164 | name: "Fold selected nodes",
165 | checkCallback: (checking: boolean) =>
166 | handleNodesViaCommands(this, checking, false, true),
167 | });
168 |
169 | this.addCommand({
170 | id: "expand-selected-nodes",
171 | name: "Expand selected nodes",
172 | checkCallback: (checking: boolean) =>
173 | handleNodesViaCommands(this, checking, false, false),
174 | });
175 | }
176 |
177 | registerCanvasEvents() {
178 | this.registerEvent(
179 | this.app.workspace.on("collapse-node:patched-canvas", () => {
180 | refreshAllCanvasView(this.app);
181 | })
182 | );
183 | this.registerEvent(
184 | this.app.workspace.on("canvas:selection-menu", (menu, canvas) => {
185 | handleSelectionContextMenu(this, menu, canvas);
186 | })
187 | );
188 | this.registerEvent(
189 | this.app.workspace.on("canvas:node-menu", (menu, node) => {
190 | handleNodeContextMenu(this, menu, node);
191 | })
192 | );
193 | }
194 |
195 | registerCustomIcons() {
196 | addIcon(
197 | "fold-vertical",
198 | ``
199 | );
200 | addIcon(
201 | "unfold-vertical",
202 | ``
203 | );
204 | }
205 |
206 | initGlobalCss() {
207 | document.body.toggleClass(
208 | "minimal-control-header",
209 | this.settings?.minimalControlHeader
210 | );
211 | document.body.toggleClass(
212 | "hide-default-node-title",
213 | this.settings?.hideDefaultNodeTitle
214 | );
215 | }
216 |
217 | async loadSettings() {
218 | this.settings = Object.assign(
219 | {},
220 | DEFAULT_SETTINGS,
221 | await this.loadData()
222 | );
223 | }
224 |
225 | debounceReloadLeaves = debounce(() => {
226 | const leaves = this.app.workspace.getLeavesOfType("canvas");
227 | leaves.forEach((leaf) => {
228 | leaf.rebuildView();
229 | });
230 | }, 1000);
231 |
232 | async saveSettings() {
233 | await this.saveData(this.settings);
234 |
235 | this.debounceReloadLeaves();
236 | }
237 | }
238 |
239 | export class CollapseSettingTab extends PluginSettingTab {
240 | plugin: CanvasCollapsePlugin;
241 |
242 | constructor(app: any, plugin: CanvasCollapsePlugin) {
243 | super(app, plugin);
244 | this.plugin = plugin;
245 | }
246 |
247 | display(): void {
248 | const { containerEl } = this;
249 |
250 | containerEl.empty();
251 |
252 | new Setting(containerEl)
253 | .setHeading()
254 | .setName("Enable nodes to be collapsable");
255 |
256 | this.createCollapsableSetting(
257 | this.plugin,
258 | containerEl,
259 | "File node",
260 | "",
261 | "collapsableFileNode"
262 | );
263 | this.createCollapsableSetting(
264 | this.plugin,
265 | containerEl,
266 | "Attachment node",
267 | "",
268 | "collapsableAttachmentNode"
269 | );
270 | this.createCollapsableSetting(
271 | this.plugin,
272 | containerEl,
273 | "Group node",
274 | "",
275 | "collapsableGroupNode"
276 | );
277 | this.createCollapsableSetting(
278 | this.plugin,
279 | containerEl,
280 | "Link node",
281 | "",
282 | "collapsableLinkNode"
283 | );
284 | this.createCollapsableSetting(
285 | this.plugin,
286 | containerEl,
287 | "Text node",
288 | "",
289 | "collapsableTextNode"
290 | );
291 |
292 | new Setting(containerEl).setHeading().setName("Detail settings");
293 |
294 | new Setting(containerEl)
295 | .setName("Line amount the enable node to be collapsed")
296 | .setDesc(
297 | "The amount of lines that will be shown when the node is collapsed"
298 | )
299 | .addText((text) => {
300 | text.setValue(
301 | this.plugin.settings.minLineAmount.toString()
302 | ).onChange(async (value) => {
303 | if (!isNaN(Number(value))) {
304 | this.plugin.settings.minLineAmount = Number(value);
305 | await this.plugin.saveSettings();
306 | }
307 | });
308 | });
309 |
310 | new Setting(containerEl)
311 | .setName("Minimal control header")
312 | .setDesc(
313 | "Hide the text and also icon in the control header of the node"
314 | )
315 | .addToggle((toggle) => {
316 | toggle
317 | .setValue(this.plugin.settings.minimalControlHeader)
318 | .onChange(async (value) => {
319 | this.plugin.settings.minimalControlHeader = value;
320 | document.body.toggleClass(
321 | "minimal-control-header",
322 | value
323 | );
324 | await this.plugin.saveSettings();
325 | });
326 | });
327 |
328 | new Setting(containerEl)
329 | .setName("Show thumbnails in collapsed state")
330 | .setDesc("Show thumbnails in the collapsed state of the node")
331 | .addToggle((toggle) => {
332 | toggle
333 | .setValue(
334 | this.plugin.settings.showThumbnailsInCollapsedState
335 | )
336 | .onChange(async (value) => {
337 | this.plugin.settings.showThumbnailsInCollapsedState =
338 | value;
339 | await this.plugin.saveSettings();
340 | });
341 | });
342 |
343 | new Setting(containerEl)
344 | .setName("Show aliases in collapsed state")
345 | .setDesc("Show aliases in the collapsed state of the node")
346 | .addToggle((toggle) => {
347 | toggle
348 | .setValue(this.plugin.settings.showAliasesInCollapsedState)
349 | .onChange(async (value) => {
350 | this.plugin.settings.showAliasesInCollapsedState =
351 | value;
352 | await this.plugin.saveSettings();
353 | });
354 | });
355 |
356 | new Setting(containerEl)
357 | .setName("Hide default node title")
358 | .setDesc("Hide the default title of the node")
359 | .addToggle((toggle) => {
360 | toggle
361 | .setValue(this.plugin.settings.hideDefaultNodeTitle)
362 | .onChange(async (value) => {
363 | this.plugin.settings.hideDefaultNodeTitle = value;
364 | document.body.toggleClass(
365 | "hide-default-node-title",
366 | value
367 | );
368 | await this.plugin.saveSettings();
369 | });
370 | });
371 |
372 | new Setting(containerEl)
373 | .setName("Show aliases always")
374 | .setDesc(
375 | "Show aliases always in the collapsed/expanded state of the node. Replace current title with alias."
376 | )
377 | .addToggle((toggle) => {
378 | toggle
379 | .setValue(this.plugin.settings.showAliasesAlways)
380 | .onChange(async (value) => {
381 | this.plugin.settings.showAliasesAlways = value;
382 | await this.plugin.saveSettings();
383 | });
384 | });
385 |
386 | new Setting(containerEl)
387 | .setName("Show thumbnails always")
388 | .setDesc(
389 | "Show thumbnails always in the collapsed state of the node"
390 | )
391 | .addToggle((toggle) => {
392 | toggle
393 | .setValue(this.plugin.settings.showThumbnailsAlways)
394 | .onChange(async (value) => {
395 | this.plugin.settings.showThumbnailsAlways = value;
396 | await this.plugin.saveSettings();
397 | });
398 | });
399 | }
400 |
401 | createCollapsableSetting(
402 | plugin: CanvasCollapsePlugin,
403 | containerEl: HTMLElement,
404 | name: string,
405 | desc: string,
406 | settingKey: keyof CollapsableNodeSettings
407 | ) {
408 | new Setting(containerEl)
409 | .setName(name)
410 | .setDesc(desc)
411 | .addToggle((toggle) => {
412 | toggle
413 | .setValue(plugin.settings[settingKey])
414 | .onChange(async (value) => {
415 | plugin.settings[settingKey] = value;
416 | await plugin.saveSettings();
417 | });
418 | });
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/src/patchUtils.ts:
--------------------------------------------------------------------------------
1 | import { around } from "monkey-around";
2 | import {
3 | Canvas,
4 | CanvasGroupNode,
5 | CanvasNode,
6 | Menu,
7 | setIcon,
8 | setTooltip,
9 | ViewState,
10 | WorkspaceLeaf,
11 | } from "obsidian";
12 |
13 | import { CanvasCoords } from "obsidian";
14 |
15 | import { CanvasView } from "obsidian";
16 | import CanvasCollapsePlugin from ".";
17 | import { CanvasData } from "obsidian/canvas";
18 |
19 | import {
20 | getSelectionCoords,
21 | handleCanvasMenu,
22 | handleMultiNodesViaNodes,
23 | handleSingleNode,
24 | } from "./utils";
25 | import CollapseControlHeader from "./ControlHeader";
26 |
27 | export const aroundCanvasMethods = (plugin: CanvasCollapsePlugin) => {
28 | const patchCanvasMethod = (canvasView: CanvasView) => {
29 | const checkCoords = (e: CanvasCoords, t: CanvasCoords) => {
30 | return (
31 | e.minX <= t.minX &&
32 | e.minY <= t.minY &&
33 | e.maxX >= t.maxX &&
34 | e.maxY >= t.maxY
35 | );
36 | };
37 |
38 | const checkTriggerByPlugin = () => {
39 | return plugin.triggerByPlugin;
40 | };
41 |
42 | const toggleTriggerByPlugin = () => {
43 | plugin.triggerByPlugin = !plugin.triggerByPlugin;
44 | };
45 |
46 | const patchCanvas = (canvas: Canvas) => {
47 | const uninstaller = around(canvas.constructor.prototype, {
48 | getContainingNodes: (next: any) =>
49 | function (e: CanvasCoords) {
50 | const result = next.call(this, e);
51 |
52 | const checkExistGroupNode: CanvasNode | null =
53 | this.nodeIndex
54 | .search(e)
55 | .find(
56 | (t: CanvasNode) =>
57 | t.unknownData.type === "group" ||
58 | (t as CanvasGroupNode).label
59 | );
60 | if (!checkExistGroupNode) return result;
61 | const renewCoords = checkExistGroupNode?.getBBox(true);
62 | if (renewCoords !== e && e.maxY - e.minY === 40) {
63 | const newResult = this.nodeIndex
64 | .search(renewCoords)
65 | .filter((t: any) => {
66 | return checkCoords(
67 | renewCoords,
68 | t.getBBox(true)
69 | );
70 | });
71 | if (newResult.length > result.length) {
72 | return newResult;
73 | }
74 | }
75 | return result;
76 | },
77 | requestSave: (next: any) =>
78 | function (args?: boolean, triggerBySelf?: boolean) {
79 | next.call(this, args);
80 | if (triggerBySelf) {
81 | if (args !== undefined) {
82 | this.data = this.getData();
83 | args && this.requestPushHistory(this.data);
84 | }
85 | }
86 | },
87 | pushHistory: (next: any) =>
88 | function (args: CanvasData) {
89 | if (checkTriggerByPlugin()) {
90 | toggleTriggerByPlugin();
91 | return;
92 | }
93 | return next.call(this, args);
94 | },
95 | selectAll: (next: any) =>
96 | function (e: Set) {
97 | if (this.wrapperEl.querySelector(".canvas-selection")) {
98 | const domCoords = getSelectionCoords(
99 | this.wrapperEl.querySelector(
100 | ".canvas-selection"
101 | ) as HTMLElement
102 | );
103 | if (domCoords) {
104 | const newResult = Array.from(e).filter(
105 | (t: CanvasNode) => {
106 | if (!t.unknownData.collapsed)
107 | return true;
108 | if (
109 | t.nodeEl.hasClass(
110 | "group-nodes-collapsed"
111 | )
112 | )
113 | return false;
114 | return checkCoords(
115 | domCoords,
116 | t.getBBox()
117 | );
118 | }
119 | );
120 | if (newResult.length > 0) {
121 | const ne = new Set(newResult);
122 | return next.call(this, ne);
123 | }
124 | if (newResult.length === 0) {
125 | return;
126 | }
127 | }
128 | }
129 | return next.call(this, e);
130 | },
131 | createTextNode: (next: any) =>
132 | function (args: any) {
133 | if (args.size === undefined && args.pos) {
134 | return next.call(this, {
135 | ...args,
136 | pos: {
137 | x: args.pos.x,
138 | y: args.pos.y,
139 | width: args?.size?.width || 250,
140 | height: args?.size?.height || 140,
141 | },
142 | size: {
143 | x: args.pos.x,
144 | y: args.pos.y,
145 | width: args?.size?.width || 250,
146 | height: args?.size?.height || 140,
147 | },
148 | });
149 | }
150 | return next.call(this, args);
151 | },
152 | createGroupNode: (next: any) =>
153 | function (args: any) {
154 | if (args.size !== undefined && args.pos) {
155 | return next.call(this, {
156 | ...args,
157 | pos: {
158 | x: args.pos.x,
159 | y: args.pos.y - 30,
160 | width: args?.size?.width,
161 | height: args?.size?.height + 30,
162 | },
163 | size: {
164 | x: args.pos.x,
165 | y: args.pos.y - 30,
166 | width: args?.size?.width,
167 | height: args?.size?.height + 30,
168 | },
169 | });
170 | }
171 | return next.call(this, args);
172 | },
173 | });
174 | plugin.register(uninstaller);
175 | plugin.patchSucceed = true;
176 |
177 | console.log("Obsidian-Collapse-Node: canvas patched");
178 | return true;
179 | };
180 |
181 | patchCanvas(canvasView.canvas);
182 | };
183 |
184 | const uninstaller = around(WorkspaceLeaf.prototype, {
185 | setViewState(next) {
186 | return function (state: ViewState, ...rest: any[]) {
187 | if (state.state?.file) {
188 | if (
189 | state.state?.file &&
190 | (state.state?.file as string).endsWith(".canvas")
191 | ) {
192 | setTimeout(() => {
193 | if (this.view.canvas) {
194 | patchCanvasMethod(this.view);
195 | uninstaller();
196 | }
197 | }, 400);
198 | }
199 | }
200 |
201 | return next.apply(this, [state, ...rest]);
202 | };
203 | },
204 | });
205 | plugin.register(uninstaller);
206 | };
207 |
208 | export const patchCanvasMenu = (plugin: CanvasCollapsePlugin) => {
209 | const triggerPlugin = () => {
210 | plugin.triggerByPlugin = true;
211 | };
212 |
213 | const patchMenu = async () => {
214 | const canvasLeaf = plugin.app.workspace
215 | .getLeavesOfType("canvas")
216 | .first();
217 |
218 | if (canvasLeaf?.isDeferred) {
219 | await canvasLeaf.loadIfDeferred();
220 | }
221 |
222 | const canvasView = canvasLeaf?.view;
223 |
224 | if (!canvasView) return false;
225 | const menu = (canvasView as CanvasView)?.canvas.menu;
226 | if (!menu) return false;
227 |
228 | const selection = menu.selection;
229 | if (!selection) return false;
230 |
231 | const menuUninstaller = around(menu.constructor.prototype, {
232 | render: (next: any) =>
233 | function (...args: any) {
234 | const result = next.call(this, ...args);
235 | if (this.menuEl.querySelector(".collapse-node-menu-item"))
236 | return result;
237 | const buttonEl = createEl(
238 | "button",
239 | "clickable-icon collapse-node-menu-item"
240 | );
241 | setTooltip(buttonEl, "Fold selected nodes", {
242 | placement: "top",
243 | });
244 | setIcon(buttonEl, "lucide-chevrons-left-right");
245 | this.menuEl.appendChild(buttonEl);
246 | buttonEl.addEventListener("click", () => {
247 | const pos = buttonEl.getBoundingClientRect();
248 | if (!buttonEl.hasClass("has-active-menu")) {
249 | buttonEl.toggleClass("has-active-menu", true);
250 | const menu = new Menu();
251 | const containingNodes =
252 | this.canvas.getContainingNodes(
253 | this.selection.bbox
254 | );
255 |
256 | handleCanvasMenu(menu, async (isFold: boolean) => {
257 | triggerPlugin();
258 | const currentSelection = this.canvas.selection;
259 | containingNodes.length > 1
260 | ? handleMultiNodesViaNodes(
261 | this.canvas,
262 | containingNodes,
263 | isFold
264 | )
265 | : currentSelection
266 | ? handleSingleNode(
267 | (
268 | Array.from(
269 | currentSelection
270 | )?.first()
271 | ),
272 | isFold
273 | )
274 | : "";
275 | buttonEl.toggleClass("has-active-menu", false);
276 | });
277 | menu.setParentElement(this.menuEl).showAtPosition({
278 | x: pos.x,
279 | y: pos.bottom,
280 | width: pos.width,
281 | overlap: true,
282 | });
283 | }
284 | });
285 |
286 | return result;
287 | },
288 | });
289 |
290 | plugin.register(menuUninstaller);
291 | plugin.app.workspace.trigger("collapse-node:patched-canvas");
292 |
293 | console.log("Obsidian-Collapse-Node: canvas history patched");
294 | return true;
295 | };
296 |
297 | plugin.app.workspace.onLayoutReady(async () => {
298 | if (!(await patchMenu())) {
299 | const evt = plugin.app.workspace.on("layout-change", async () => {
300 | (await patchMenu()) && plugin.app.workspace.offref(evt);
301 | });
302 | plugin.registerEvent(evt);
303 | }
304 | });
305 | };
306 |
307 | export const patchCanvasInteraction = (plugin: CanvasCollapsePlugin) => {
308 | const patchInteraction = () => {
309 | const canvasView = plugin.app.workspace
310 | .getLeavesOfType("canvas")
311 | .first()?.view;
312 | if (!canvasView) return false;
313 |
314 | const canvas = (canvasView as CanvasView)?.canvas.nodeInteractionLayer;
315 | if (!canvas) return false;
316 |
317 | const uninstaller = around(canvas.constructor.prototype, {
318 | render: (next: any) =>
319 | function (...args: any) {
320 | const result = next.call(this, ...args);
321 | if (!this.target) return result;
322 | const isCollapsed =
323 | this.target.nodeEl.hasClass("collapsed");
324 | const isGroupNodesCollapsed = this.target.nodeEl.hasClass(
325 | "group-nodes-collapsed"
326 | );
327 |
328 | if (this.target.unknownData) {
329 | this.interactionEl.toggleClass(
330 | "collapsed-interaction",
331 | isCollapsed
332 | );
333 | }
334 | this.interactionEl.toggleClass(
335 | "group-nodes-collapsed",
336 | isGroupNodesCollapsed
337 | );
338 | return result;
339 | },
340 | });
341 | plugin.register(uninstaller);
342 |
343 | console.log("Obsidian-Collapse-Node: canvas history patched");
344 | return true;
345 | };
346 |
347 | plugin.app.workspace.onLayoutReady(() => {
348 | if (!patchInteraction()) {
349 | const evt = plugin.app.workspace.on("layout-change", () => {
350 | patchInteraction() && plugin.app.workspace.offref(evt);
351 | });
352 | plugin.registerEvent(evt);
353 | }
354 | });
355 | };
356 |
357 | export const initControlHeader = (plugin: CanvasCollapsePlugin, node: any) => {
358 | return new CollapseControlHeader(plugin, node);
359 | };
360 |
361 | export const renderNodeWithHeader = (
362 | plugin: CanvasCollapsePlugin,
363 | node: any
364 | ) => {
365 | // If header already exists, don't create another one
366 | if (node.headerComponent) return;
367 |
368 | // Check node type against plugin settings
369 | const nodeType = node.unknownData.type;
370 |
371 | // Return early if this node type is disabled in settings
372 | if (
373 | nodeType === "file" &&
374 | node.file?.extension === "md" &&
375 | !plugin.settings.collapsableFileNode
376 | )
377 | return;
378 | if (
379 | nodeType === "file" &&
380 | node.file?.extension !== "md" &&
381 | !plugin.settings.collapsableAttachmentNode
382 | )
383 | return;
384 | if (nodeType === "group" && !plugin.settings.collapsableGroupNode) return;
385 | if (nodeType === "link" && !plugin.settings.collapsableLinkNode) return;
386 | if (nodeType === "text" && !plugin.settings.collapsableTextNode) return;
387 |
388 | // Check minimum line amount for text and file nodes
389 | if (
390 | plugin.settings.minLineAmount > 0 &&
391 | (nodeType === "text" || nodeType === "file")
392 | ) {
393 | let lineCount = 0;
394 |
395 | if (nodeType === "text" && typeof node.text === "string") {
396 | lineCount = node.text.split("\n").length;
397 | } else if (
398 | nodeType === "file" &&
399 | node.file?.extension === "md" &&
400 | node.child
401 | ) {
402 | lineCount = node.child.data?.split("\n").length || 0;
403 | }
404 |
405 | if (lineCount < plugin.settings.minLineAmount) return;
406 | }
407 |
408 | // Create header component
409 | node.headerComponent = initControlHeader(
410 | plugin,
411 | node
412 | ) as CollapseControlHeader;
413 | node.nodeEl.setAttribute("data-node-type", nodeType);
414 |
415 | // Wait for containerEl to be loaded before adding header
416 | const addHeader = () => {
417 | if (!node.containerEl) {
418 | setTimeout(addHeader, 0);
419 | return;
420 | }
421 | (node.containerEl as HTMLDivElement).prepend(
422 | node.headerComponent.onload()
423 | );
424 | };
425 | addHeader();
426 |
427 | // Apply collapsed state if needed
428 | if (node.unknownData.collapsed) {
429 | node.nodeEl.toggleClass("collapsed", true);
430 | node.headerComponent.updateEdges();
431 | }
432 | };
433 |
434 | export const updateAllNodeWithHeader = (plugin: CanvasCollapsePlugin) => {
435 | const canvasLeaves = plugin.app.workspace.getLeavesOfType("canvas");
436 |
437 | for (const canvasLeaf of canvasLeaves) {
438 | const canvas: Canvas = (canvasLeaf.view as CanvasView)?.canvas;
439 | if (!canvas) continue;
440 |
441 | const nodes = canvas.nodes.values();
442 |
443 | for (const node of nodes) {
444 | renderNodeWithHeader(plugin, node);
445 | }
446 | }
447 | };
448 |
449 | export const patchCanvasNode = (plugin: CanvasCollapsePlugin) => {
450 | const patchNode = () => {
451 | const canvasView = plugin.app.workspace
452 | .getLeavesOfType("canvas")
453 | .first()?.view;
454 | if (!canvasView) return false;
455 |
456 | const canvas: Canvas = (canvasView as CanvasView)?.canvas;
457 | if (!canvas) return false;
458 |
459 | const node = (
460 | plugin.app.workspace.getLeavesOfType("canvas").first()?.view as any
461 | ).canvas.nodes
462 | .values()
463 | .next().value;
464 |
465 | if (!node) return false;
466 | let prototype = Object.getPrototypeOf(node);
467 | while (prototype && prototype !== Object.prototype) {
468 | prototype = Object.getPrototypeOf(prototype);
469 | // @ts-expected-error Find the parent prototype
470 | if (prototype.renderZIndex) {
471 | break;
472 | }
473 | }
474 |
475 | console.log(prototype);
476 |
477 | if (!prototype) return false;
478 |
479 | const uninstaller = around(prototype, {
480 | render: (next: any) =>
481 | function (...args: any) {
482 | const result = next.call(this, ...args);
483 | renderNodeWithHeader(plugin, this);
484 | return result;
485 | },
486 | getBBox: (next: any) =>
487 | function (containing?: boolean) {
488 | const result = next.call(this);
489 | if (
490 | containing !== true &&
491 | (this.nodeEl as HTMLDivElement).hasClass("collapsed")
492 | ) {
493 | const x = this.x;
494 | const y = this.y;
495 | const width = this.width;
496 | const height = 40;
497 | return {
498 | minX: x,
499 | minY: y,
500 | maxX: x + width,
501 | maxY: y + height,
502 | };
503 | }
504 | return result;
505 | },
506 | setData: (next: any) =>
507 | function (data: any) {
508 | if (data.collapsed !== undefined) {
509 | this.headerComponent?.setCollapsed(data.collapsed);
510 | }
511 | return next.call(this, data);
512 | },
513 | });
514 | plugin.register(uninstaller);
515 | updateAllNodeWithHeader(plugin);
516 |
517 | console.log("Obsidian-Collapse-Node: canvas node patched");
518 | return true;
519 | };
520 |
521 | plugin.app.workspace.onLayoutReady(() => {
522 | if (!patchNode()) {
523 | const evt = plugin.app.workspace.on("layout-change", () => {
524 | patchNode() && plugin.app.workspace.offref(evt);
525 | });
526 | plugin.registerEvent(evt);
527 | }
528 | });
529 | };
530 |
--------------------------------------------------------------------------------
/src/types/canvas.d.ts:
--------------------------------------------------------------------------------
1 | export type CanvasNodeType = 'link' | 'file' | 'text' | 'group';
2 | export type CanvasDirection =
3 | 'bottomright'
4 | | 'bottomleft'
5 | | 'topright'
6 | | 'topleft'
7 | | 'right'
8 | | 'left'
9 | | 'top'
10 | | 'bottom';
11 |
12 | export interface CanvasNodeUnknownData {
13 | id: string;
14 | type: CanvasNodeType;
15 | collapsed: boolean;
16 |
17 | [key: string]: any;
18 | }
19 |
--------------------------------------------------------------------------------
/src/types/custom.d.ts:
--------------------------------------------------------------------------------
1 | import { CanvasNode, Component } from "obsidian";
2 | import { CanvasNodeType } from "./canvas";
3 |
4 | interface HeaderComponent extends Component {
5 | checkNodeType(): CanvasNodeType;
6 |
7 | initHeader(): void;
8 |
9 | initContent(): void;
10 |
11 | initTypeIcon(): void;
12 |
13 | setIconOrContent(action: string): void;
14 |
15 | setCollapsed(collapsed: boolean): void;
16 |
17 | toggleCollapsed(): Promise;
18 |
19 | refreshHistory(): void;
20 |
21 | updateNode(): void;
22 |
23 | updateNodesInGroup(): void;
24 |
25 | updateEdges(): void;
26 |
27 | updateEdgesInGroup(node: CanvasNode, triggerCollapsed?: boolean): void;
28 | }
29 |
--------------------------------------------------------------------------------
/src/types/event.d.ts:
--------------------------------------------------------------------------------
1 | type CanvasCollapseEvt = "collapse" | "expand";
2 |
3 |
--------------------------------------------------------------------------------
/src/types/obsidian.d.ts:
--------------------------------------------------------------------------------
1 | import "obsidian";
2 | import { App, Component, EventRef, ItemView, TFile } from "obsidian";
3 | import { CanvasDirection, CanvasNodeUnknownData } from "./canvas";
4 | import { CanvasData } from "obsidian/canvas";
5 |
6 | declare module "obsidian" {
7 |
8 | type CanvasNodeID = string;
9 | type CanvasEdgeID = string;
10 |
11 | interface Menu {
12 | setParentElement(parent: HTMLElement): Menu;
13 | }
14 |
15 | interface MenuItem {
16 | setSubmenu(): Menu;
17 | }
18 |
19 | interface WorkspaceLeaf {
20 | rebuildView(): void;
21 | }
22 |
23 | interface Workspace {
24 | on(
25 | name: CanvasCollapseEvt,
26 | cb: (
27 | view: ItemView,
28 | nodeID?: string[] | undefined,
29 | ) => any,
30 | ): EventRef;
31 |
32 | on(
33 | name: "canvas:selection-menu",
34 | cb: (
35 | menu: Menu,
36 | canvas: Canvas,
37 | ) => any,
38 | ): EventRef;
39 |
40 | on(
41 | name: "canvas:node-menu",
42 | cb: (
43 | menu: Menu,
44 | node: CanvasNode,
45 | ) => any,
46 | ): EventRef;
47 |
48 | on(
49 | name: "collapse-node:plugin-disabled",
50 | cb: () => any,
51 | ): EventRef;
52 |
53 | on(
54 | name: "collapse-node:patched-canvas",
55 | cb: () => any,
56 | ): EventRef;
57 | }
58 |
59 | interface Workspace {
60 | trigger(
61 | name: CanvasCollapseEvt,
62 | view: ItemView,
63 | nodeID?: string[] | undefined,
64 | ): void;
65 |
66 | trigger(
67 | name: "collapse-node:plugin-disabled",
68 | ): void;
69 |
70 | trigger(
71 | name: "collapse-node:patched-canvas",
72 | ): void;
73 | }
74 |
75 | interface CanvasView extends View {
76 | canvas: Canvas;
77 | }
78 |
79 | interface Canvas {
80 | readonly: boolean;
81 |
82 | x: number;
83 | y: number;
84 | nodes: Map;
85 | edges: Map;
86 | nodeInteractionLayer: CanvasInteractionLayer;
87 | selection: Set;
88 |
89 | menu: CanvasMenu;
90 |
91 | wrapperEl: HTMLElement;
92 |
93 | history: any;
94 | requestPushHistory: any;
95 | nodeIndex: any;
96 |
97 | requestSave(save?: boolean, triggerBySelf?: boolean): void;
98 |
99 | getData(): CanvasData;
100 |
101 | setData(data: CanvasData): void;
102 |
103 | getEdgesForNode(node: CanvasNode): CanvasEdge[];
104 |
105 | getContainingNodes(coords: CanvasCoords): CanvasNode[];
106 |
107 | deselectAll(): void;
108 |
109 | select(nodes: CanvasNode): void;
110 |
111 | requestFrame(): void;
112 | }
113 |
114 | interface ICanvasData {
115 | nodes: CanvasNode[];
116 | edges: CanvasEdge[];
117 | }
118 |
119 | interface CanvasMenu {
120 | containerEl: HTMLElement;
121 | menuEl: HTMLElement;
122 | canvas: Canvas;
123 | selection: CanvasSelection;
124 |
125 | render(): void;
126 |
127 | updateZIndex(): void;
128 | }
129 |
130 | interface CanvasSelection {
131 | selectionEl: HTMLElement;
132 | resizerEls: HTMLElement;
133 | canvas: Canvas;
134 | bbox: CanvasCoords | undefined;
135 |
136 | render(): void;
137 |
138 | hide(): void;
139 |
140 | onResizePointerDown(e: PointerEvent, direction: CanvasDirection): void;
141 |
142 | update(bbox: CanvasCoords): void;
143 |
144 | }
145 |
146 | interface CanvasInteractionLayer {
147 | interactionEl: HTMLElement;
148 | canvas: Canvas;
149 | target: CanvasNode | null;
150 |
151 | render(): void;
152 |
153 | setTarget(target: CanvasNode | null): void;
154 | }
155 |
156 | interface CanvasNode {
157 | id: CanvasNodeID;
158 |
159 | x: number;
160 | y: number;
161 | width: number;
162 | height: number;
163 | zIndex: number;
164 | bbox: CanvasCoords;
165 | unknownData: CanvasNodeUnknownData;
166 | renderedZIndex: number;
167 |
168 | headerComponent: Component;
169 |
170 | nodeEl: HTMLElement;
171 | labelEl: HTMLElement;
172 | contentEl: HTMLElement;
173 | containerEl: HTMLElement;
174 |
175 | canvas: Canvas;
176 | app: App;
177 |
178 | getBBox(containing?: boolean): CanvasCoords;
179 |
180 | render(): void;
181 | }
182 |
183 | interface CanvasTextNode extends CanvasNode {
184 | text: string;
185 | }
186 |
187 | interface CanvasFileNode extends CanvasNode {
188 | file: TFile;
189 | }
190 |
191 | interface CanvasLinkNode extends CanvasNode {
192 | url: string;
193 | }
194 |
195 | interface CanvasGroupNode extends CanvasNode {
196 | label: string;
197 | }
198 |
199 | interface CanvasEdge {
200 | id: CanvasEdgeID;
201 |
202 | label: string | undefined;
203 | lineStartGroupEl: SVGGElement;
204 | lineEndGroupEl: SVGGElement;
205 | lineGroupEl: SVGGElement;
206 |
207 | labelElement: {
208 | wrapperEl: HTMLElement;
209 | textEl: HTMLElement;
210 | };
211 |
212 | path: {
213 | display: SVGPathElement;
214 | interaction: SVGPathElement;
215 | }
216 |
217 | canvas: Canvas;
218 | bbox: CanvasCoords;
219 | render(): void;
220 |
221 | unknownData: CanvasNodeUnknownData;
222 | }
223 |
224 | interface CanvasCoords {
225 | maxX: number;
226 | maxY: number;
227 | minX: number;
228 | minY: number;
229 | }
230 | }
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | App,
3 | Canvas,
4 | CanvasCoords,
5 | CanvasNode,
6 | ItemView,
7 | Menu,
8 | MenuItem,
9 | Modal,
10 | SuggestModal,
11 | TFile,
12 | } from "obsidian";
13 | import CollapseControlHeader from "./ControlHeader";
14 | import CanvasCollapsePlugin from ".";
15 |
16 | const getBoundingRect = (nodes: CanvasNode[]) => {
17 | const bboxArray = nodes.map((t: CanvasNode) => t.getBBox());
18 |
19 | const minX = Math.min(...bboxArray.map((t: CanvasCoords) => t.minX));
20 | const minY = Math.min(...bboxArray.map((t: CanvasCoords) => t.minY));
21 | const maxX = Math.max(...bboxArray.map((t: CanvasCoords) => t.maxX));
22 | const maxY = Math.max(...bboxArray.map((t: CanvasCoords) => t.maxY));
23 |
24 | return {
25 | minX,
26 | minY,
27 | maxX,
28 | maxY,
29 | };
30 | };
31 | const updateSelection = (canvas: Canvas) => {
32 | if (canvas.menu.selection.bbox) {
33 | const selection = Array.from(canvas.selection);
34 | const currentNodesInSelection = canvas.getContainingNodes(
35 | canvas.menu.selection.bbox
36 | );
37 | if (currentNodesInSelection.length > 0) {
38 | const boundingRect = getBoundingRect(
39 | selection.length > currentNodesInSelection.length
40 | ? selection
41 | : currentNodesInSelection
42 | );
43 | if (boundingRect) {
44 | canvas.menu.selection.update(boundingRect);
45 | }
46 | }
47 | }
48 | };
49 | const handleMultiNodes = (
50 | canvas: Canvas,
51 | allNodes: boolean,
52 | collapse: boolean
53 | ) => {
54 | const nodes = allNodes
55 | ? Array.from(canvas.nodes.values())
56 | : (Array.from(canvas.selection) as any[]);
57 | const canvasData = canvas.getData();
58 |
59 | if (nodes && nodes.length > 0) {
60 | for (const node of nodes) {
61 | if (node.unknownData.type === "group") {
62 | node.headerComponent.updateNodesInGroup(collapse);
63 | }
64 | node.headerComponent?.setCollapsed(collapse);
65 | const nodeData = canvasData.nodes.find(
66 | (t: any) => t.id === node.id
67 | );
68 | if (nodeData) nodeData.collapsed = collapse;
69 | }
70 | canvas.setData(canvasData);
71 | }
72 | canvas.requestSave(true, true);
73 | canvas.requestFrame();
74 | updateSelection(canvas);
75 | };
76 | export const handleMultiNodesViaNodes = (
77 | canvas: Canvas,
78 | nodes: CanvasNode[],
79 | collapse: boolean
80 | ) => {
81 | const canvasData = canvas.getData();
82 |
83 | if (nodes && nodes.length > 0) {
84 | for (const node of nodes) {
85 | if (node.unknownData.type === "group") {
86 | (
87 | node.headerComponent as CollapseControlHeader
88 | ).updateNodesInGroup(collapse);
89 | }
90 | (node.headerComponent as CollapseControlHeader)?.setCollapsed(
91 | collapse
92 | );
93 | const nodeData = canvasData.nodes.find(
94 | (t: any) => t.id === node.id
95 | );
96 | if (nodeData) nodeData.collapsed = collapse;
97 | }
98 | canvas.setData(canvasData);
99 | }
100 | canvas.requestSave(true, true);
101 | updateSelection(canvas);
102 | };
103 | export const handleSingleNode = (node: CanvasNode, collapse: boolean) => {
104 | if (node.unknownData.type === "group") {
105 | (node.headerComponent as CollapseControlHeader).updateNodesInGroup();
106 | }
107 | const canvasData = node.canvas.getData();
108 | const nodeData = canvasData.nodes.find((t: any) => t.id === node.id);
109 | if (nodeData) nodeData.collapsed = collapse;
110 | node.canvas.setData(canvasData);
111 | node.canvas.requestSave(true, true);
112 | updateSelection(node.canvas);
113 | };
114 |
115 | export const handleNodesViaCommands = (
116 | plugin: CanvasCollapsePlugin,
117 | checking: boolean,
118 | allNodes: boolean,
119 | collapse: boolean
120 | ) => {
121 | plugin.triggerByPlugin = true;
122 | const currentView = plugin.app.workspace.getActiveViewOfType(ItemView);
123 | if (currentView && currentView.getViewType() === "canvas") {
124 | if (!checking) {
125 | const canvasView = currentView as any;
126 | const canvas = canvasView.canvas as Canvas;
127 | handleMultiNodes(canvas, allNodes, collapse);
128 | }
129 |
130 | return true;
131 | }
132 | };
133 |
134 | const createHandleContextMenu = (
135 | section: string,
136 | callback: (isFold: boolean) => Promise
137 | ) => {
138 | return (menu: Menu) => {
139 | menu.addItem((item: MenuItem) => {
140 | const subMenu = item
141 | .setSection(section)
142 | .setTitle("Collapse node")
143 | .setIcon("chevrons-left-right")
144 | .setSubmenu();
145 | handleCanvasMenu(subMenu, callback);
146 | });
147 | };
148 | };
149 |
150 | export const handleCanvasMenu = (
151 | subMenu: Menu,
152 | callback: (isFold: boolean) => Promise
153 | ) => {
154 | return subMenu
155 | .addItem((item: MenuItem) => {
156 | item.setIcon("fold-vertical")
157 | .setTitle("Fold selected nodes")
158 | .onClick(async () => {
159 | await callback(true);
160 | });
161 | })
162 | .addItem((item: any) => {
163 | item.setIcon("unfold-vertical")
164 | .setTitle("Expand selected nodes")
165 | .onClick(async () => {
166 | await callback(false);
167 | });
168 | });
169 | };
170 |
171 | export const handleSelectionContextMenu = (
172 | plugin: CanvasCollapsePlugin,
173 | menu: Menu,
174 | canvas: Canvas
175 | ) => {
176 | plugin.triggerByPlugin = true;
177 | const callback = async (isFold: boolean) => {
178 | handleMultiNodes(canvas, false, isFold);
179 | };
180 | createHandleContextMenu("action", callback)(menu);
181 | };
182 |
183 | export const handleNodeContextMenu = (
184 | plugin: CanvasCollapsePlugin,
185 | menu: Menu,
186 | node: CanvasNode
187 | ) => {
188 | plugin.triggerByPlugin = true;
189 | const callback = async (isFold: boolean) => {
190 | handleSingleNode(node, isFold);
191 | };
192 | createHandleContextMenu("canvas", callback)(menu);
193 |
194 | // Add Alias and Thumbnail menu items
195 | menu.addItem((item: MenuItem) => {
196 | item.setSection("canvas")
197 | .setTitle("Set Node Alias")
198 | .setIcon("text-cursor-input")
199 | .onClick(async () => {
200 | await setNodeAlias(plugin, node);
201 | });
202 | });
203 |
204 | menu.addItem((item: MenuItem) => {
205 | item.setSection("canvas")
206 | .setTitle("Set Node Thumbnail")
207 | .setIcon("image")
208 | .onClick(async () => {
209 | await setNodeThumbnail(plugin, node);
210 | });
211 | });
212 |
213 | // Add option to remove alias/thumbnail
214 | if (node.unknownData.alias || node.unknownData.thumbnail) {
215 | menu.addItem((item: MenuItem) => {
216 | item.setSection("canvas")
217 | .setTitle("Remove Node Customizations")
218 | .setIcon("trash")
219 | .onClick(async () => {
220 | await removeNodeCustomizations(plugin, node);
221 | });
222 | });
223 | }
224 | };
225 |
226 | // Function to set alias for a node
227 | export const setNodeAlias = async (
228 | plugin: CanvasCollapsePlugin,
229 | node: CanvasNode
230 | ) => {
231 | const modal = new TextInputModal(
232 | plugin.app,
233 | "Enter alias for node",
234 | node.unknownData.alias || "",
235 | "Set Alias"
236 | );
237 |
238 | const alias = await modal.openAndGetValue();
239 | if (alias !== null) {
240 | // Set the alias in node data
241 | node.unknownData.alias = alias;
242 |
243 | // Update the node if it has a header component
244 | if (
245 | node.headerComponent &&
246 | node.headerComponent instanceof CollapseControlHeader
247 | ) {
248 | const header = node.headerComponent as CollapseControlHeader;
249 | header.updateNodeAlias(alias);
250 | header.updateNode();
251 | }
252 |
253 | // Save the canvas state
254 | node.canvas.requestSave(false, true);
255 | }
256 | };
257 |
258 | // Function to set thumbnail for a node
259 | export const setNodeThumbnail = async (
260 | plugin: CanvasCollapsePlugin,
261 | node: CanvasNode
262 | ) => {
263 | const modal = new ThumbnailSelectionModal(plugin.app);
264 | const thumbnailPath = await modal.openAndGetValue();
265 |
266 | if (thumbnailPath) {
267 | // Set the thumbnail in node data
268 | node.unknownData.thumbnail = thumbnailPath;
269 |
270 | // Update the node if it has a header component
271 | if (
272 | node.headerComponent &&
273 | node.headerComponent instanceof CollapseControlHeader
274 | ) {
275 | const header = node.headerComponent as CollapseControlHeader;
276 | header.updateNodeThumbnail(thumbnailPath);
277 | header.updateNode();
278 | }
279 |
280 | // Save the canvas state
281 | node.canvas.requestSave(false, true);
282 | }
283 | };
284 |
285 | // Function to remove alias and thumbnail from node
286 | export const removeNodeCustomizations = async (
287 | plugin: CanvasCollapsePlugin,
288 | node: CanvasNode
289 | ) => {
290 | delete node.unknownData.alias;
291 | delete node.unknownData.thumbnail;
292 |
293 | // Update the node if it has a header component
294 | if (
295 | node.headerComponent &&
296 | node.headerComponent instanceof CollapseControlHeader
297 | ) {
298 | const header = node.headerComponent as CollapseControlHeader;
299 | header.updateNodeAlias("");
300 | header.updateNodeThumbnail("");
301 | header.updateNode();
302 | }
303 |
304 | // Save the canvas state
305 | node.canvas.requestSave(false, true);
306 | };
307 |
308 | // Class for text input modal
309 | class TextInputModal extends Modal {
310 | private value: string;
311 | private inputEl: HTMLInputElement;
312 | private resolvePromise: (value: string | null) => void;
313 | private buttonText: string;
314 |
315 | constructor(
316 | app: App,
317 | private title: string,
318 | initialValue: string,
319 | buttonText: string
320 | ) {
321 | super(app);
322 | this.value = initialValue;
323 | this.buttonText = buttonText;
324 | }
325 |
326 | onOpen() {
327 | const { contentEl } = this;
328 |
329 | contentEl.createEl("h2", { text: this.title });
330 |
331 | this.inputEl = contentEl.createEl("input", {
332 | type: "text",
333 | value: this.value,
334 | });
335 | this.inputEl.style.width = "100%";
336 | this.inputEl.style.marginBottom = "1em";
337 |
338 | // Focus input
339 | setTimeout(() => this.inputEl.focus(), 10);
340 |
341 | // Add buttons
342 | const buttonContainer = contentEl.createDiv();
343 | buttonContainer.style.display = "flex";
344 | buttonContainer.style.justifyContent = "flex-end";
345 | buttonContainer.style.gap = "10px";
346 |
347 | const cancelButton = buttonContainer.createEl("button", {
348 | text: "Cancel",
349 | });
350 | const saveButton = buttonContainer.createEl("button", {
351 | text: this.buttonText,
352 | cls: "mod-cta",
353 | });
354 |
355 | cancelButton.addEventListener("click", () => {
356 | this.resolvePromise(null);
357 | this.close();
358 | });
359 |
360 | saveButton.addEventListener("click", () => {
361 | this.resolvePromise(this.inputEl.value);
362 | this.close();
363 | });
364 |
365 | // Handle Enter key
366 | this.inputEl.addEventListener("keydown", (e) => {
367 | if (e.key === "Enter") {
368 | this.resolvePromise(this.inputEl.value);
369 | this.close();
370 | }
371 | });
372 | }
373 |
374 | onClose() {
375 | const { contentEl } = this;
376 | contentEl.empty();
377 | }
378 |
379 | openAndGetValue(): Promise {
380 | return new Promise((resolve) => {
381 | this.resolvePromise = resolve;
382 | this.open();
383 | });
384 | }
385 | }
386 |
387 | // Class for thumbnail selection modal
388 | class ThumbnailSelectionModal extends Modal {
389 | private resolvePromise: (value: string | null) => void;
390 | private inputEl: HTMLInputElement;
391 | private fileInput: HTMLInputElement;
392 | private preview: HTMLImageElement;
393 |
394 | constructor(app: App) {
395 | super(app);
396 | }
397 |
398 | onOpen() {
399 | const { contentEl } = this;
400 |
401 | contentEl.createEl("h2", { text: "Set Node Thumbnail" });
402 |
403 | // URL input
404 | contentEl.createEl("p", {
405 | text: "Enter image URL or path to an attachment:",
406 | });
407 |
408 | this.inputEl = contentEl.createEl("input", {
409 | type: "text",
410 | placeholder: "https://example.com/image.jpg or image.jpg",
411 | });
412 | this.inputEl.style.width = "100%";
413 | this.inputEl.style.marginBottom = "1em";
414 |
415 | // Or use file picker to select from vault
416 | contentEl.createEl("p", {
417 | text: "Or select an image from your attachments:",
418 | });
419 |
420 | const selectFileButton = contentEl.createEl("button", {
421 | text: "Browse vault files",
422 | });
423 | selectFileButton.addEventListener("click", () => {
424 | this.openFilePicker();
425 | });
426 |
427 | // Preview area
428 | contentEl.createEl("p", {
429 | text: "Preview:",
430 | cls: "thumbnail-preview-label",
431 | });
432 | this.preview = contentEl.createEl("img", { cls: "thumbnail-preview" });
433 | this.preview.style.maxWidth = "100%";
434 | this.preview.style.maxHeight = "150px";
435 | this.preview.style.display = "none";
436 |
437 | // Update preview when URL changes
438 | this.inputEl.addEventListener("input", () => {
439 | this.updatePreview();
440 | });
441 |
442 | // Buttons
443 | const buttonContainer = contentEl.createDiv();
444 | buttonContainer.style.display = "flex";
445 | buttonContainer.style.justifyContent = "flex-end";
446 | buttonContainer.style.gap = "10px";
447 | buttonContainer.style.marginTop = "1em";
448 |
449 | const cancelButton = buttonContainer.createEl("button", {
450 | text: "Cancel",
451 | });
452 | const saveButton = buttonContainer.createEl("button", {
453 | text: "Set Thumbnail",
454 | cls: "mod-cta",
455 | });
456 |
457 | cancelButton.addEventListener("click", () => {
458 | this.resolvePromise(null);
459 | this.close();
460 | });
461 |
462 | saveButton.addEventListener("click", () => {
463 | this.resolvePromise(this.inputEl.value);
464 | this.close();
465 | });
466 | }
467 |
468 | openFilePicker() {
469 | const fileSuggestModal = new FileSuggestModal(this.app, (file) => {
470 | if (file) {
471 | this.inputEl.value = file.path;
472 | this.updatePreview();
473 | }
474 | });
475 | fileSuggestModal.open();
476 | }
477 |
478 | updatePreview() {
479 | const url = this.inputEl.value;
480 | if (url) {
481 | try {
482 | // For remote URLs
483 | if (url.startsWith("http")) {
484 | this.preview.src = url;
485 | this.preview.style.display = "block";
486 | }
487 | // For local files
488 | else {
489 | this.preview.src =
490 | this.app.vault.adapter.getResourcePath(url);
491 | this.preview.style.display = "block";
492 | }
493 | } catch (e) {
494 | this.preview.style.display = "none";
495 | }
496 | } else {
497 | this.preview.style.display = "none";
498 | }
499 | }
500 |
501 | onClose() {
502 | const { contentEl } = this;
503 | contentEl.empty();
504 | }
505 |
506 | openAndGetValue(): Promise {
507 | return new Promise((resolve) => {
508 | this.resolvePromise = resolve;
509 | this.open();
510 | });
511 | }
512 | }
513 |
514 | // File selection modal for picking a file from the vault
515 | class FileSuggestModal extends SuggestModal {
516 | private callback: (file: TFile | null) => void;
517 |
518 | constructor(app: App, callback: (file: TFile | null) => void) {
519 | super(app);
520 | this.callback = callback;
521 | }
522 |
523 | getSuggestions(query: string): TFile[] {
524 | const imageExtensions = [
525 | "png",
526 | "jpg",
527 | "jpeg",
528 | "gif",
529 | "bmp",
530 | "svg",
531 | "webp",
532 | ];
533 |
534 | // Get all image files from the vault
535 | const files = this.app.vault
536 | .getFiles()
537 | .filter(
538 | (file) =>
539 | imageExtensions.includes(file.extension.toLowerCase()) &&
540 | file.path.toLowerCase().includes(query.toLowerCase())
541 | );
542 |
543 | return files;
544 | }
545 |
546 | renderSuggestion(file: TFile, el: HTMLElement) {
547 | el.createEl("div", { text: file.path });
548 | }
549 |
550 | onChooseSuggestion(file: TFile, evt: MouseEvent | KeyboardEvent) {
551 | this.callback(file);
552 | }
553 |
554 | onClose() {
555 | // If no selection was made, call the callback with null
556 | if (this.callback) {
557 | this.callback(null);
558 | }
559 | }
560 | }
561 |
562 | export const refreshAllCanvasView = (app: App) => {
563 | const cavasLeaves = app.workspace.getLeavesOfType("canvas");
564 | if (!cavasLeaves || cavasLeaves.length === 0) return;
565 | for (const leaf of cavasLeaves) {
566 | leaf.rebuildView();
567 | }
568 | };
569 |
570 | export const getSelectionCoords = (dom: HTMLElement) => {
571 | const domHTML = dom.outerHTML;
572 |
573 | const translateRegex = /translate\((-?\d+\.?\d*)px, (-?\d+\.?\d*)px\)/;
574 | const sizeRegex = /width: (\d+\.?\d*)px; height: (\d+\.?\d*)px;/;
575 | const translateMatches = domHTML.match(translateRegex);
576 | const sizeMatches = domHTML.match(sizeRegex);
577 | if (translateMatches && sizeMatches) {
578 | const x = parseFloat(translateMatches[1]);
579 | const y = parseFloat(translateMatches[2]);
580 |
581 | const width = parseFloat(sizeMatches[1]);
582 | const height = parseFloat(sizeMatches[2]);
583 |
584 | return {
585 | minX: x,
586 | minY: y,
587 | maxX: x + width,
588 | maxY: y + height,
589 | };
590 | }
591 | };
592 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | .canvas-node-collapse-control {
2 | z-index: 100;
3 | position: relative;
4 |
5 | overflow: hidden;
6 |
7 | min-width: 100%;
8 | height: calc(30px * var(--zoom-multiplier));
9 | /*max-height: 100px;*/
10 | min-height: max(calc(30px * var(--zoom-multiplier)), 20px);
11 | cursor: pointer;
12 |
13 | display: flex;
14 | align-items: center;
15 | padding-left: var(--size-4-2);
16 | gap: var(--size-4-3);
17 | margin-top: calc(30px * var(--zoom-multiplier) * -1);
18 |
19 | transition: background-color 0.2s ease-in-out;
20 | }
21 |
22 | .canvas-node-container:has(.canvas-node-content > img)
23 | .canvas-node-collapse-control {
24 | position: absolute;
25 | }
26 |
27 | .canvas-node-container:not(:has(.canvas-node-content > img)):has(
28 | .canvas-node-collapse-control
29 | ) {
30 | padding-top: calc(30px * var(--zoom-multiplier));
31 | }
32 |
33 | .canvas-node-container:has(.canvas-node-content > img)
34 | .canvas-node-collapse-control {
35 | margin-top: 0;
36 | }
37 |
38 | .canvas-node:not(.collapsed) .canvas-node-collapse-control {
39 | background-color: rgba(var(--canvas-color), 0.15);
40 | }
41 |
42 | .canvas-node:not(.collapsed) .canvas-node-collapse-control:hover {
43 | background-color: rgba(var(--canvas-color), 0.5);
44 | }
45 |
46 | .canvas-node.collapsed .canvas-node-collapse-control {
47 | background-color: rgb(var(--canvas-color));
48 | }
49 |
50 | .canvas-node:has(.canvas-node-collapse-control) {
51 | width: fit-content;
52 | height: fit-content;
53 | }
54 |
55 | /*.canvas-node-group:not(.is-focused):not(.is-selected) {*/
56 | /* pointer-events: unset;*/
57 | /*}*/
58 |
59 | .canvas-node-group:has(.canvas-node-collapse-control)
60 | .canvas-node-collapse-control {
61 | pointer-events: initial !important;
62 | }
63 |
64 | /*.canvas-node.collapsed .canvas-node-collapse-control {*/
65 | /* border-radius: var(--radius-m);*/
66 | /*}*/
67 |
68 | .canvas-node.collapsed {
69 | height: calc(30px * var(--zoom-multiplier)) !important;
70 | max-height: fit-content;
71 | transition: height 0.2s ease-in-out, max-height 0.2s ease-in-out;
72 | }
73 |
74 | .canvas-node.collapsed .canvas-node-placeholder {
75 | display: none;
76 | }
77 |
78 | .group-nodes-collapsed,
79 | .group-edges-collapsed {
80 | display: none;
81 | transition: opacity 0.2s ease-in-out;
82 | opacity: 0;
83 | }
84 |
85 | .canvas-node.collapsed .canvas-node-content {
86 | display: none;
87 | transition: opacity 0.2s ease-in-out;
88 | opacity: 0;
89 | }
90 |
91 | .collapsed-interaction {
92 | height: calc(30px * var(--zoom-multiplier)) !important;
93 | transition: height 0.2s ease-in-out;
94 | }
95 |
96 | .canvas-node-collapse-control-icon,
97 | .canvas-node-type-icon {
98 | display: flex;
99 | }
100 |
101 | .canvas-node-collapse-control-icon svg,
102 | .canvas-node-type-icon svg {
103 | height: calc(18px * var(--zoom-multiplier));
104 | width: calc(18px * var(--zoom-multiplier));
105 | }
106 |
107 | .canvas-node-collapse-control + .canvas-node-content.media-embed img {
108 | height: auto;
109 | max-height: 100%;
110 | object-fit: contain;
111 | }
112 |
113 | .minimal-control-header .canvas-node-collapse-control-title,
114 | .minimal-control-header .canvas-node-type-icon {
115 | display: none;
116 | }
117 |
118 | .canvas-node {
119 | /* Add transition for height and max-height only */
120 | transition: height 0.2s ease-in-out, max-height 0.2s ease-in-out;
121 | }
122 |
123 | .canvas-node-content {
124 | /* Add transition for opacity */
125 | transition: opacity 0.2s ease-in-out;
126 | opacity: 1;
127 | }
128 |
129 | .canvas-node.collapsed .canvas-node-content {
130 | opacity: 0;
131 | /* Keep the display:none but add it with a delay so the opacity transition is visible */
132 | transition: opacity 0.2s ease-in-out;
133 | }
134 |
135 | /* Add rotation animation to the collapse icon */
136 | .canvas-node-collapse-control-icon svg {
137 | transition: transform 0.2s ease;
138 | }
139 |
140 | .canvas-node.collapsed .canvas-node-collapse-control-icon svg {
141 | transform: rotate(-90deg);
142 | transition: transform 0.2s ease;
143 | }
144 |
145 | /* Remove hover scale effect that causes node drift */
146 | /* .canvas-node:not(.collapsed):hover {
147 | transform: scale(1.01);
148 | } */
149 |
150 | /* Animate the collapse control background */
151 | .canvas-node-collapse-control {
152 | transition: background-color 0.2s ease-in-out;
153 | }
154 |
155 | /* Modify drawer animation to prevent drift */
156 | .canvas-node-group .group-nodes,
157 | .canvas-node-group .group-edges {
158 | transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out;
159 | /* Remove transform transitions that cause drift */
160 | }
161 |
162 | .canvas-node-group.collapsed .group-nodes-collapsed,
163 | .canvas-node-group.collapsed .group-edges-collapsed {
164 | opacity: 0;
165 | transition: opacity 0.2s ease-in-out;
166 | /* Remove transform that causes drift */
167 | }
168 |
169 | /* Animation classes for transitions */
170 | .node-transitioning {
171 | transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out !important;
172 | /* Remove transform transition that causes drift */
173 | }
174 |
175 | .animating {
176 | overflow: hidden !important;
177 | }
178 |
179 | /* Simplified drawer animation to prevent drift */
180 | .canvas-node-group .group-nodes {
181 | transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
182 | will-change: opacity, max-height;
183 | /* Remove transform properties that cause drift */
184 | }
185 |
186 | .canvas-node-group.collapsed .group-nodes {
187 | max-height: 0;
188 | opacity: 0;
189 | transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
190 | /* Remove transform that causes drift */
191 | }
192 |
193 | /* Animation for edges */
194 | .canvas-edge {
195 | transition: opacity 0.2s ease-in-out;
196 | }
197 |
198 | .group-edges-collapsed {
199 | transition: opacity 0.2s ease-in-out;
200 | opacity: 0;
201 | }
202 |
203 | /* Remove bounce animation that causes drift */
204 | /* @keyframes bounceIn {
205 | 0% {
206 | transform: scale(0.95);
207 | opacity: 0;
208 | }
209 | 70% {
210 | transform: scale(1.02);
211 | opacity: 1;
212 | }
213 | 100% {
214 | transform: scale(1);
215 | opacity: 1;
216 | }
217 | }
218 |
219 | .canvas-node:not(.collapsed):not(.animating) {
220 | animation: bounceIn 0.4s ease-out;
221 | } */
222 |
223 | .collapse-handler.collapsed svg {
224 | transform: rotate(-90deg);
225 | transition: transform 0.2s ease-in-out;
226 | }
227 |
228 | .collapse-handler svg {
229 | transform: rotate(0deg);
230 | transition: transform 0.2s ease-in-out;
231 | }
232 |
233 | .canvas-node .canvas-node-collapse-control-thumbnail {
234 | display: none;
235 | }
236 |
237 | /* Styles for thumbnails and aliases in collapsed state */
238 | .canvas-node.collapsed .canvas-node-collapse-control-thumbnail {
239 | max-width: 100%;
240 | max-height: 100px;
241 | object-fit: contain;
242 | margin: 5px auto;
243 | display: block;
244 | border-radius: 4px;
245 | position: absolute;
246 | bottom: calc(27px * var(--zoom-multiplier));
247 | }
248 |
249 | .canvas-node.collapsed .canvas-node-collapse-control-alias {
250 | display: block;
251 | font-weight: bold;
252 | text-align: center;
253 | overflow: hidden;
254 | text-overflow: ellipsis;
255 | white-space: nowrap;
256 |
257 | color: var(--text-normal);
258 | }
259 |
260 | /* Adjust the header to accommodate thumbnails and aliases */
261 | .canvas-node.collapsed .canvas-node-collapse-control {
262 | display: flex;
263 | flex-direction: row;
264 | width: 100%;
265 | }
266 |
267 | /* Make sure the header elements are properly spaced in collapsed mode */
268 | .canvas-node .canvas-node-collapse-control-icon,
269 | .canvas-node .canvas-node-type-icon {
270 | margin-bottom: 4px;
271 | }
272 |
273 | body.hide-default-node-title
274 | .canvas-node:has(.canvas-node-collapse-control)
275 | .canvas-node-label {
276 | display: none;
277 | }
278 |
279 | .collapsed-node-hidden {
280 | display: none !important;
281 | }
282 |
283 |
284 | span.canvas-node-collapse-control-title {
285 | overflow: hidden;
286 | text-overflow: ellipsis;
287 | white-space: nowrap;
288 | max-width: 100%;
289 | }
290 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "inlineSourceMap": true,
5 | "inlineSources": true,
6 | "module": "ESNext",
7 | "target": "ES6",
8 | "allowJs": true,
9 | "noImplicitAny": true,
10 | "moduleResolution": "node",
11 | "importHelpers": true,
12 | "isolatedModules": true,
13 | "strictNullChecks": true,
14 | "lib": [
15 | "DOM",
16 | "ES5",
17 | "ES6",
18 | "ES7"
19 | ]
20 | },
21 | "include": [
22 | "**/*.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/version-bump.mjs:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from "fs";
2 |
3 | const targetVersion = process.env.npm_package_version;
4 |
5 | // read minAppVersion from manifest.json and bump version to target version
6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
7 | const { minAppVersion } = manifest;
8 | manifest.version = targetVersion;
9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
10 |
11 | // update versions.json with target version and minAppVersion from manifest.json
12 | let versions = JSON.parse(readFileSync("versions.json", "utf8"));
13 | versions[targetVersion] = minAppVersion;
14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
15 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "1.0.0": "1.1.0",
3 | "1.0.1": "1.1.0",
4 | "1.1.0": "1.1.0",
5 | "1.1.1": "1.1.0",
6 | "2.0.0": "1.1.0"
7 | }
--------------------------------------------------------------------------------