├── .cursorignore ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── esbuild.config.mjs ├── manifest.json ├── package.json ├── pnpm-lock.yaml ├── showcase.gif ├── src ├── canvasMindMap.ts ├── mindMapSettings.ts ├── types │ └── obsidian.d.ts └── utils.ts ├── styles.css ├── tsconfig.json ├── version-bump.mjs └── versions.json /.cursorignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.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-canvas-mindmap 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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | ### Shiny new things 4 | 5 | - Auto layout 6 | - Support `Right to left` and `Left to right` mindmap directions 7 | - Support `Top to bottom` and `Bottom to top` mindmap directions 8 | - Support node auto resize when text is changed 9 | - Support move node out of mindmap; 10 | - Support trigger auto layout when node is changed; 11 | - Support trigger auto layout when node is deleted; 12 | - Settings for all features 13 | - Change hotkey to edit node/create next sibling node/create previous sibling node; 14 | - Set width, height, gap between nodes; 15 | - Set default mindmap direction; 16 | - Set ignore files regex; 17 | - Better navigation between nodes 18 | - Context menu for node 19 | - Create sibling node; 20 | - Create child node; 21 | - Select node's tree; 22 | - Commands (All commands will ignore if current file is being edited in canvas) 23 | - Create child node; 24 | - Create sibling node; 25 | - Create floating node; 26 | - Open changelog; 27 | - Enter/exit edit mode; 28 | - Hotkeys (All hotkeys can change current mindmap direction) 29 | - `Mod + Shift + ArrowLeft` to create a new child node on the left side of the current node; 30 | - `Mod + Shift + ArrowRight` to create a new child node on the right side of the current node; 31 | - `Mod + Shift + ArrowUp` to create a new child node on the top side of the current node; 32 | - `Mod + Shift + ArrowDown` to create a new child node on the bottom side of the current node; 33 | 34 | 35 | ### Bug fixes 36 | 37 | - Cannot enter edit mode when create a new node; 38 | - Cannot delete node when node is the only child of its parent/only node in the mindmap; 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Canvas MindMap 2 | 3 | ## Usage 4 | 5 | 1. Install the plugin 6 | 2. Create a new canvas 7 | 3. Create a node in the empty canvas 8 | 4. Select the node and the press `Tab` to create a child node (You need to exit edit mode first by pressing `Escape`) 9 | 5. Select the child node and press `Enter` to create a brother node 10 | 6. Done, you get a mindmap. 11 | 12 | ## Features 13 | 14 | - Auto layout for specific direction 15 | - Select node's tree 16 | - Change color for node's edges 17 | 18 | ### Hotkeys 19 | 20 | - Press `Enter` to create brother card; (You can change this hotkey in setting) 21 | - Press `Tab` to create child card; 22 | - Press `Alt` + `←/→/↓/↑` to navigate between nodes; 23 | - Press `Cmd / Ctrl` + `←/→/↓/↑` to create floating nodes; 24 | - Press `Cmd / Ctrl` + `Shift` + `←/→/↓/↑` to create child nodes; 25 | 26 | ### Commands 27 | 28 | - Create child node 29 | - Create sibling node 30 | - Create floating node 31 | - Open changelog 32 | - Enter/exit edit mode 33 | 34 | ### Showcase 35 | 36 | ![showcase](https://raw.githubusercontent.com/Quorafind/obsidian-canvas-mindmap/master/showcase.gif) 37 | 38 | ## How to Install 39 | 40 | - Obsidian: [Available on Obsidian Plugin Market](https://obsidian.md/plugins?id=canvas-mindmap) 41 | - BRAT: Add `quorafind/canvas-mindmap` to BRAT. 42 | - Manual: Download the latest release. Extract and put the three files (main.js, manifest.json, styles.css) to `{{obsidian_vault}}/.obsidian/plugins/obsidian-canvas-mindmap`. -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['src/canvasMindMap.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins], 35 | format: 'cjs', 36 | watch: !prod, 37 | target: 'es2018', 38 | logLevel: "info", 39 | sourcemap: prod ? false : 'inline', 40 | treeShaking: true, 41 | outfile: 'main.js', 42 | }).catch(() => process.exit(1)); 43 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "canvas-mindmap", 3 | "name": "Canvas Mindmap", 4 | "version": "1.0.2", 5 | "minAppVersion": "1.4.0", 6 | "description": "A plugin to make your canvas work like a mindmap.", 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": "canvas-mindmap", 3 | "version": "1.0.2", 4 | "description": "A plugin to make your canvas work like a mindmap.", 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": "Boninall", 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 | "monkey-around": "^2.3.0", 21 | "obsidian": "latest", 22 | "tslib": "2.4.0", 23 | "typescript": "4.7.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@types/node': 9 | specifier: ^16.11.6 10 | version: 16.18.10 11 | '@typescript-eslint/eslint-plugin': 12 | specifier: 5.29.0 13 | version: 5.29.0(@typescript-eslint/parser@5.29.0)(eslint@8.54.0)(typescript@4.7.4) 14 | '@typescript-eslint/parser': 15 | specifier: 5.29.0 16 | version: 5.29.0(eslint@8.54.0)(typescript@4.7.4) 17 | builtin-modules: 18 | specifier: 3.3.0 19 | version: 3.3.0 20 | esbuild: 21 | specifier: 0.14.47 22 | version: 0.14.47 23 | monkey-around: 24 | specifier: ^2.3.0 25 | version: 2.3.0 26 | obsidian: 27 | specifier: latest 28 | version: 1.4.11(@codemirror/state@6.3.1)(@codemirror/view@6.22.0) 29 | tslib: 30 | specifier: 2.4.0 31 | version: 2.4.0 32 | typescript: 33 | specifier: 4.7.4 34 | version: 4.7.4 35 | 36 | packages: 37 | 38 | /@aashutoshrathi/word-wrap@1.2.6: 39 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 40 | engines: {node: '>=0.10.0'} 41 | dev: true 42 | 43 | /@codemirror/state@6.3.1: 44 | resolution: {integrity: sha512-88e4HhMtKJyw6fKprGaN/yZfiaoGYOi2nM45YCUC6R/kex9sxFWBDGatS1vk4lMgnWmdIIB9tk8Gj1LmL8YfvA==} 45 | dev: true 46 | 47 | /@codemirror/view@6.22.0: 48 | resolution: {integrity: sha512-6zLj4YIoIpfTGKrDMTbeZRpa8ih4EymMCKmddEDcJWrCdp/N1D46B38YEz4creTb4T177AVS9EyXkLeC/HL2jA==} 49 | dependencies: 50 | '@codemirror/state': 6.3.1 51 | style-mod: 4.1.0 52 | w3c-keyname: 2.2.8 53 | dev: true 54 | 55 | /@eslint-community/eslint-utils@4.4.0(eslint@8.54.0): 56 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 57 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 58 | peerDependencies: 59 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 60 | dependencies: 61 | eslint: 8.54.0 62 | eslint-visitor-keys: 3.4.3 63 | dev: true 64 | 65 | /@eslint-community/regexpp@4.10.0: 66 | resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 67 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 68 | dev: true 69 | 70 | /@eslint/eslintrc@2.1.3: 71 | resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} 72 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 73 | dependencies: 74 | ajv: 6.12.6 75 | debug: 4.3.4 76 | espree: 9.6.1 77 | globals: 13.23.0 78 | ignore: 5.2.4 79 | import-fresh: 3.3.0 80 | js-yaml: 4.1.0 81 | minimatch: 3.1.2 82 | strip-json-comments: 3.1.1 83 | transitivePeerDependencies: 84 | - supports-color 85 | dev: true 86 | 87 | /@eslint/js@8.54.0: 88 | resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==} 89 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 90 | dev: true 91 | 92 | /@humanwhocodes/config-array@0.11.13: 93 | resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 94 | engines: {node: '>=10.10.0'} 95 | dependencies: 96 | '@humanwhocodes/object-schema': 2.0.1 97 | debug: 4.3.4 98 | minimatch: 3.1.2 99 | transitivePeerDependencies: 100 | - supports-color 101 | dev: true 102 | 103 | /@humanwhocodes/module-importer@1.0.1: 104 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 105 | engines: {node: '>=12.22'} 106 | dev: true 107 | 108 | /@humanwhocodes/object-schema@2.0.1: 109 | resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 110 | dev: true 111 | 112 | /@nodelib/fs.scandir@2.1.5: 113 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 114 | engines: {node: '>= 8'} 115 | dependencies: 116 | '@nodelib/fs.stat': 2.0.5 117 | run-parallel: 1.2.0 118 | dev: true 119 | 120 | /@nodelib/fs.stat@2.0.5: 121 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 122 | engines: {node: '>= 8'} 123 | dev: true 124 | 125 | /@nodelib/fs.walk@1.2.8: 126 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 127 | engines: {node: '>= 8'} 128 | dependencies: 129 | '@nodelib/fs.scandir': 2.1.5 130 | fastq: 1.14.0 131 | dev: true 132 | 133 | /@types/codemirror@5.60.8: 134 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==} 135 | dependencies: 136 | '@types/tern': 0.23.4 137 | dev: true 138 | 139 | /@types/estree@1.0.0: 140 | resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} 141 | dev: true 142 | 143 | /@types/json-schema@7.0.11: 144 | resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} 145 | dev: true 146 | 147 | /@types/node@16.18.10: 148 | resolution: {integrity: sha512-XU1+v7h81p7145ddPfjv7jtWvkSilpcnON3mQ+bDi9Yuf7OI56efOglXRyXWgQ57xH3fEQgh7WOJMncRHVew5w==} 149 | dev: true 150 | 151 | /@types/tern@0.23.4: 152 | resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==} 153 | dependencies: 154 | '@types/estree': 1.0.0 155 | dev: true 156 | 157 | /@typescript-eslint/eslint-plugin@5.29.0(@typescript-eslint/parser@5.29.0)(eslint@8.54.0)(typescript@4.7.4): 158 | resolution: {integrity: sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==} 159 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 160 | peerDependencies: 161 | '@typescript-eslint/parser': ^5.0.0 162 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 163 | typescript: '*' 164 | peerDependenciesMeta: 165 | typescript: 166 | optional: true 167 | dependencies: 168 | '@typescript-eslint/parser': 5.29.0(eslint@8.54.0)(typescript@4.7.4) 169 | '@typescript-eslint/scope-manager': 5.29.0 170 | '@typescript-eslint/type-utils': 5.29.0(eslint@8.54.0)(typescript@4.7.4) 171 | '@typescript-eslint/utils': 5.29.0(eslint@8.54.0)(typescript@4.7.4) 172 | debug: 4.3.4 173 | eslint: 8.54.0 174 | functional-red-black-tree: 1.0.1 175 | ignore: 5.2.4 176 | regexpp: 3.2.0 177 | semver: 7.3.8 178 | tsutils: 3.21.0(typescript@4.7.4) 179 | typescript: 4.7.4 180 | transitivePeerDependencies: 181 | - supports-color 182 | dev: true 183 | 184 | /@typescript-eslint/parser@5.29.0(eslint@8.54.0)(typescript@4.7.4): 185 | resolution: {integrity: sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==} 186 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 187 | peerDependencies: 188 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 189 | typescript: '*' 190 | peerDependenciesMeta: 191 | typescript: 192 | optional: true 193 | dependencies: 194 | '@typescript-eslint/scope-manager': 5.29.0 195 | '@typescript-eslint/types': 5.29.0 196 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4) 197 | debug: 4.3.4 198 | eslint: 8.54.0 199 | typescript: 4.7.4 200 | transitivePeerDependencies: 201 | - supports-color 202 | dev: true 203 | 204 | /@typescript-eslint/scope-manager@5.29.0: 205 | resolution: {integrity: sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==} 206 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 207 | dependencies: 208 | '@typescript-eslint/types': 5.29.0 209 | '@typescript-eslint/visitor-keys': 5.29.0 210 | dev: true 211 | 212 | /@typescript-eslint/type-utils@5.29.0(eslint@8.54.0)(typescript@4.7.4): 213 | resolution: {integrity: sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==} 214 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 215 | peerDependencies: 216 | eslint: '*' 217 | typescript: '*' 218 | peerDependenciesMeta: 219 | typescript: 220 | optional: true 221 | dependencies: 222 | '@typescript-eslint/utils': 5.29.0(eslint@8.54.0)(typescript@4.7.4) 223 | debug: 4.3.4 224 | eslint: 8.54.0 225 | tsutils: 3.21.0(typescript@4.7.4) 226 | typescript: 4.7.4 227 | transitivePeerDependencies: 228 | - supports-color 229 | dev: true 230 | 231 | /@typescript-eslint/types@5.29.0: 232 | resolution: {integrity: sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==} 233 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 234 | dev: true 235 | 236 | /@typescript-eslint/typescript-estree@5.29.0(typescript@4.7.4): 237 | resolution: {integrity: sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==} 238 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 239 | peerDependencies: 240 | typescript: '*' 241 | peerDependenciesMeta: 242 | typescript: 243 | optional: true 244 | dependencies: 245 | '@typescript-eslint/types': 5.29.0 246 | '@typescript-eslint/visitor-keys': 5.29.0 247 | debug: 4.3.4 248 | globby: 11.1.0 249 | is-glob: 4.0.3 250 | semver: 7.3.8 251 | tsutils: 3.21.0(typescript@4.7.4) 252 | typescript: 4.7.4 253 | transitivePeerDependencies: 254 | - supports-color 255 | dev: true 256 | 257 | /@typescript-eslint/utils@5.29.0(eslint@8.54.0)(typescript@4.7.4): 258 | resolution: {integrity: sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==} 259 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 260 | peerDependencies: 261 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 262 | dependencies: 263 | '@types/json-schema': 7.0.11 264 | '@typescript-eslint/scope-manager': 5.29.0 265 | '@typescript-eslint/types': 5.29.0 266 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4) 267 | eslint: 8.54.0 268 | eslint-scope: 5.1.1 269 | eslint-utils: 3.0.0(eslint@8.54.0) 270 | transitivePeerDependencies: 271 | - supports-color 272 | - typescript 273 | dev: true 274 | 275 | /@typescript-eslint/visitor-keys@5.29.0: 276 | resolution: {integrity: sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==} 277 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 278 | dependencies: 279 | '@typescript-eslint/types': 5.29.0 280 | eslint-visitor-keys: 3.3.0 281 | dev: true 282 | 283 | /@ungap/structured-clone@1.2.0: 284 | resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 285 | dev: true 286 | 287 | /acorn-jsx@5.3.2(acorn@8.11.2): 288 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 289 | peerDependencies: 290 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 291 | dependencies: 292 | acorn: 8.11.2 293 | dev: true 294 | 295 | /acorn@8.11.2: 296 | resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 297 | engines: {node: '>=0.4.0'} 298 | hasBin: true 299 | dev: true 300 | 301 | /ajv@6.12.6: 302 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 303 | dependencies: 304 | fast-deep-equal: 3.1.3 305 | fast-json-stable-stringify: 2.1.0 306 | json-schema-traverse: 0.4.1 307 | uri-js: 4.4.1 308 | dev: true 309 | 310 | /ansi-regex@5.0.1: 311 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 312 | engines: {node: '>=8'} 313 | dev: true 314 | 315 | /ansi-styles@4.3.0: 316 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 317 | engines: {node: '>=8'} 318 | dependencies: 319 | color-convert: 2.0.1 320 | dev: true 321 | 322 | /argparse@2.0.1: 323 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 324 | dev: true 325 | 326 | /array-union@2.1.0: 327 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 328 | engines: {node: '>=8'} 329 | dev: true 330 | 331 | /balanced-match@1.0.2: 332 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 333 | dev: true 334 | 335 | /brace-expansion@1.1.11: 336 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 337 | dependencies: 338 | balanced-match: 1.0.2 339 | concat-map: 0.0.1 340 | dev: true 341 | 342 | /braces@3.0.2: 343 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 344 | engines: {node: '>=8'} 345 | dependencies: 346 | fill-range: 7.0.1 347 | dev: true 348 | 349 | /builtin-modules@3.3.0: 350 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 351 | engines: {node: '>=6'} 352 | dev: true 353 | 354 | /callsites@3.1.0: 355 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 356 | engines: {node: '>=6'} 357 | dev: true 358 | 359 | /chalk@4.1.2: 360 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 361 | engines: {node: '>=10'} 362 | dependencies: 363 | ansi-styles: 4.3.0 364 | supports-color: 7.2.0 365 | dev: true 366 | 367 | /color-convert@2.0.1: 368 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 369 | engines: {node: '>=7.0.0'} 370 | dependencies: 371 | color-name: 1.1.4 372 | dev: true 373 | 374 | /color-name@1.1.4: 375 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 376 | dev: true 377 | 378 | /concat-map@0.0.1: 379 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 380 | dev: true 381 | 382 | /cross-spawn@7.0.3: 383 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 384 | engines: {node: '>= 8'} 385 | dependencies: 386 | path-key: 3.1.1 387 | shebang-command: 2.0.0 388 | which: 2.0.2 389 | dev: true 390 | 391 | /debug@4.3.4: 392 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 393 | engines: {node: '>=6.0'} 394 | peerDependencies: 395 | supports-color: '*' 396 | peerDependenciesMeta: 397 | supports-color: 398 | optional: true 399 | dependencies: 400 | ms: 2.1.2 401 | dev: true 402 | 403 | /deep-is@0.1.4: 404 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 405 | dev: true 406 | 407 | /dir-glob@3.0.1: 408 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 409 | engines: {node: '>=8'} 410 | dependencies: 411 | path-type: 4.0.0 412 | dev: true 413 | 414 | /doctrine@3.0.0: 415 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 416 | engines: {node: '>=6.0.0'} 417 | dependencies: 418 | esutils: 2.0.3 419 | dev: true 420 | 421 | /esbuild-android-64@0.14.47: 422 | resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} 423 | engines: {node: '>=12'} 424 | cpu: [x64] 425 | os: [android] 426 | requiresBuild: true 427 | dev: true 428 | optional: true 429 | 430 | /esbuild-android-arm64@0.14.47: 431 | resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} 432 | engines: {node: '>=12'} 433 | cpu: [arm64] 434 | os: [android] 435 | requiresBuild: true 436 | dev: true 437 | optional: true 438 | 439 | /esbuild-darwin-64@0.14.47: 440 | resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} 441 | engines: {node: '>=12'} 442 | cpu: [x64] 443 | os: [darwin] 444 | requiresBuild: true 445 | dev: true 446 | optional: true 447 | 448 | /esbuild-darwin-arm64@0.14.47: 449 | resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} 450 | engines: {node: '>=12'} 451 | cpu: [arm64] 452 | os: [darwin] 453 | requiresBuild: true 454 | dev: true 455 | optional: true 456 | 457 | /esbuild-freebsd-64@0.14.47: 458 | resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} 459 | engines: {node: '>=12'} 460 | cpu: [x64] 461 | os: [freebsd] 462 | requiresBuild: true 463 | dev: true 464 | optional: true 465 | 466 | /esbuild-freebsd-arm64@0.14.47: 467 | resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} 468 | engines: {node: '>=12'} 469 | cpu: [arm64] 470 | os: [freebsd] 471 | requiresBuild: true 472 | dev: true 473 | optional: true 474 | 475 | /esbuild-linux-32@0.14.47: 476 | resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} 477 | engines: {node: '>=12'} 478 | cpu: [ia32] 479 | os: [linux] 480 | requiresBuild: true 481 | dev: true 482 | optional: true 483 | 484 | /esbuild-linux-64@0.14.47: 485 | resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} 486 | engines: {node: '>=12'} 487 | cpu: [x64] 488 | os: [linux] 489 | requiresBuild: true 490 | dev: true 491 | optional: true 492 | 493 | /esbuild-linux-arm64@0.14.47: 494 | resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} 495 | engines: {node: '>=12'} 496 | cpu: [arm64] 497 | os: [linux] 498 | requiresBuild: true 499 | dev: true 500 | optional: true 501 | 502 | /esbuild-linux-arm@0.14.47: 503 | resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} 504 | engines: {node: '>=12'} 505 | cpu: [arm] 506 | os: [linux] 507 | requiresBuild: true 508 | dev: true 509 | optional: true 510 | 511 | /esbuild-linux-mips64le@0.14.47: 512 | resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} 513 | engines: {node: '>=12'} 514 | cpu: [mips64el] 515 | os: [linux] 516 | requiresBuild: true 517 | dev: true 518 | optional: true 519 | 520 | /esbuild-linux-ppc64le@0.14.47: 521 | resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} 522 | engines: {node: '>=12'} 523 | cpu: [ppc64] 524 | os: [linux] 525 | requiresBuild: true 526 | dev: true 527 | optional: true 528 | 529 | /esbuild-linux-riscv64@0.14.47: 530 | resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} 531 | engines: {node: '>=12'} 532 | cpu: [riscv64] 533 | os: [linux] 534 | requiresBuild: true 535 | dev: true 536 | optional: true 537 | 538 | /esbuild-linux-s390x@0.14.47: 539 | resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} 540 | engines: {node: '>=12'} 541 | cpu: [s390x] 542 | os: [linux] 543 | requiresBuild: true 544 | dev: true 545 | optional: true 546 | 547 | /esbuild-netbsd-64@0.14.47: 548 | resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} 549 | engines: {node: '>=12'} 550 | cpu: [x64] 551 | os: [netbsd] 552 | requiresBuild: true 553 | dev: true 554 | optional: true 555 | 556 | /esbuild-openbsd-64@0.14.47: 557 | resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} 558 | engines: {node: '>=12'} 559 | cpu: [x64] 560 | os: [openbsd] 561 | requiresBuild: true 562 | dev: true 563 | optional: true 564 | 565 | /esbuild-sunos-64@0.14.47: 566 | resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} 567 | engines: {node: '>=12'} 568 | cpu: [x64] 569 | os: [sunos] 570 | requiresBuild: true 571 | dev: true 572 | optional: true 573 | 574 | /esbuild-windows-32@0.14.47: 575 | resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} 576 | engines: {node: '>=12'} 577 | cpu: [ia32] 578 | os: [win32] 579 | requiresBuild: true 580 | dev: true 581 | optional: true 582 | 583 | /esbuild-windows-64@0.14.47: 584 | resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} 585 | engines: {node: '>=12'} 586 | cpu: [x64] 587 | os: [win32] 588 | requiresBuild: true 589 | dev: true 590 | optional: true 591 | 592 | /esbuild-windows-arm64@0.14.47: 593 | resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} 594 | engines: {node: '>=12'} 595 | cpu: [arm64] 596 | os: [win32] 597 | requiresBuild: true 598 | dev: true 599 | optional: true 600 | 601 | /esbuild@0.14.47: 602 | resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} 603 | engines: {node: '>=12'} 604 | hasBin: true 605 | requiresBuild: true 606 | optionalDependencies: 607 | esbuild-android-64: 0.14.47 608 | esbuild-android-arm64: 0.14.47 609 | esbuild-darwin-64: 0.14.47 610 | esbuild-darwin-arm64: 0.14.47 611 | esbuild-freebsd-64: 0.14.47 612 | esbuild-freebsd-arm64: 0.14.47 613 | esbuild-linux-32: 0.14.47 614 | esbuild-linux-64: 0.14.47 615 | esbuild-linux-arm: 0.14.47 616 | esbuild-linux-arm64: 0.14.47 617 | esbuild-linux-mips64le: 0.14.47 618 | esbuild-linux-ppc64le: 0.14.47 619 | esbuild-linux-riscv64: 0.14.47 620 | esbuild-linux-s390x: 0.14.47 621 | esbuild-netbsd-64: 0.14.47 622 | esbuild-openbsd-64: 0.14.47 623 | esbuild-sunos-64: 0.14.47 624 | esbuild-windows-32: 0.14.47 625 | esbuild-windows-64: 0.14.47 626 | esbuild-windows-arm64: 0.14.47 627 | dev: true 628 | 629 | /escape-string-regexp@4.0.0: 630 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 631 | engines: {node: '>=10'} 632 | dev: true 633 | 634 | /eslint-scope@5.1.1: 635 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 636 | engines: {node: '>=8.0.0'} 637 | dependencies: 638 | esrecurse: 4.3.0 639 | estraverse: 4.3.0 640 | dev: true 641 | 642 | /eslint-scope@7.2.2: 643 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 644 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 645 | dependencies: 646 | esrecurse: 4.3.0 647 | estraverse: 5.3.0 648 | dev: true 649 | 650 | /eslint-utils@3.0.0(eslint@8.54.0): 651 | resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} 652 | engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} 653 | peerDependencies: 654 | eslint: '>=5' 655 | dependencies: 656 | eslint: 8.54.0 657 | eslint-visitor-keys: 2.1.0 658 | dev: true 659 | 660 | /eslint-visitor-keys@2.1.0: 661 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} 662 | engines: {node: '>=10'} 663 | dev: true 664 | 665 | /eslint-visitor-keys@3.3.0: 666 | resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} 667 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 668 | dev: true 669 | 670 | /eslint-visitor-keys@3.4.3: 671 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 672 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 673 | dev: true 674 | 675 | /eslint@8.54.0: 676 | resolution: {integrity: sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==} 677 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 678 | hasBin: true 679 | dependencies: 680 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) 681 | '@eslint-community/regexpp': 4.10.0 682 | '@eslint/eslintrc': 2.1.3 683 | '@eslint/js': 8.54.0 684 | '@humanwhocodes/config-array': 0.11.13 685 | '@humanwhocodes/module-importer': 1.0.1 686 | '@nodelib/fs.walk': 1.2.8 687 | '@ungap/structured-clone': 1.2.0 688 | ajv: 6.12.6 689 | chalk: 4.1.2 690 | cross-spawn: 7.0.3 691 | debug: 4.3.4 692 | doctrine: 3.0.0 693 | escape-string-regexp: 4.0.0 694 | eslint-scope: 7.2.2 695 | eslint-visitor-keys: 3.4.3 696 | espree: 9.6.1 697 | esquery: 1.5.0 698 | esutils: 2.0.3 699 | fast-deep-equal: 3.1.3 700 | file-entry-cache: 6.0.1 701 | find-up: 5.0.0 702 | glob-parent: 6.0.2 703 | globals: 13.23.0 704 | graphemer: 1.4.0 705 | ignore: 5.2.4 706 | imurmurhash: 0.1.4 707 | is-glob: 4.0.3 708 | is-path-inside: 3.0.3 709 | js-yaml: 4.1.0 710 | json-stable-stringify-without-jsonify: 1.0.1 711 | levn: 0.4.1 712 | lodash.merge: 4.6.2 713 | minimatch: 3.1.2 714 | natural-compare: 1.4.0 715 | optionator: 0.9.3 716 | strip-ansi: 6.0.1 717 | text-table: 0.2.0 718 | transitivePeerDependencies: 719 | - supports-color 720 | dev: true 721 | 722 | /espree@9.6.1: 723 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 724 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 725 | dependencies: 726 | acorn: 8.11.2 727 | acorn-jsx: 5.3.2(acorn@8.11.2) 728 | eslint-visitor-keys: 3.4.3 729 | dev: true 730 | 731 | /esquery@1.5.0: 732 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 733 | engines: {node: '>=0.10'} 734 | dependencies: 735 | estraverse: 5.3.0 736 | dev: true 737 | 738 | /esrecurse@4.3.0: 739 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 740 | engines: {node: '>=4.0'} 741 | dependencies: 742 | estraverse: 5.3.0 743 | dev: true 744 | 745 | /estraverse@4.3.0: 746 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 747 | engines: {node: '>=4.0'} 748 | dev: true 749 | 750 | /estraverse@5.3.0: 751 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 752 | engines: {node: '>=4.0'} 753 | dev: true 754 | 755 | /esutils@2.0.3: 756 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 757 | engines: {node: '>=0.10.0'} 758 | dev: true 759 | 760 | /fast-deep-equal@3.1.3: 761 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 762 | dev: true 763 | 764 | /fast-glob@3.2.12: 765 | resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} 766 | engines: {node: '>=8.6.0'} 767 | dependencies: 768 | '@nodelib/fs.stat': 2.0.5 769 | '@nodelib/fs.walk': 1.2.8 770 | glob-parent: 5.1.2 771 | merge2: 1.4.1 772 | micromatch: 4.0.5 773 | dev: true 774 | 775 | /fast-json-stable-stringify@2.1.0: 776 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 777 | dev: true 778 | 779 | /fast-levenshtein@2.0.6: 780 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 781 | dev: true 782 | 783 | /fastq@1.14.0: 784 | resolution: {integrity: sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==} 785 | dependencies: 786 | reusify: 1.0.4 787 | dev: true 788 | 789 | /file-entry-cache@6.0.1: 790 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 791 | engines: {node: ^10.12.0 || >=12.0.0} 792 | dependencies: 793 | flat-cache: 3.2.0 794 | dev: true 795 | 796 | /fill-range@7.0.1: 797 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 798 | engines: {node: '>=8'} 799 | dependencies: 800 | to-regex-range: 5.0.1 801 | dev: true 802 | 803 | /find-up@5.0.0: 804 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 805 | engines: {node: '>=10'} 806 | dependencies: 807 | locate-path: 6.0.0 808 | path-exists: 4.0.0 809 | dev: true 810 | 811 | /flat-cache@3.2.0: 812 | resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 813 | engines: {node: ^10.12.0 || >=12.0.0} 814 | dependencies: 815 | flatted: 3.2.9 816 | keyv: 4.5.4 817 | rimraf: 3.0.2 818 | dev: true 819 | 820 | /flatted@3.2.9: 821 | resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 822 | dev: true 823 | 824 | /fs.realpath@1.0.0: 825 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 826 | dev: true 827 | 828 | /functional-red-black-tree@1.0.1: 829 | resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} 830 | dev: true 831 | 832 | /glob-parent@5.1.2: 833 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 834 | engines: {node: '>= 6'} 835 | dependencies: 836 | is-glob: 4.0.3 837 | dev: true 838 | 839 | /glob-parent@6.0.2: 840 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 841 | engines: {node: '>=10.13.0'} 842 | dependencies: 843 | is-glob: 4.0.3 844 | dev: true 845 | 846 | /glob@7.2.3: 847 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 848 | dependencies: 849 | fs.realpath: 1.0.0 850 | inflight: 1.0.6 851 | inherits: 2.0.4 852 | minimatch: 3.1.2 853 | once: 1.4.0 854 | path-is-absolute: 1.0.1 855 | dev: true 856 | 857 | /globals@13.23.0: 858 | resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 859 | engines: {node: '>=8'} 860 | dependencies: 861 | type-fest: 0.20.2 862 | dev: true 863 | 864 | /globby@11.1.0: 865 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 866 | engines: {node: '>=10'} 867 | dependencies: 868 | array-union: 2.1.0 869 | dir-glob: 3.0.1 870 | fast-glob: 3.2.12 871 | ignore: 5.2.4 872 | merge2: 1.4.1 873 | slash: 3.0.0 874 | dev: true 875 | 876 | /graphemer@1.4.0: 877 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 878 | dev: true 879 | 880 | /has-flag@4.0.0: 881 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 882 | engines: {node: '>=8'} 883 | dev: true 884 | 885 | /ignore@5.2.4: 886 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 887 | engines: {node: '>= 4'} 888 | dev: true 889 | 890 | /import-fresh@3.3.0: 891 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 892 | engines: {node: '>=6'} 893 | dependencies: 894 | parent-module: 1.0.1 895 | resolve-from: 4.0.0 896 | dev: true 897 | 898 | /imurmurhash@0.1.4: 899 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 900 | engines: {node: '>=0.8.19'} 901 | dev: true 902 | 903 | /inflight@1.0.6: 904 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 905 | dependencies: 906 | once: 1.4.0 907 | wrappy: 1.0.2 908 | dev: true 909 | 910 | /inherits@2.0.4: 911 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 912 | dev: true 913 | 914 | /is-extglob@2.1.1: 915 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 916 | engines: {node: '>=0.10.0'} 917 | dev: true 918 | 919 | /is-glob@4.0.3: 920 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 921 | engines: {node: '>=0.10.0'} 922 | dependencies: 923 | is-extglob: 2.1.1 924 | dev: true 925 | 926 | /is-number@7.0.0: 927 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 928 | engines: {node: '>=0.12.0'} 929 | dev: true 930 | 931 | /is-path-inside@3.0.3: 932 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 933 | engines: {node: '>=8'} 934 | dev: true 935 | 936 | /isexe@2.0.0: 937 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 938 | dev: true 939 | 940 | /js-yaml@4.1.0: 941 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 942 | hasBin: true 943 | dependencies: 944 | argparse: 2.0.1 945 | dev: true 946 | 947 | /json-buffer@3.0.1: 948 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 949 | dev: true 950 | 951 | /json-schema-traverse@0.4.1: 952 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 953 | dev: true 954 | 955 | /json-stable-stringify-without-jsonify@1.0.1: 956 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 957 | dev: true 958 | 959 | /keyv@4.5.4: 960 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 961 | dependencies: 962 | json-buffer: 3.0.1 963 | dev: true 964 | 965 | /levn@0.4.1: 966 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 967 | engines: {node: '>= 0.8.0'} 968 | dependencies: 969 | prelude-ls: 1.2.1 970 | type-check: 0.4.0 971 | dev: true 972 | 973 | /locate-path@6.0.0: 974 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 975 | engines: {node: '>=10'} 976 | dependencies: 977 | p-locate: 5.0.0 978 | dev: true 979 | 980 | /lodash.merge@4.6.2: 981 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 982 | dev: true 983 | 984 | /lru-cache@6.0.0: 985 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 986 | engines: {node: '>=10'} 987 | dependencies: 988 | yallist: 4.0.0 989 | dev: true 990 | 991 | /merge2@1.4.1: 992 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 993 | engines: {node: '>= 8'} 994 | dev: true 995 | 996 | /micromatch@4.0.5: 997 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 998 | engines: {node: '>=8.6'} 999 | dependencies: 1000 | braces: 3.0.2 1001 | picomatch: 2.3.1 1002 | dev: true 1003 | 1004 | /minimatch@3.1.2: 1005 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1006 | dependencies: 1007 | brace-expansion: 1.1.11 1008 | dev: true 1009 | 1010 | /moment@2.29.4: 1011 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 1012 | dev: true 1013 | 1014 | /monkey-around@2.3.0: 1015 | resolution: {integrity: sha512-QWcCUWjqE/MCk9cXlSKZ1Qc486LD439xw/Ak8Nt6l2PuL9+yrc9TJakt7OHDuOqPRYY4nTWBAEFKn32PE/SfXA==} 1016 | dev: true 1017 | 1018 | /ms@2.1.2: 1019 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1020 | dev: true 1021 | 1022 | /natural-compare@1.4.0: 1023 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1024 | dev: true 1025 | 1026 | /obsidian@1.4.11(@codemirror/state@6.3.1)(@codemirror/view@6.22.0): 1027 | resolution: {integrity: sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==} 1028 | peerDependencies: 1029 | '@codemirror/state': ^6.0.0 1030 | '@codemirror/view': ^6.0.0 1031 | dependencies: 1032 | '@codemirror/state': 6.3.1 1033 | '@codemirror/view': 6.22.0 1034 | '@types/codemirror': 5.60.8 1035 | moment: 2.29.4 1036 | dev: true 1037 | 1038 | /once@1.4.0: 1039 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1040 | dependencies: 1041 | wrappy: 1.0.2 1042 | dev: true 1043 | 1044 | /optionator@0.9.3: 1045 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1046 | engines: {node: '>= 0.8.0'} 1047 | dependencies: 1048 | '@aashutoshrathi/word-wrap': 1.2.6 1049 | deep-is: 0.1.4 1050 | fast-levenshtein: 2.0.6 1051 | levn: 0.4.1 1052 | prelude-ls: 1.2.1 1053 | type-check: 0.4.0 1054 | dev: true 1055 | 1056 | /p-limit@3.1.0: 1057 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1058 | engines: {node: '>=10'} 1059 | dependencies: 1060 | yocto-queue: 0.1.0 1061 | dev: true 1062 | 1063 | /p-locate@5.0.0: 1064 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1065 | engines: {node: '>=10'} 1066 | dependencies: 1067 | p-limit: 3.1.0 1068 | dev: true 1069 | 1070 | /parent-module@1.0.1: 1071 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1072 | engines: {node: '>=6'} 1073 | dependencies: 1074 | callsites: 3.1.0 1075 | dev: true 1076 | 1077 | /path-exists@4.0.0: 1078 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1079 | engines: {node: '>=8'} 1080 | dev: true 1081 | 1082 | /path-is-absolute@1.0.1: 1083 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1084 | engines: {node: '>=0.10.0'} 1085 | dev: true 1086 | 1087 | /path-key@3.1.1: 1088 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1089 | engines: {node: '>=8'} 1090 | dev: true 1091 | 1092 | /path-type@4.0.0: 1093 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1094 | engines: {node: '>=8'} 1095 | dev: true 1096 | 1097 | /picomatch@2.3.1: 1098 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1099 | engines: {node: '>=8.6'} 1100 | dev: true 1101 | 1102 | /prelude-ls@1.2.1: 1103 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1104 | engines: {node: '>= 0.8.0'} 1105 | dev: true 1106 | 1107 | /punycode@2.3.1: 1108 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1109 | engines: {node: '>=6'} 1110 | dev: true 1111 | 1112 | /queue-microtask@1.2.3: 1113 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1114 | dev: true 1115 | 1116 | /regexpp@3.2.0: 1117 | resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} 1118 | engines: {node: '>=8'} 1119 | dev: true 1120 | 1121 | /resolve-from@4.0.0: 1122 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1123 | engines: {node: '>=4'} 1124 | dev: true 1125 | 1126 | /reusify@1.0.4: 1127 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1128 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1129 | dev: true 1130 | 1131 | /rimraf@3.0.2: 1132 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1133 | hasBin: true 1134 | dependencies: 1135 | glob: 7.2.3 1136 | dev: true 1137 | 1138 | /run-parallel@1.2.0: 1139 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1140 | dependencies: 1141 | queue-microtask: 1.2.3 1142 | dev: true 1143 | 1144 | /semver@7.3.8: 1145 | resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} 1146 | engines: {node: '>=10'} 1147 | hasBin: true 1148 | dependencies: 1149 | lru-cache: 6.0.0 1150 | dev: true 1151 | 1152 | /shebang-command@2.0.0: 1153 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1154 | engines: {node: '>=8'} 1155 | dependencies: 1156 | shebang-regex: 3.0.0 1157 | dev: true 1158 | 1159 | /shebang-regex@3.0.0: 1160 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1161 | engines: {node: '>=8'} 1162 | dev: true 1163 | 1164 | /slash@3.0.0: 1165 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1166 | engines: {node: '>=8'} 1167 | dev: true 1168 | 1169 | /strip-ansi@6.0.1: 1170 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1171 | engines: {node: '>=8'} 1172 | dependencies: 1173 | ansi-regex: 5.0.1 1174 | dev: true 1175 | 1176 | /strip-json-comments@3.1.1: 1177 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1178 | engines: {node: '>=8'} 1179 | dev: true 1180 | 1181 | /style-mod@4.1.0: 1182 | resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} 1183 | dev: true 1184 | 1185 | /supports-color@7.2.0: 1186 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1187 | engines: {node: '>=8'} 1188 | dependencies: 1189 | has-flag: 4.0.0 1190 | dev: true 1191 | 1192 | /text-table@0.2.0: 1193 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1194 | dev: true 1195 | 1196 | /to-regex-range@5.0.1: 1197 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1198 | engines: {node: '>=8.0'} 1199 | dependencies: 1200 | is-number: 7.0.0 1201 | dev: true 1202 | 1203 | /tslib@1.14.1: 1204 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 1205 | dev: true 1206 | 1207 | /tslib@2.4.0: 1208 | resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} 1209 | dev: true 1210 | 1211 | /tsutils@3.21.0(typescript@4.7.4): 1212 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 1213 | engines: {node: '>= 6'} 1214 | peerDependencies: 1215 | 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' 1216 | dependencies: 1217 | tslib: 1.14.1 1218 | typescript: 4.7.4 1219 | dev: true 1220 | 1221 | /type-check@0.4.0: 1222 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1223 | engines: {node: '>= 0.8.0'} 1224 | dependencies: 1225 | prelude-ls: 1.2.1 1226 | dev: true 1227 | 1228 | /type-fest@0.20.2: 1229 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1230 | engines: {node: '>=10'} 1231 | dev: true 1232 | 1233 | /typescript@4.7.4: 1234 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} 1235 | engines: {node: '>=4.2.0'} 1236 | hasBin: true 1237 | dev: true 1238 | 1239 | /uri-js@4.4.1: 1240 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1241 | dependencies: 1242 | punycode: 2.3.1 1243 | dev: true 1244 | 1245 | /w3c-keyname@2.2.8: 1246 | resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} 1247 | dev: true 1248 | 1249 | /which@2.0.2: 1250 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1251 | engines: {node: '>= 8'} 1252 | hasBin: true 1253 | dependencies: 1254 | isexe: 2.0.0 1255 | dev: true 1256 | 1257 | /wrappy@1.0.2: 1258 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1259 | dev: true 1260 | 1261 | /yallist@4.0.0: 1262 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1263 | dev: true 1264 | 1265 | /yocto-queue@0.1.0: 1266 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1267 | engines: {node: '>=10'} 1268 | dev: true 1269 | -------------------------------------------------------------------------------- /showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quorafind/Obsidian-Canvas-MindMap/b26802cd164c47b84172fb35b9dd0a0806b1c377/showcase.gif -------------------------------------------------------------------------------- /src/canvasMindMap.ts: -------------------------------------------------------------------------------- 1 | import { Canvas, CanvasEdge, CanvasNode, ItemView, Plugin, requireApiVersion, SettingTab, TFile } from 'obsidian'; 2 | import { around } from "monkey-around"; 3 | import { addEdge, addNode, buildTrees, createChildFileNode, random } from "./utils"; 4 | import { DEFAULT_SETTINGS, MindMapSettings, MindMapSettingTab } from "./mindMapSettings"; 5 | import { CanvasEdgeData } from "obsidian/canvas"; 6 | 7 | const createEdge = async (node1: any, node2: any, canvas: any) => { 8 | 9 | addEdge(canvas, random(16), { 10 | fromOrTo: "from", 11 | side: "right", 12 | node: node1 13 | }, { 14 | fromOrTo: "to", 15 | side: "left", 16 | node: node2 17 | }); 18 | 19 | }; 20 | 21 | const navigate = (canvas: Canvas, direction: string) => { 22 | const currentSelection = canvas.selection; 23 | if (currentSelection.size !== 1) return; 24 | 25 | // Check if the selected node is editing 26 | if (currentSelection.values().next().value.isEditing) return; 27 | 28 | const selectedItem = currentSelection.values().next().value as CanvasNode; 29 | const viewportNodes = canvas.getViewportNodes(); 30 | const {x, y, width, height} = selectedItem; 31 | 32 | canvas.deselectAll(); 33 | 34 | const isVertical = direction === "top" || direction === "bottom"; 35 | const comparePrimary = isVertical ? (a: CanvasNode, b: CanvasNode) => a.y - b.y : (a: CanvasNode, b: CanvasNode) => a.x - b.x; 36 | const compareSecondary = isVertical ? (a: CanvasNode, b: CanvasNode) => a.x - b.x : (a: CanvasNode, b: CanvasNode) => a.y - b.y; 37 | const filterCondition = (node: CanvasNode) => { 38 | const inRange = isVertical 39 | ? node.x < x + width / 2 && node.x + node.width > x + width / 2 40 | : node.y < y + height / 2 && node.y + node.height > y + height / 2; 41 | const directionCondition = direction === "top" ? node.y < y : direction === "bottom" ? node.y > y : direction === "left" ? node.x < x : node.x > x; 42 | return inRange && directionCondition; 43 | }; 44 | 45 | const filteredNodes = viewportNodes.filter(filterCondition); 46 | const sortedNodes = filteredNodes.length > 0 ? filteredNodes.sort(comparePrimary) : viewportNodes.filter((node: CanvasNode) => direction === "top" ? node.y < y : direction === "bottom" ? node.y > y : direction === "left" ? node.x < x : node.x > x).sort(compareSecondary); 47 | const nextNode = sortedNodes[0]; 48 | 49 | if (nextNode) { 50 | canvas.selectOnly(nextNode); 51 | canvas.zoomToSelection(); 52 | } 53 | 54 | return nextNode; 55 | }; 56 | 57 | const createFloatingNode = (canvas: any, direction: string) => { 58 | let selection = canvas.selection; 59 | 60 | if (selection.size !== 1) return; 61 | // Check if the selected node is editing 62 | if (selection.values().next().value.isEditing) return; 63 | 64 | let node = selection.values().next().value; 65 | let x = direction === "left" ? node.x - node.width - 50 : direction === "right" ? node.x + node.width + 50 : node.x; 66 | let y = direction === "top" ? node.y - node.height - 100 : direction === "bottom" ? node.y + node.height + 100 : node.y; 67 | 68 | 69 | const tempChildNode = addNode( 70 | canvas, random(16), { 71 | x: x, 72 | y: y, 73 | width: node.width, 74 | height: node.height, 75 | type: 'text', 76 | content: "", 77 | } 78 | ); 79 | 80 | canvas?.requestSave(); 81 | 82 | const currentNode = canvas.nodes?.get(tempChildNode?.id!); 83 | if (!currentNode) return; 84 | 85 | canvas.selectOnly(currentNode); 86 | canvas.zoomToSelection(); 87 | 88 | setTimeout(() => { 89 | currentNode.startEditing(); 90 | }, 100); 91 | 92 | return tempChildNode; 93 | }; 94 | 95 | const childNode = async (canvas: Canvas, parentNode: CanvasNode, y: number) => { 96 | let tempChildNode = addNode( 97 | canvas, random(16), { 98 | x: parentNode.x + parentNode.width + 200, 99 | y: y, 100 | width: parentNode.width, 101 | height: parentNode.height, 102 | type: 'text', 103 | content: "", 104 | } 105 | ); 106 | await createEdge(parentNode, tempChildNode, canvas); 107 | 108 | canvas.deselectAll(); 109 | const node = canvas.nodes?.get(tempChildNode?.id!); 110 | if (!node) return; 111 | canvas.selectOnly(node); 112 | 113 | canvas.requestSave(); 114 | 115 | return tempChildNode; 116 | }; 117 | 118 | const createChildNode = async (canvas: Canvas, ignored: boolean) => { 119 | if (canvas.selection.size !== 1) return; 120 | const parentNode = canvas.selection.entries().next().value[1]; 121 | 122 | if (parentNode.isEditing && !ignored) return; 123 | 124 | // Calculate the height of all the children nodes 125 | let wholeHeight = 0; 126 | let tempChildNode; 127 | const canvasData = canvas.getData(); 128 | 129 | const prevParentEdges = canvasData.edges.filter((item: CanvasEdgeData) => { 130 | return (item.fromNode === parentNode.id && item.toSide === "left"); 131 | }); 132 | 133 | if (prevParentEdges.length === 0) { 134 | tempChildNode = await childNode(canvas, parentNode, parentNode.y); 135 | } else { 136 | tempChildNode = await siblingNode(canvas, parentNode, prevParentEdges); 137 | } 138 | 139 | return tempChildNode; 140 | 141 | }; 142 | 143 | const siblingNode = async (canvas: Canvas, parentNode: CanvasNode, prevParentEdges: CanvasEdgeData[]) => { 144 | const allEdges = canvas.getEdgesForNode(parentNode).filter((item: CanvasEdge) => { 145 | return prevParentEdges.some((edge: CanvasEdgeData) => { 146 | return item.to.node.id === edge.toNode; 147 | }); 148 | }); 149 | 150 | const allNodes = allEdges.map((edge: CanvasEdge) => edge.to.node); 151 | allNodes.sort((a, b) => a.y - b.y); 152 | const lastNode = allNodes[allNodes.length - 1]; 153 | canvas.selectOnly(lastNode); 154 | return await createSiblingNode(canvas, false); 155 | 156 | }; 157 | 158 | const createSiblingNode = async (canvas: Canvas, ignored: boolean) => { 159 | if (canvas.selection.size !== 1) return; 160 | const selectedNode = canvas.selection.entries().next().value[1]; 161 | 162 | if (selectedNode.isEditing && !ignored) return; 163 | 164 | const incomingEdges = canvas.getEdgesForNode(selectedNode).filter((edge: CanvasEdge) => edge.to.node.id === selectedNode.id); 165 | if (incomingEdges.length === 0) return; 166 | const parentNode = incomingEdges[0].from.node; 167 | 168 | const newYPosition = selectedNode.y + selectedNode.height / 2 + 110; 169 | const newChildNode = await childNode(canvas, parentNode, newYPosition); 170 | 171 | const leftSideEdges = canvas.getEdgesForNode(parentNode).filter((edge: CanvasEdge) => edge.from.node.id === parentNode.id && edge.to.side === "left"); 172 | 173 | let nodes = leftSideEdges.map((edge: CanvasEdge) => edge.to.node); 174 | let totalHeight = nodes.reduce((acc: number, node: CanvasNode) => acc + node.height + 20, 0); 175 | 176 | nodes.sort((a, b) => a.y - b.y); 177 | 178 | if (nodes.length <= 1) return; 179 | if (nodes.length > 1 && nodes[0].x === nodes[1]?.x) { 180 | nodes.forEach((node: CanvasNode, index: number) => { 181 | const yPos = index === 0 ? parentNode.y + parentNode.height / 2 - totalHeight / 2 : nodes[index - 1].y + nodes[index - 1].height + 20; 182 | node.moveTo({x: selectedNode.x, y: yPos}); 183 | }); 184 | } 185 | 186 | canvas.requestSave(); 187 | return newChildNode; 188 | }; 189 | 190 | 191 | export default class CanvasMindMap extends Plugin { 192 | settings: MindMapSettings; 193 | settingTab: MindMapSettingTab; 194 | 195 | 196 | async onload() { 197 | await this.registerSettings(); 198 | this.registerCommands(); 199 | this.patchCanvas(); 200 | this.patchMarkdownFileInfo(); 201 | this.patchCanvasNode(); 202 | } 203 | 204 | onunload() { 205 | 206 | } 207 | 208 | async registerSettings() { 209 | this.settingTab = new MindMapSettingTab(this.app, this); 210 | this.addSettingTab(this.settingTab); 211 | await this.loadSettings(); 212 | } 213 | 214 | registerCommands() { 215 | this.addCommand({ 216 | id: 'split-heading-into-mindmap', 217 | name: 'Split Heading into mindmap based on H1', 218 | checkCallback: (checking: boolean) => { 219 | // Conditions to check 220 | const canvasView = app.workspace.getActiveViewOfType(ItemView); 221 | if (canvasView?.getViewType() === "canvas") { 222 | // If checking is true, we're simply "checking" if the command can be run. 223 | // If checking is false, then we want to actually perform the operation. 224 | 225 | if (!checking) { 226 | // @ts-ignore 227 | const canvas = canvasView?.canvas; 228 | const currentSelection = canvas?.selection; 229 | if (currentSelection.size > 1) { 230 | return; 231 | } 232 | 233 | const currentSelectionItem = currentSelection.values().next().value; 234 | if (!currentSelectionItem.filePath) return; 235 | 236 | const currentSelectionItemFile = currentSelectionItem.file as TFile; 237 | if (!(currentSelectionItemFile.extension === "md")) return; 238 | 239 | const currentFileHeadings = app.metadataCache.getFileCache(currentSelectionItemFile)?.headings; 240 | if (!currentFileHeadings) return; 241 | 242 | const currentFileHeadingH1 = currentFileHeadings.filter(heading => heading.level === 1); 243 | if (currentFileHeadingH1.length === 0) return; 244 | 245 | const nodeGroupHeight = (currentSelectionItem.height * 0.6 + 20) * currentFileHeadingH1.length; 246 | let direction = -1; 247 | const nodeGroupY = currentSelectionItem.y + currentSelectionItem.height / 2 + (nodeGroupHeight / 2) * direction; 248 | 249 | currentFileHeadingH1.forEach((item, index) => { 250 | createChildFileNode(canvas, currentSelectionItem, currentSelectionItemFile, "#" + item.heading, nodeGroupY - direction * (currentSelectionItem.height * 0.6 + 20) * index); 251 | }); 252 | } 253 | return true; 254 | } 255 | } 256 | }); 257 | 258 | this.addCommand({ 259 | id: 'create-floating-node', 260 | name: 'Create floating node', 261 | checkCallback: (checking: boolean) => { 262 | // Conditions to check 263 | const canvasView = this.app.workspace.getActiveViewOfType(ItemView); 264 | if (canvasView?.getViewType() === "canvas") { 265 | // If checking is true, we're simply "checking" if the command can be run. 266 | // If checking is false, then we want to actually perform the operation. 267 | if (!checking) { 268 | // @ts-ignore 269 | const canvas = canvasView?.canvas; 270 | 271 | const node = canvas.createTextNode({ 272 | pos: { 273 | x: 0, 274 | y: 0, 275 | height: 500, 276 | width: 400 277 | }, 278 | size: { 279 | x: 0, 280 | y: 0, 281 | height: 500, 282 | width: 400 283 | }, 284 | text: "", 285 | focus: true, 286 | save: true, 287 | }); 288 | 289 | canvas.addNode(node); 290 | canvas.requestSave(); 291 | if (!node) return; 292 | 293 | setTimeout(() => { 294 | node.startEditing(); 295 | canvas.zoomToSelection(); 296 | }, 0); 297 | } 298 | 299 | // This command will only show up in Command Palette when the check function returns true 300 | return true; 301 | } 302 | } 303 | }); 304 | 305 | this.addCommand({ 306 | id: 'create-child-node', 307 | name: 'Create child node', 308 | checkCallback: (checking: boolean) => { 309 | const view = this.app.workspace.getActiveViewOfType(ItemView); 310 | const canvasView = this.app.workspace.getActiveViewOfType(ItemView); 311 | if (canvasView?.getViewType() === "canvas") { 312 | if (!checking) { 313 | // @ts-ignore 314 | const canvas = canvasView?.canvas; 315 | 316 | createChildNode(canvas, true).then((node) => { 317 | if (!node) return; 318 | 319 | setTimeout(() => { 320 | const realNode = canvas.nodes?.get(node.id); 321 | canvas.zoomToSelection(); 322 | 323 | realNode?.startEditing(); 324 | }, 0); 325 | }); 326 | 327 | 328 | } 329 | 330 | return true; 331 | } 332 | } 333 | }); 334 | 335 | this.addCommand({ 336 | id: 'create-sibling-node', 337 | name: 'Create sibling node', 338 | checkCallback: (checking: boolean) => { 339 | const view = this.app.workspace.getActiveViewOfType(ItemView); 340 | const canvasView = this.app.workspace.getActiveViewOfType(ItemView); 341 | if (canvasView?.getViewType() === "canvas") { 342 | if (!checking) { 343 | // @ts-ignore 344 | const canvas = canvasView?.canvas; 345 | 346 | createSiblingNode(canvas, true).then((node) => { 347 | if (!node) return; 348 | 349 | setTimeout(() => { 350 | // @ts-ignore 351 | const realNode = canvas.nodes?.get(node.id); 352 | canvas.zoomToSelection(); 353 | 354 | realNode?.startEditing(); 355 | }, 0); 356 | }); 357 | 358 | 359 | } 360 | 361 | return true; 362 | } 363 | } 364 | }); 365 | } 366 | 367 | patchCanvas() { 368 | 369 | 370 | const patchCanvas = () => { 371 | 372 | const self = this; 373 | 374 | const canvasView = this.app.workspace.getLeavesOfType("canvas").first()?.view; 375 | // @ts-ignore 376 | const canvas = canvasView?.canvas; 377 | 378 | if (!canvasView) return false; 379 | const patchCanvasView = canvas.constructor; 380 | 381 | const canvasViewunistaller = around(canvasView.constructor.prototype, { 382 | onOpen: (next) => 383 | async function () { 384 | if (self.settings.create.createFloat) { 385 | this.scope.register(["Mod"], "ArrowUp", () => { 386 | createFloatingNode(this.canvas, "top"); 387 | }); 388 | this.scope.register(["Mod"], "ArrowDown", () => { 389 | createFloatingNode(this.canvas, "bottom"); 390 | }); 391 | this.scope.register(["Mod"], "ArrowLeft", () => { 392 | createFloatingNode(this.canvas, "left"); 393 | }); 394 | this.scope.register(["Mod"], "ArrowRight", () => { 395 | createFloatingNode(this.canvas, "right"); 396 | }); 397 | } 398 | 399 | if (self.settings.navigate.useNavigate) { 400 | this.scope.register(["Alt"], "ArrowUp", () => { 401 | navigate(this.canvas, "top"); 402 | }); 403 | this.scope.register(["Alt"], "ArrowDown", () => { 404 | navigate(this.canvas, "bottom"); 405 | }); 406 | this.scope.register(["Alt"], "ArrowLeft", () => { 407 | navigate(this.canvas, "left"); 408 | }); 409 | this.scope.register(["Alt"], "ArrowRight", () => { 410 | navigate(this.canvas, "right"); 411 | }); 412 | } 413 | 414 | this.scope.register([], "Enter", async () => { 415 | const node = await createSiblingNode(this.canvas, false); 416 | if (!node) return; 417 | 418 | setTimeout(() => { 419 | const realNode = this.canvas.nodes?.get(node.id); 420 | realNode?.startEditing(); 421 | this.canvas.zoomToSelection(); 422 | }, 0); 423 | }); 424 | 425 | this.scope.register([], "Tab", async (ev: KeyboardEvent) => { 426 | 427 | const node = await createChildNode(this.canvas, false); 428 | if (!node) return; 429 | 430 | setTimeout(() => { 431 | const realNode = this.canvas.nodes?.get(node.id); 432 | realNode?.startEditing(); 433 | this.canvas.zoomToSelection(); 434 | }, 0); 435 | }); 436 | 437 | this.scope.register([], 'Space', async (ev: KeyboardEvent) => { 438 | const selection = this.canvas.selection; 439 | if (selection.size !== 1) return; 440 | const node = selection.entries().next().value[1]; 441 | 442 | if (node?.label || node?.url) return; 443 | 444 | if (node.isEditing) return; 445 | node.startEditing(); 446 | 447 | }); 448 | 449 | return next.call(this); 450 | } 451 | }); 452 | 453 | const uninstaller = around(patchCanvasView.prototype, { 454 | onKeydown: (next) => 455 | async function (e: any) { 456 | if (e.key === "Backspace" || e.key === "Delete") { 457 | if (this.selection.size !== 1) { 458 | return next.call(this, e); 459 | } 460 | const childNode = this.selection.entries().next().value[1]; 461 | if (childNode.isEditing) return; 462 | 463 | const edges = this.getEdgesForNode(childNode).filter((item: any) => { 464 | return item.to.node.id === childNode.id; 465 | }); 466 | if (edges.length === 0) return; 467 | const parentNode = edges[0].from.node; 468 | 469 | 470 | next.call(this, e); 471 | 472 | let wholeHeight = 0; 473 | let parentEdges = this.getEdgesForNode(parentNode).filter((item: any) => { 474 | return (item.from.node.id === parentNode.id && item.to.side === "left"); 475 | }); 476 | 477 | let allnodes = []; 478 | for (let i = 0; i < parentEdges.length; i++) { 479 | let node = parentEdges[i].to.node; 480 | allnodes.push(node); 481 | wholeHeight += (node.height + 20); 482 | } 483 | allnodes.sort((a: any, b: any) => { 484 | return a.y - b.y; 485 | }); 486 | 487 | // Check if this is a Mindmap 488 | if (allnodes.length === 1) return; 489 | if (allnodes.length > 1) { 490 | if (allnodes[0].x !== allnodes[0].x) { 491 | return; 492 | } 493 | } 494 | 495 | let preNode; 496 | for (let i = 0; i < allnodes.length; i++) { 497 | let tempNode; 498 | if (i === 0) { 499 | (tempNode = allnodes[i]).moveTo({ 500 | x: childNode.x, 501 | y: parentNode.y + parentNode.height - (wholeHeight / 2) 502 | }); 503 | } else { 504 | (tempNode = allnodes[i]).moveTo({ 505 | x: childNode.x, 506 | y: preNode.y + preNode.height + 20 507 | }); 508 | } 509 | this.requestSave(); 510 | preNode = tempNode; 511 | } 512 | 513 | this.requestSave(); 514 | 515 | this.selectOnly(parentNode); 516 | this.zoomToSelection(); 517 | parentNode.startEditing(); 518 | 519 | return; 520 | } 521 | 522 | if (e.key === " ") { 523 | const selection = this.selection; 524 | if (selection.size !== 1) return; 525 | const node = selection.entries().next().value[1]; 526 | 527 | if (node?.label || node?.url) return; 528 | 529 | if (node.isEditing) return; 530 | node.startEditing(); 531 | } 532 | 533 | next.call(this, e); 534 | }, 535 | }); 536 | this.register(uninstaller); 537 | this.register(canvasViewunistaller); 538 | 539 | canvas?.view.leaf.rebuildView(); 540 | console.log("Obsidian-Canvas-MindMap: canvas view patched"); 541 | return true; 542 | }; 543 | 544 | this.app.workspace.onLayoutReady(() => { 545 | if (!patchCanvas()) { 546 | const evt = this.app.workspace.on("layout-change", () => { 547 | patchCanvas() && this.app.workspace.offref(evt); 548 | }); 549 | this.registerEvent(evt); 550 | } 551 | }); 552 | } 553 | 554 | patchCanvasNode() { 555 | const patchNode = () => { 556 | const canvasView = app.workspace.getLeavesOfType("canvas").first()?.view; 557 | // @ts-ignore 558 | const canvas = canvasView?.canvas; 559 | if (!canvas) return false; 560 | 561 | const node = Array.from(canvas.nodes).first(); 562 | if (!node) return false; 563 | 564 | // @ts-ignore 565 | const nodeInstance = node[1]; 566 | 567 | const uninstaller = around(nodeInstance.constructor.prototype, { 568 | setColor: (next: any) => 569 | function (e: any, t: any) { 570 | next.call(this, e, t); 571 | this.canvas.getEdgesForNode(this).forEach((edge: any) => { 572 | if (edge.from.node === this) { 573 | edge.setColor(e, true); 574 | edge.render(); 575 | // edge.to.node.setColor(e, true); 576 | } 577 | }); 578 | canvas.requestSave(); 579 | }, 580 | }); 581 | this.register(uninstaller); 582 | 583 | console.log("Obsidian-Canvas-MindMap: canvas node patched"); 584 | return true; 585 | }; 586 | 587 | this.app.workspace.onLayoutReady(() => { 588 | if (!patchNode()) { 589 | const evt = app.workspace.on("layout-change", () => { 590 | patchNode() && app.workspace.offref(evt); 591 | }); 592 | this.registerEvent(evt); 593 | } 594 | }); 595 | } 596 | 597 | patchMarkdownFileInfo() { 598 | const patchEditor = () => { 599 | const editorInfo = app.workspace.activeEditor; 600 | 601 | console.log(editorInfo); 602 | if (!editorInfo) return false; 603 | if (!editorInfo || !editorInfo.containerEl || editorInfo.containerEl.closest('.common-editor-inputer')) return false; 604 | 605 | const patchEditorInfo = editorInfo.constructor; 606 | 607 | const uninstaller = around(patchEditorInfo.prototype, { 608 | showPreview: (next) => 609 | function (e: any) { 610 | next.call(this, e); 611 | if (e) { 612 | this.node?.canvas.wrapperEl.focus(); 613 | this.node?.setIsEditing(false); 614 | } 615 | }, 616 | }); 617 | this.register(uninstaller); 618 | 619 | console.log("Obsidian-Canvas-MindMap: markdown file info patched"); 620 | return true; 621 | }; 622 | 623 | this.app.workspace.onLayoutReady(() => { 624 | if (!patchEditor()) { 625 | const evt = app.workspace.on("file-open", () => { 626 | setTimeout(() => { 627 | patchEditor() && app.workspace.offref(evt); 628 | }, 100); 629 | }); 630 | this.registerEvent(evt); 631 | } 632 | }); 633 | } 634 | 635 | public async loadSettings(): Promise { 636 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 637 | } 638 | 639 | async saveSettings(): Promise { 640 | await this.saveData(this.settings); 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /src/mindMapSettings.ts: -------------------------------------------------------------------------------- 1 | import { App, debounce, Platform, PluginSettingTab, Setting } from "obsidian"; 2 | import CanvasMindMap from "./canvasMindMap"; 3 | 4 | type ModifierKey = 'Alt' | 'Mod' | 'Shift'; 5 | 6 | function supportModifierKey() { 7 | 8 | return ['Alt', 'Mod', 'Shift']; 9 | 10 | } 11 | 12 | export interface MindMapSettings { 13 | navigate: { 14 | useNavigate: boolean; 15 | modifierKey: string[]; 16 | }; 17 | create: { 18 | createFloat: boolean; 19 | childDirection: string; 20 | siblingWidth: number; 21 | siblingHeight: number; 22 | }; 23 | layout: { 24 | direction: 'TB' | 'BT' | 'LR' | 'RL'; 25 | autoHeight: boolean; 26 | autoLayout: boolean; 27 | autoLayoutDirection: 'TB' | 'BT' | 'LR' | 'RL'; 28 | }; 29 | advanced: { 30 | transferToCommands: boolean; 31 | }; 32 | } 33 | 34 | 35 | export const DEFAULT_SETTINGS: MindMapSettings = { 36 | navigate: { 37 | useNavigate: true, 38 | modifierKey: ['Alt'], 39 | }, 40 | create: { 41 | createFloat: true, 42 | childDirection: 'right', 43 | siblingWidth: 200, 44 | siblingHeight: 100, 45 | }, 46 | layout: { 47 | direction: 'LR', 48 | autoLayout: true, 49 | autoLayoutDirection: 'LR', 50 | autoHeight: true, 51 | }, 52 | advanced: { 53 | transferToCommands: false, 54 | } 55 | }; 56 | 57 | export class MindMapSettingTab extends PluginSettingTab { 58 | plugin: CanvasMindMap; 59 | 60 | updateSettings(key: any, value: any): void { 61 | 62 | this.plugin.settings = { 63 | ...this.plugin.settings, 64 | [key.split('.')[0]]: { 65 | // @ts-ignore 66 | ...this.plugin.settings[key.split('.')[0]], 67 | [key.split('.')[1]]: value, 68 | }, 69 | }; 70 | this.applySettingsUpdate(); 71 | } 72 | 73 | applySettingsUpdate = debounce( 74 | async () => { 75 | await this.plugin.saveSettings(); 76 | console.log('debounce'); 77 | }, 78 | 300, 79 | true, 80 | ); 81 | 82 | constructor(app: App, plugin: CanvasMindMap) { 83 | super(app, plugin); 84 | this.plugin = plugin; 85 | } 86 | 87 | display(): void { 88 | const {containerEl} = this; 89 | 90 | containerEl.empty(); 91 | 92 | containerEl.createEl('h2', {text: 'Canvas MindMap'}); 93 | 94 | this.useNavigateHotkeySetting(containerEl, this.plugin.settings); 95 | this.createHotkeySetting(containerEl, this.plugin.settings); 96 | 97 | new Setting(containerEl) 98 | .setName('Donate') 99 | .setDesc('If you like this plugin, consider donating to support continued development:') 100 | .addButton((bt) => { 101 | bt.buttonEl.outerHTML = ``; 102 | }); 103 | } 104 | 105 | useNavigateHotkeySetting(containerEl: HTMLElement, setting: MindMapSettings) { 106 | new Setting(containerEl) 107 | .setName('Use Navigate Hotkey') 108 | .setDesc('Use the hotkey to navigate the mind map') 109 | .addToggle((toggle) => { 110 | toggle.setValue(setting.navigate.useNavigate); 111 | toggle.onChange((value) => { 112 | this.updateSettings('navigate.useNavigate', value); 113 | 114 | setTimeout(() => { 115 | this.display(); 116 | }, 700); 117 | }); 118 | }); 119 | 120 | // if (setting.navigate.useNavigate) { 121 | // new Setting(containerEl) 122 | // .setName('Modifier Key') 123 | // .setDesc('The modifier key to use with the hotkey') 124 | // .addDropdown((dropdown) => { 125 | // const mods = supportModifierKey(); 126 | // dropdown.addOption('None', 'None'); 127 | // dropdown.setValue(setting.navigate.modifierKey[0]); 128 | // dropdown.onChange((value) => { 129 | // this.updateSettings('navigate.modifierKey.0', value); 130 | // }); 131 | // }); 132 | // } 133 | 134 | } 135 | 136 | private createHotkeySetting(containerEl: HTMLElement, setting: MindMapSettings) { 137 | new Setting(containerEl) 138 | .setName('Create Float') 139 | .setDesc('Create a float node') 140 | .addToggle((toggle) => { 141 | toggle.setValue(setting.create.createFloat); 142 | toggle.onChange((value) => { 143 | this.updateSettings('create.createFloat', value); 144 | }); 145 | }); 146 | // 147 | // new Setting(containerEl) 148 | // .setName('Child Direction') 149 | // .setDesc('The direction of the child node') 150 | // .addDropdown((dropdown) => { 151 | // dropdown.addOption('Right', 'right'); 152 | // dropdown.addOption('Left', 'left'); 153 | // dropdown.addOption('Up', 'up'); 154 | // dropdown.addOption('Down', 'down'); 155 | // dropdown.setValue(setting.create.childDirection); 156 | // dropdown.onChange((value) => { 157 | // this.updateSettings('create.childDirection', value); 158 | // }); 159 | // }); 160 | // 161 | // new Setting(containerEl) 162 | // .setName('Sibling Width') 163 | // .setDesc('The width of the sibling node') 164 | // .addSlider((slider) => { 165 | // slider.setLimits(100, 500, 10); 166 | // slider.setValue(setting.create.siblingWidth); 167 | // slider.onChange((value) => { 168 | // this.updateSettings('create.siblingWidth', value); 169 | // }); 170 | // }); 171 | // 172 | // new Setting(containerEl) 173 | // .setName('Sibling Height') 174 | // .setDesc('The height of the sibling node') 175 | // .addSlider((slider) => { 176 | // slider.setLimits(50, 300, 10); 177 | // slider.setValue(setting.create.siblingHeight); 178 | // slider.onChange((value) => { 179 | // this.updateSettings('create.siblingHeight', value); 180 | // }); 181 | // }); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/types/obsidian.d.ts: -------------------------------------------------------------------------------- 1 | import 'obsidian'; 2 | import { MarkdownView, TFile } from 'obsidian'; 3 | import { CanvasData } from 'obsidian/canvas'; 4 | 5 | export interface CanvasNodeUnknownData { 6 | id: string; 7 | collapsed: boolean; 8 | 9 | [key: string]: any; 10 | } 11 | 12 | declare module 'obsidian' { 13 | type CanvasNodeID = string; 14 | type CanvasEdgeID = string; 15 | 16 | interface App { 17 | appId: string; 18 | plugins: { 19 | getPlugin(name: string): any; 20 | }; 21 | commands: any; 22 | } 23 | 24 | interface MarkdownFileInfo { 25 | containerEl: HTMLElement; 26 | } 27 | 28 | interface View { 29 | contentEl: HTMLElement; 30 | 31 | file: TFile; 32 | } 33 | 34 | interface CanvasView extends View { 35 | canvas: Canvas; 36 | file: TFile; 37 | } 38 | 39 | interface Canvas { 40 | readonly: boolean; 41 | view: MarkdownView; 42 | x: number; 43 | y: number; 44 | nodes: Map; 45 | edges: Map; 46 | nodeInteractionLayer: CanvasInteractionLayer; 47 | selection: Set; 48 | 49 | menu: CanvasMenu; 50 | 51 | wrapperEl: HTMLElement; 52 | 53 | history: any; 54 | requestPushHistory: any; 55 | nodeIndex: any; 56 | 57 | importData(data: CanvasData): void; 58 | 59 | requestSave(save?: boolean, triggerBySelf?: boolean): void; 60 | 61 | getData(): CanvasData; 62 | 63 | setData(data: CanvasData): void; 64 | 65 | getEdgesForNode(node: CanvasNode): CanvasEdge[]; 66 | 67 | getContainingNodes(coords: CanvasCoords): CanvasNode[]; 68 | 69 | deselectAll(): void; 70 | 71 | select(nodes: CanvasNode): void; 72 | 73 | requestFrame(): void; 74 | 75 | getViewportNodes(): CanvasNode[]; 76 | 77 | selectOnly(nodes: CanvasNode): void; 78 | 79 | requestSave(save?: boolean, triggerBySelf?: boolean): void; 80 | 81 | zoomToSelection(): void; 82 | } 83 | 84 | interface ICanvasData { 85 | nodes: CanvasNode[]; 86 | edges: CanvasEdge[]; 87 | } 88 | 89 | interface CanvasMenu { 90 | containerEl: HTMLElement; 91 | menuEl: HTMLElement; 92 | canvas: Canvas; 93 | selection: CanvasSelection; 94 | 95 | render(): void; 96 | 97 | updateZIndex(): void; 98 | } 99 | 100 | interface CanvasSelection { 101 | selectionEl: HTMLElement; 102 | resizerEls: HTMLElement; 103 | canvas: Canvas; 104 | bbox: CanvasCoords | undefined; 105 | 106 | render(): void; 107 | 108 | hide(): void; 109 | 110 | onResizePointerDown(e: PointerEvent, direction: CanvasDirection): void; 111 | 112 | update(bbox: CanvasCoords): void; 113 | } 114 | 115 | interface CanvasInteractionLayer { 116 | interactionEl: HTMLElement; 117 | canvas: Canvas; 118 | target: CanvasNode | null; 119 | 120 | render(): void; 121 | 122 | setTarget(target: CanvasNode | null): void; 123 | } 124 | 125 | interface CanvasNode { 126 | id: CanvasNodeID; 127 | 128 | x: number; 129 | y: number; 130 | width: number; 131 | height: number; 132 | zIndex: number; 133 | bbox: CanvasCoords; 134 | unknownData: CanvasNodeUnknownData; 135 | renderedZIndex: number; 136 | 137 | headerComponent: Component; 138 | 139 | nodeEl: HTMLElement; 140 | labelEl: HTMLElement; 141 | contentEl: HTMLElement; 142 | containerEl: HTMLElement; 143 | 144 | canvas: Canvas; 145 | app: App; 146 | 147 | getBBox(containing?: boolean): CanvasCoords; 148 | 149 | moveTo({x, y}: { x: number, y: number }): void; 150 | 151 | render(): void; 152 | } 153 | 154 | interface CanvasTextNode extends CanvasNode { 155 | text: string; 156 | child: any; 157 | } 158 | 159 | interface CanvasFileNode extends CanvasNode { 160 | file: TFile; 161 | } 162 | 163 | interface CanvasLinkNode extends CanvasNode { 164 | url: string; 165 | } 166 | 167 | interface CanvasGroupNode extends CanvasNode { 168 | label: string; 169 | } 170 | 171 | interface CanvasEdge { 172 | id: CanvasEdgeID; 173 | 174 | label: string | undefined; 175 | lineStartGroupEl: SVGGElement; 176 | lineEndGroupEl: SVGGElement; 177 | lineGroupEl: SVGGElement; 178 | 179 | path: { 180 | display: SVGPathElement; 181 | interaction: SVGPathElement; 182 | }; 183 | 184 | from: { 185 | node: CanvasNode; 186 | }; 187 | 188 | to: { 189 | side: 'left' | 'right' | 'top' | 'bottom'; 190 | node: CanvasNode; 191 | }; 192 | 193 | canvas: Canvas; 194 | bbox: CanvasCoords; 195 | 196 | unknownData: CanvasNodeUnknownData; 197 | } 198 | 199 | interface CanvasCoords { 200 | maxX: number; 201 | maxY: number; 202 | minX: number; 203 | minY: number; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Canvas, CanvasEdge, CanvasNode, requireApiVersion, TFile } from "obsidian"; 2 | import { CanvasData, CanvasEdgeData, CanvasFileData, CanvasNodeData, CanvasTextData } from "obsidian/canvas"; 3 | 4 | interface edgeT { 5 | fromOrTo: string; 6 | side: string, 7 | node: CanvasNode | CanvasNodeData, 8 | } 9 | 10 | interface TreeNode { 11 | id: string; 12 | children: TreeNode[]; 13 | } 14 | 15 | export const random = (e: number) => { 16 | let t = []; 17 | for (let n = 0; n < e; n++) { 18 | t.push((16 * Math.random() | 0).toString(16)); 19 | } 20 | return t.join(""); 21 | }; 22 | 23 | export const createChildFileNode = (canvas: any, parentNode: any, file: TFile, path: string, y: number) => { 24 | const node = addNode( 25 | canvas, random(16), 26 | { 27 | x: parentNode.x + parentNode.width + 200, 28 | y: y, 29 | width: parentNode.width, 30 | height: parentNode.height * 0.6, 31 | 32 | type: 'file', 33 | content: file.path, 34 | subpath: path, 35 | } 36 | ); 37 | 38 | addEdge(canvas, random(16), { 39 | fromOrTo: "from", 40 | side: "right", 41 | node: parentNode 42 | }, { 43 | fromOrTo: "to", 44 | side: "left", 45 | node: node 46 | }); 47 | 48 | canvas.requestSave(); 49 | 50 | return node; 51 | }; 52 | 53 | 54 | export const addNode = (canvas: Canvas, id: string, { 55 | x, 56 | y, 57 | width, 58 | height, 59 | type, 60 | content, 61 | subpath, 62 | }: { 63 | x: number, 64 | y: number, 65 | width: number, 66 | height: number, 67 | type: 'text' | 'file', 68 | content: string, 69 | subpath?: string, 70 | }) => { 71 | if (!canvas) return; 72 | 73 | const data = canvas.getData(); 74 | if (!data) return; 75 | 76 | const node: Partial = { 77 | "id": id, 78 | "x": x, 79 | "y": y, 80 | "width": width, 81 | "height": height, 82 | "type": type, 83 | }; 84 | 85 | switch (type) { 86 | case 'text': 87 | node.text = content; 88 | break; 89 | case 'file': 90 | node.file = content; 91 | if (subpath) node.subpath = subpath; 92 | break; 93 | } 94 | 95 | canvas.importData({ 96 | "nodes": [ 97 | ...data.nodes, 98 | node], 99 | "edges": data.edges, 100 | }); 101 | 102 | canvas.requestFrame(); 103 | 104 | return node; 105 | }; 106 | 107 | export const addEdge = (canvas: any, edgeID: string, fromEdge: edgeT, toEdge: edgeT) => { 108 | if (!canvas) return; 109 | 110 | const data = canvas.getData(); 111 | if (!data) return; 112 | 113 | canvas.importData({ 114 | "edges": [ 115 | ...data.edges, 116 | { 117 | "id": edgeID, 118 | "fromNode": fromEdge.node.id, 119 | "fromSide": fromEdge.side, 120 | "toNode": toEdge.node.id, 121 | "toSide": toEdge.side 122 | } 123 | ], 124 | "nodes": data.nodes, 125 | }); 126 | 127 | canvas.requestFrame(); 128 | }; 129 | 130 | export function buildTrees(canvasData: CanvasData, direction: 'LR' | 'RL' | 'TB' | 'BT'): TreeNode[] { 131 | const trees: TreeNode[] = []; 132 | const nodeMap: Map = new Map(); 133 | const edgeMap: Map = new Map(); 134 | 135 | canvasData.nodes.forEach(node => { 136 | nodeMap.set(node.id, { 137 | ...node, 138 | children: [] 139 | }); 140 | }); 141 | 142 | canvasData.edges.forEach(edge => { 143 | if (!edgeMap.has(edge.fromNode)) { 144 | edgeMap.set(edge.fromNode, []); 145 | } 146 | edgeMap.get(edge.fromNode)?.push(edge.toNode); 147 | }); 148 | 149 | const rootNodes = canvasData.nodes.filter(node => 150 | !canvasData.edges.some(edge => edge.toNode === node.id) 151 | ); 152 | 153 | rootNodes.forEach(rootNode => { 154 | const tree = buildTree(rootNode.id, edgeMap, nodeMap, direction); 155 | trees.push(tree); 156 | }); 157 | 158 | return trees; 159 | } 160 | 161 | function buildTree(nodeId: string, edgeMap: Map, nodeMap: Map, direction: 'LR' | 'RL' | 'TB' | 'BT'): TreeNode { 162 | const node = nodeMap.get(nodeId) as TreeNode; 163 | 164 | edgeMap.get(nodeId)?.forEach(childId => { 165 | if (shouldAddChild(nodeId, childId, direction, nodeMap)) { 166 | node.children.push(buildTree(childId, edgeMap, nodeMap, direction)); 167 | } 168 | }); 169 | return node; 170 | } 171 | 172 | function shouldAddChild(parentId: string, childId: string, direction: 'LR' | 'RL' | 'TB' | 'BT', nodeMap: Map): boolean { 173 | const parent = nodeMap.get(parentId) as unknown as CanvasNodeData; 174 | const child = nodeMap.get(childId) as unknown as CanvasNodeData; 175 | 176 | switch (direction) { 177 | case 'LR': 178 | return parent.x < child.x; 179 | case 'RL': 180 | return parent.x > child.x; 181 | case 'TB': 182 | return parent.y < child.y; 183 | case 'BT': 184 | return parent.y > child.y; 185 | default: 186 | return true; 187 | } 188 | } 189 | 190 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | -------------------------------------------------------------------------------- /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 | "0.0.1": "1.1.0", 3 | "0.0.2": "1.1.0", 4 | "0.0.3": "1.1.0", 5 | "0.0.4": "1.1.0", 6 | "0.0.5": "1.1.0", 7 | "0.0.6": "1.1.0", 8 | "0.0.7": "1.1.0", 9 | "0.1.0": "1.1.0", 10 | "0.1.1": "1.1.0", 11 | "0.1.2": "1.1.0", 12 | "0.1.3": "1.1.0", 13 | "0.1.4": "1.4.0", 14 | "0.1.5": "1.4.0", 15 | "0.1.6": "1.4.0", 16 | "0.1.7": "1.4.0", 17 | "0.1.8": "1.4.0", 18 | "0.1.9": "1.4.0", 19 | "0.1.10": "1.4.0", 20 | "0.1.11": "1.4.0", 21 | "1.0.0": "1.4.0", 22 | "1.0.1": "1.4.0", 23 | "1.0.2": "1.4.0" 24 | } --------------------------------------------------------------------------------