├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── README.md ├── README_CN.md ├── assets ├── canvas_candy.jpg ├── connection_line.jpg ├── css_config.jpg ├── cssclasses.jpg ├── custom_icon.jpg ├── menu_config.jpg └── submenu_config.jpg ├── esbuild.config.mjs ├── manifest.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── src ├── CanvasStyle.ts ├── main.ts ├── memuConfigs.ts ├── setting.ts └── utils.ts ├── styles.css ├── tsconfig.json ├── version-bump.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Canvas Style Menu 2 | 3 | [中文说明](README_CN.md) 4 | 5 | First of all, I want to express gratitude to @[Quorafind (Boninall) (github.com) ](https://github.com/Quorafind)for bringing us many fantastic and practical plugins: 6 | 7 | - [Obsidian-Memos](https://github.com/Quorafind/Obsidian-Memos) 8 | - [Obsidian-Card-Library](https://github.com/Quorafind/Obsidian-Card-Library) 9 | - [Obsidian-Canvas-MindMap](https://github.com/Quorafind/Obsidian-Canvas-MindMap) 10 | - [Obsidian-Collapse-Node](https://github.com/Quorafind/Obsidian-Collapse-Node) 11 | - and more... 12 | 13 | **Obsidian Canvas Style Menu is based on [Obsidian-Collapse-Node](https://github.com/Quorafind/Obsidian-Collapse-Node). I modified the relevant code, added new features, and created this new plugin. 14 | 15 | Once again, thank you for allowing me to use your code!!! 16 | 17 | **Obsidian Canvas Style Menu** allows you to seamlessly modify canvas card styles through the canvas menu and supports the extension of styles using your own CSS snippets. 18 | 19 | ## Usage 20 | 21 | **Obsidian Canvas Style Menu **comes with several simple styles that you can directly use in the style menu, right-clicking on the button allows you to remove the style. You can also extend or override the default style menu using your own menu configuration and CSS snippets. Simply add your own menu configuration on the plugin settings page. Here's a brief explanation, and detailed documentation will be added later. 22 | 23 | 1. First, add Menu configuration: 24 | 25 | ![](./assets/menu_config.jpg) 26 | 27 | **Note: The default Menu Config added is for style menus on cards. If you want to add style menus for connection lines, simply add `cat: 'edge'` in the Menu Config.** 28 | 29 | ![](./assets/connection_line.jpg) 30 | 31 | 2. Second, add Sub Menu configuration: 32 | **Note: No need to add `cat: 'edge'` in the Sub Menu Config.** 33 | 34 | ![](./assets/submenu_config.jpg) 35 | 36 | 3. Third, add your own CSS snippets, where the class names for each style should correspond to the class names in your menu configuration: 37 | 38 | ![](./assets/css_config.jpg) 39 | 40 | 4. Fourth, congratulations, you can now use the styles you added in the style menu! 41 | 42 | **Note:** If your menu does not have sub-menus, the menu button will become a toggle button. Left-click applies the style, and right-click cancels the style. 43 | 44 | **Obsidian Canvas Style Menu** supports the optional Obsidian default cssclasses styling method. You can achieve the effect of cssclasses without manually writing cssclasses by simply adding `selector: 'cc'` in your Menu Config. 45 | 46 | ![](./assets/cssclasses.jpg) 47 | 48 | **For [Canvas Candy](https://tfthacker.com/canvas-candy) users, no need to write cssclasses anymore. Simply add the commonly used Canvas Candy cssclasses to the menu configs for easy use.** 49 | 50 | **Note: Canvas Candy is a paid product and is not included in Canvas Style Menu. If you wish to use it, please visit [Canvas Candy](https://tfthacker.com/canvas-candy) for more information.** 51 | 52 | ![](./assets/canvas_candy.jpg) 53 | 54 | **Obsidian Canvas Style Menu** supports adding custom icons. On the plugin settings page, scroll to the bottom, enter the icon name and SVG code, then click the add button on the left. 55 | 56 | How to get an SVG icon? 57 | Taking [Lucide](https://lucide.dev/) icons as an example, find the desired icon, click the "Copy SVG" button, then paste it on the plugin settings page. 58 | **Note that the icon must comply with Obsidian's [Icon design guidelines](https://docs.obsidian.md/Plugins/User+interface/Icons#Icon+design+guidelines)**. 59 | 60 | ![](./assets/custom_icon.jpg) 61 | 62 | ## Installation 63 | 64 | - Not ready for market yet 65 | - Can be installed via the [Brat](https://github.com/TfTHacker/obsidian42-brat) plugin 66 | - Manual installation 67 | 68 | 1. Find the release page on this github page and click 69 | 2. Download the latest release zip file 70 | 3. Unzip it, copy the unzipped folder to the obsidian plugin folder, make sure there are main.js and manifest.json files 71 | in the folder 72 | 4. Restart obsidian (do not restart also, you have to refresh plugin list), in the settings interface to enable the 73 | plugin 74 | 5. Done! 75 | 76 | ## Say Thank You 77 | 78 | If you are enjoy using Obsidian-Canvas-Style-Menu then please support my work and enthusiasm by buying me a coffee on [https://www.buymeacoffee.com/michaellw](https://www.buymeacoffee.com/michaellw). 79 | 80 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Obsidian Canvas Style Menu 2 | 3 | [English](README.md) 4 | 5 | 6 | 7 | 首先要感谢@[Quorafind (Boninall) (github.com)](https://github.com/Quorafind),他为我们带来了很多超棒的实用的插件: 8 | 9 | - [Obsidian-Memos](https://github.com/Quorafind/Obsidian-Memos) 10 | - [Obsidian-Card-Library](https://github.com/Quorafind/Obsidian-Card-Library) 11 | - [Obsidian-Canvas-MindMap](https://github.com/Quorafind/Obsidian-Canvas-MindMap) 12 | - [Obsidian-Collapse-Node](https://github.com/Quorafind/Obsidian-Collapse-Node) 13 | - 以及其他…… 14 | 15 | **Obsidian Canvas Style Menu**正是基于[Obsidian-Collapse-Node](https://github.com/Quorafind/Obsidian-Collapse-Node)而来,我在它的基础之上修改了相关代码,添加了新特性,并创建了这个新的插件。 16 | 再次表示感谢让我使用您的代码!!! 17 | 18 | **Obsidian Canvas Style Menu**让你可以通过画布菜单无缝修改画布卡片样式,并支持使用您自己的CSS片段进行样式菜单扩展。 19 | 20 | ## 使用 21 | 22 | **Obsidian Canvas Style Menu**自带几种简单的样式,您可以直接在样式菜单中使用,右键按钮就可以取消样式。你也可以使用您自己的css片段对默认的样式菜单进行扩展或者覆盖,只需要在插件设置页面添加您自己的菜单配置,以下是简单说明,详细文档后续会添加。 23 | 24 | 第一步,添加Menu config: 25 | 26 | ![](./assets/menu_config.jpg) 27 | 28 | **注意:默认添加的Menu Config是卡片的样式按钮,如果要为连接线添加样式按钮,只需在Menu Config中添加`cat: 'edge'`。 29 | 30 | ![](./assets/connection_line.jpg) 31 | 32 | 第二步,添加Sub Menu Config: 33 | **注意:Sub Menu Config无需添加`cat: 'edge'`**。 34 | 35 | ![](./assets/submenu_config.jpg) 36 | 37 | 第三步,添加您自己的css片段,其中每个样式的类名称需要跟您的菜单配置中的类名称对应上: 38 | 39 | ![](./assets/css_config.jpg) 40 | 41 | 第四步,恭喜,您可以在样式菜单中使用您添加的样式了! 42 | 43 | **注意:**如果您的按钮没有子菜单的话,那么按钮将会变成一个开关按钮,左键点击应用样式,右键点击取消样式。 44 | 45 | **Obsidian Canvas Style Menu**支持可选的obsidian默认的cssclasses样式方法,用户无需手写cssclasses也可实现cssclasses的效果,只需在你的Menu Config中添加`selector: 'cc'`。 46 | 47 | ![](./assets/cssclasses.jpg) 48 | 49 | **对于[Canvas Candy](https://tfthacker.com/canvas-candy)用户,现在你可以不用写cssclasses了,只需将常用的Canvas Candy的css classes添加到menu configs中即可方便使用。** 50 | 51 | **注意:Canvas Candy是收费产品,不包含在Canvas Style Menu中,如需使用请访问[Canvas Candy](https://tfthacker.com/canvas-candy)了解更多信息** 52 | 53 | ![](./assets/canvas_candy.jpg) 54 | 55 | **Obsidian Canvas Style Menu**支持添加自定义图标,在插件设置页面,拖动到最下面,输入图标名称和svg代码,然后点击左侧添加按钮。 56 | 57 | 如何获取svg图标? 58 | 以[Lucide]([Lucide | Lucide](https://lucide.dev/))图标为例,找到想要的图标,点击`Copy SVG`按钮,然后在插件设置页面粘贴即可。 59 | **注意,图标需要符合obsidian关于图标的要求 [Obsidian Icon design guidelines](https://docs.obsidian.md/Plugins/User+interface/Icons#Icon+design+guidelines)**。 60 | 61 | ![](./assets/custom_icon.jpg) 62 | 63 | ## 安装 64 | 65 | - 还没上架官方插件市场 66 | - 可以通过 [Brat](https://github.com/TfTHacker/obsidian42-brat) 插件安装 67 | - 手动安装 68 | 69 | 1. 在此 github 页面找到发布页面,然后点击 70 | 2. 下载最新发布的压缩文件 71 | 3. 解压缩,将解压后的文件夹复制到 obsidian 插件文件夹,确保其中有 main.js 和 manifest.json 文件 72 | 4. 重启 obsidian(不要同时重启,必须刷新插件列表),在设置界面启用 73 | 插件 74 | 5. 搞定! 75 | 76 | ## 向我表达感谢 77 | 78 | 如果您觉得 Obsidian-Canvas-Style-Menu 很有用,帮到了您,您可以考虑给我买杯咖啡:[https://www.buymeacoffee.com/michaellw](https://www.buymeacoffee.com/michaellw). 79 | 80 | -------------------------------------------------------------------------------- /assets/canvas_candy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/canvas_candy.jpg -------------------------------------------------------------------------------- /assets/connection_line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/connection_line.jpg -------------------------------------------------------------------------------- /assets/css_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/css_config.jpg -------------------------------------------------------------------------------- /assets/cssclasses.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/cssclasses.jpg -------------------------------------------------------------------------------- /assets/custom_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/custom_icon.jpg -------------------------------------------------------------------------------- /assets/menu_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/menu_config.jpg -------------------------------------------------------------------------------- /assets/submenu_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaellw/Obsidian-Canvas-Style-Menu/29d4ce7c248909eddc4ebfa52b3b41171ee73fdc/assets/submenu_config.jpg -------------------------------------------------------------------------------- /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/main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins], 35 | format: 'cjs', 36 | 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-style-menu", 3 | "name": "Canvas Style Menu", 4 | "version": "0.0.6", 5 | "minAppVersion": "1.1.0", 6 | "description": "Customize canvas styles seamlessly through the canvas menu and support styles extension using CSS snippets.", 7 | "author": "Michaellw", 8 | "authorUrl": "https://github.com/Michaellw", 9 | "fundingUrl": { 10 | "Buy Me a Coffee": "https://www.buymeacoffee.com/michaellw" 11 | }, 12 | "isDesktopOnly": false 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvas-style-menu", 3 | "version": "0.0.6", 4 | "description": "Customize canvas styles seamlessly through the canvas menu and support styles extension using CSS snippets.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.14.47", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | }, 24 | "dependencies": { 25 | "monkey-around": "^2.3.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | '@types/lodash': 9 | specifier: ^4.14.197 10 | version: 4.14.197 11 | lodash: 12 | specifier: ^4.17.21 13 | version: 4.17.21 14 | monkey-around: 15 | specifier: ^2.3.0 16 | version: 2.3.0 17 | 18 | devDependencies: 19 | '@types/node': 20 | specifier: ^16.11.6 21 | version: 16.18.46 22 | '@typescript-eslint/eslint-plugin': 23 | specifier: 5.29.0 24 | version: 5.29.0(@typescript-eslint/parser@5.29.0)(eslint@8.48.0)(typescript@4.7.4) 25 | '@typescript-eslint/parser': 26 | specifier: 5.29.0 27 | version: 5.29.0(eslint@8.48.0)(typescript@4.7.4) 28 | builtin-modules: 29 | specifier: 3.3.0 30 | version: 3.3.0 31 | esbuild: 32 | specifier: 0.14.47 33 | version: 0.14.47 34 | obsidian: 35 | specifier: latest 36 | version: 1.4.4(@codemirror/state@6.2.1)(@codemirror/view@6.16.0) 37 | tslib: 38 | specifier: 2.4.0 39 | version: 2.4.0 40 | typescript: 41 | specifier: 4.7.4 42 | version: 4.7.4 43 | 44 | packages: 45 | 46 | /@aashutoshrathi/word-wrap@1.2.6: 47 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 48 | engines: {node: '>=0.10.0'} 49 | dev: true 50 | 51 | /@codemirror/state@6.2.1: 52 | resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==} 53 | dev: true 54 | 55 | /@codemirror/view@6.16.0: 56 | resolution: {integrity: sha512-1Z2HkvkC3KR/oEZVuW9Ivmp8TWLzGEd8T8TA04TTwPvqogfkHBdYSlflytDOqmkUxM2d1ywTg7X2dU5mC+SXvg==} 57 | dependencies: 58 | '@codemirror/state': 6.2.1 59 | style-mod: 4.1.0 60 | w3c-keyname: 2.2.8 61 | dev: true 62 | 63 | /@eslint-community/eslint-utils@4.4.0(eslint@8.48.0): 64 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 65 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 66 | peerDependencies: 67 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 68 | dependencies: 69 | eslint: 8.48.0 70 | eslint-visitor-keys: 3.4.3 71 | dev: true 72 | 73 | /@eslint-community/regexpp@4.8.0: 74 | resolution: {integrity: sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==} 75 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 76 | dev: true 77 | 78 | /@eslint/eslintrc@2.1.2: 79 | resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} 80 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 81 | dependencies: 82 | ajv: 6.12.6 83 | debug: 4.3.4 84 | espree: 9.6.1 85 | globals: 13.21.0 86 | ignore: 5.2.4 87 | import-fresh: 3.3.0 88 | js-yaml: 4.1.0 89 | minimatch: 3.1.2 90 | strip-json-comments: 3.1.1 91 | transitivePeerDependencies: 92 | - supports-color 93 | dev: true 94 | 95 | /@eslint/js@8.48.0: 96 | resolution: {integrity: sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==} 97 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 98 | dev: true 99 | 100 | /@humanwhocodes/config-array@0.11.10: 101 | resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} 102 | engines: {node: '>=10.10.0'} 103 | dependencies: 104 | '@humanwhocodes/object-schema': 1.2.1 105 | debug: 4.3.4 106 | minimatch: 3.1.2 107 | transitivePeerDependencies: 108 | - supports-color 109 | dev: true 110 | 111 | /@humanwhocodes/module-importer@1.0.1: 112 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 113 | engines: {node: '>=12.22'} 114 | dev: true 115 | 116 | /@humanwhocodes/object-schema@1.2.1: 117 | resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} 118 | dev: true 119 | 120 | /@nodelib/fs.scandir@2.1.5: 121 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 122 | engines: {node: '>= 8'} 123 | dependencies: 124 | '@nodelib/fs.stat': 2.0.5 125 | run-parallel: 1.2.0 126 | dev: true 127 | 128 | /@nodelib/fs.stat@2.0.5: 129 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 130 | engines: {node: '>= 8'} 131 | dev: true 132 | 133 | /@nodelib/fs.walk@1.2.8: 134 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 135 | engines: {node: '>= 8'} 136 | dependencies: 137 | '@nodelib/fs.scandir': 2.1.5 138 | fastq: 1.15.0 139 | dev: true 140 | 141 | /@types/codemirror@5.60.8: 142 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==} 143 | dependencies: 144 | '@types/tern': 0.23.4 145 | dev: true 146 | 147 | /@types/estree@1.0.1: 148 | resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} 149 | dev: true 150 | 151 | /@types/json-schema@7.0.12: 152 | resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} 153 | dev: true 154 | 155 | /@types/lodash@4.14.197: 156 | resolution: {integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==} 157 | dev: false 158 | 159 | /@types/node@16.18.46: 160 | resolution: {integrity: sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==} 161 | dev: true 162 | 163 | /@types/tern@0.23.4: 164 | resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==} 165 | dependencies: 166 | '@types/estree': 1.0.1 167 | dev: true 168 | 169 | /@typescript-eslint/eslint-plugin@5.29.0(@typescript-eslint/parser@5.29.0)(eslint@8.48.0)(typescript@4.7.4): 170 | resolution: {integrity: sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==} 171 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 172 | peerDependencies: 173 | '@typescript-eslint/parser': ^5.0.0 174 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 175 | typescript: '*' 176 | peerDependenciesMeta: 177 | typescript: 178 | optional: true 179 | dependencies: 180 | '@typescript-eslint/parser': 5.29.0(eslint@8.48.0)(typescript@4.7.4) 181 | '@typescript-eslint/scope-manager': 5.29.0 182 | '@typescript-eslint/type-utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4) 183 | '@typescript-eslint/utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4) 184 | debug: 4.3.4 185 | eslint: 8.48.0 186 | functional-red-black-tree: 1.0.1 187 | ignore: 5.2.4 188 | regexpp: 3.2.0 189 | semver: 7.5.4 190 | tsutils: 3.21.0(typescript@4.7.4) 191 | typescript: 4.7.4 192 | transitivePeerDependencies: 193 | - supports-color 194 | dev: true 195 | 196 | /@typescript-eslint/parser@5.29.0(eslint@8.48.0)(typescript@4.7.4): 197 | resolution: {integrity: sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==} 198 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 199 | peerDependencies: 200 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 201 | typescript: '*' 202 | peerDependenciesMeta: 203 | typescript: 204 | optional: true 205 | dependencies: 206 | '@typescript-eslint/scope-manager': 5.29.0 207 | '@typescript-eslint/types': 5.29.0 208 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4) 209 | debug: 4.3.4 210 | eslint: 8.48.0 211 | typescript: 4.7.4 212 | transitivePeerDependencies: 213 | - supports-color 214 | dev: true 215 | 216 | /@typescript-eslint/scope-manager@5.29.0: 217 | resolution: {integrity: sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==} 218 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 219 | dependencies: 220 | '@typescript-eslint/types': 5.29.0 221 | '@typescript-eslint/visitor-keys': 5.29.0 222 | dev: true 223 | 224 | /@typescript-eslint/type-utils@5.29.0(eslint@8.48.0)(typescript@4.7.4): 225 | resolution: {integrity: sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==} 226 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 227 | peerDependencies: 228 | eslint: '*' 229 | typescript: '*' 230 | peerDependenciesMeta: 231 | typescript: 232 | optional: true 233 | dependencies: 234 | '@typescript-eslint/utils': 5.29.0(eslint@8.48.0)(typescript@4.7.4) 235 | debug: 4.3.4 236 | eslint: 8.48.0 237 | tsutils: 3.21.0(typescript@4.7.4) 238 | typescript: 4.7.4 239 | transitivePeerDependencies: 240 | - supports-color 241 | dev: true 242 | 243 | /@typescript-eslint/types@5.29.0: 244 | resolution: {integrity: sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==} 245 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 246 | dev: true 247 | 248 | /@typescript-eslint/typescript-estree@5.29.0(typescript@4.7.4): 249 | resolution: {integrity: sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==} 250 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 251 | peerDependencies: 252 | typescript: '*' 253 | peerDependenciesMeta: 254 | typescript: 255 | optional: true 256 | dependencies: 257 | '@typescript-eslint/types': 5.29.0 258 | '@typescript-eslint/visitor-keys': 5.29.0 259 | debug: 4.3.4 260 | globby: 11.1.0 261 | is-glob: 4.0.3 262 | semver: 7.5.4 263 | tsutils: 3.21.0(typescript@4.7.4) 264 | typescript: 4.7.4 265 | transitivePeerDependencies: 266 | - supports-color 267 | dev: true 268 | 269 | /@typescript-eslint/utils@5.29.0(eslint@8.48.0)(typescript@4.7.4): 270 | resolution: {integrity: sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==} 271 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 272 | peerDependencies: 273 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 274 | dependencies: 275 | '@types/json-schema': 7.0.12 276 | '@typescript-eslint/scope-manager': 5.29.0 277 | '@typescript-eslint/types': 5.29.0 278 | '@typescript-eslint/typescript-estree': 5.29.0(typescript@4.7.4) 279 | eslint: 8.48.0 280 | eslint-scope: 5.1.1 281 | eslint-utils: 3.0.0(eslint@8.48.0) 282 | transitivePeerDependencies: 283 | - supports-color 284 | - typescript 285 | dev: true 286 | 287 | /@typescript-eslint/visitor-keys@5.29.0: 288 | resolution: {integrity: sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==} 289 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 290 | dependencies: 291 | '@typescript-eslint/types': 5.29.0 292 | eslint-visitor-keys: 3.4.3 293 | dev: true 294 | 295 | /acorn-jsx@5.3.2(acorn@8.10.0): 296 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 297 | peerDependencies: 298 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 299 | dependencies: 300 | acorn: 8.10.0 301 | dev: true 302 | 303 | /acorn@8.10.0: 304 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} 305 | engines: {node: '>=0.4.0'} 306 | hasBin: true 307 | dev: true 308 | 309 | /ajv@6.12.6: 310 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 311 | dependencies: 312 | fast-deep-equal: 3.1.3 313 | fast-json-stable-stringify: 2.1.0 314 | json-schema-traverse: 0.4.1 315 | uri-js: 4.4.1 316 | dev: true 317 | 318 | /ansi-regex@5.0.1: 319 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 320 | engines: {node: '>=8'} 321 | dev: true 322 | 323 | /ansi-styles@4.3.0: 324 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 325 | engines: {node: '>=8'} 326 | dependencies: 327 | color-convert: 2.0.1 328 | dev: true 329 | 330 | /argparse@2.0.1: 331 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 332 | dev: true 333 | 334 | /array-union@2.1.0: 335 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 336 | engines: {node: '>=8'} 337 | dev: true 338 | 339 | /balanced-match@1.0.2: 340 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 341 | dev: true 342 | 343 | /brace-expansion@1.1.11: 344 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 345 | dependencies: 346 | balanced-match: 1.0.2 347 | concat-map: 0.0.1 348 | dev: true 349 | 350 | /braces@3.0.2: 351 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 352 | engines: {node: '>=8'} 353 | dependencies: 354 | fill-range: 7.0.1 355 | dev: true 356 | 357 | /builtin-modules@3.3.0: 358 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 359 | engines: {node: '>=6'} 360 | dev: true 361 | 362 | /callsites@3.1.0: 363 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 364 | engines: {node: '>=6'} 365 | dev: true 366 | 367 | /chalk@4.1.2: 368 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 369 | engines: {node: '>=10'} 370 | dependencies: 371 | ansi-styles: 4.3.0 372 | supports-color: 7.2.0 373 | dev: true 374 | 375 | /color-convert@2.0.1: 376 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 377 | engines: {node: '>=7.0.0'} 378 | dependencies: 379 | color-name: 1.1.4 380 | dev: true 381 | 382 | /color-name@1.1.4: 383 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 384 | dev: true 385 | 386 | /concat-map@0.0.1: 387 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 388 | dev: true 389 | 390 | /cross-spawn@7.0.3: 391 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 392 | engines: {node: '>= 8'} 393 | dependencies: 394 | path-key: 3.1.1 395 | shebang-command: 2.0.0 396 | which: 2.0.2 397 | dev: true 398 | 399 | /debug@4.3.4: 400 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 401 | engines: {node: '>=6.0'} 402 | peerDependencies: 403 | supports-color: '*' 404 | peerDependenciesMeta: 405 | supports-color: 406 | optional: true 407 | dependencies: 408 | ms: 2.1.2 409 | dev: true 410 | 411 | /deep-is@0.1.4: 412 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 413 | dev: true 414 | 415 | /dir-glob@3.0.1: 416 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 417 | engines: {node: '>=8'} 418 | dependencies: 419 | path-type: 4.0.0 420 | dev: true 421 | 422 | /doctrine@3.0.0: 423 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 424 | engines: {node: '>=6.0.0'} 425 | dependencies: 426 | esutils: 2.0.3 427 | dev: true 428 | 429 | /esbuild-android-64@0.14.47: 430 | resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} 431 | engines: {node: '>=12'} 432 | cpu: [x64] 433 | os: [android] 434 | requiresBuild: true 435 | dev: true 436 | optional: true 437 | 438 | /esbuild-android-arm64@0.14.47: 439 | resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} 440 | engines: {node: '>=12'} 441 | cpu: [arm64] 442 | os: [android] 443 | requiresBuild: true 444 | dev: true 445 | optional: true 446 | 447 | /esbuild-darwin-64@0.14.47: 448 | resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} 449 | engines: {node: '>=12'} 450 | cpu: [x64] 451 | os: [darwin] 452 | requiresBuild: true 453 | dev: true 454 | optional: true 455 | 456 | /esbuild-darwin-arm64@0.14.47: 457 | resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} 458 | engines: {node: '>=12'} 459 | cpu: [arm64] 460 | os: [darwin] 461 | requiresBuild: true 462 | dev: true 463 | optional: true 464 | 465 | /esbuild-freebsd-64@0.14.47: 466 | resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} 467 | engines: {node: '>=12'} 468 | cpu: [x64] 469 | os: [freebsd] 470 | requiresBuild: true 471 | dev: true 472 | optional: true 473 | 474 | /esbuild-freebsd-arm64@0.14.47: 475 | resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} 476 | engines: {node: '>=12'} 477 | cpu: [arm64] 478 | os: [freebsd] 479 | requiresBuild: true 480 | dev: true 481 | optional: true 482 | 483 | /esbuild-linux-32@0.14.47: 484 | resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} 485 | engines: {node: '>=12'} 486 | cpu: [ia32] 487 | os: [linux] 488 | requiresBuild: true 489 | dev: true 490 | optional: true 491 | 492 | /esbuild-linux-64@0.14.47: 493 | resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} 494 | engines: {node: '>=12'} 495 | cpu: [x64] 496 | os: [linux] 497 | requiresBuild: true 498 | dev: true 499 | optional: true 500 | 501 | /esbuild-linux-arm64@0.14.47: 502 | resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} 503 | engines: {node: '>=12'} 504 | cpu: [arm64] 505 | os: [linux] 506 | requiresBuild: true 507 | dev: true 508 | optional: true 509 | 510 | /esbuild-linux-arm@0.14.47: 511 | resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} 512 | engines: {node: '>=12'} 513 | cpu: [arm] 514 | os: [linux] 515 | requiresBuild: true 516 | dev: true 517 | optional: true 518 | 519 | /esbuild-linux-mips64le@0.14.47: 520 | resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} 521 | engines: {node: '>=12'} 522 | cpu: [mips64el] 523 | os: [linux] 524 | requiresBuild: true 525 | dev: true 526 | optional: true 527 | 528 | /esbuild-linux-ppc64le@0.14.47: 529 | resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} 530 | engines: {node: '>=12'} 531 | cpu: [ppc64] 532 | os: [linux] 533 | requiresBuild: true 534 | dev: true 535 | optional: true 536 | 537 | /esbuild-linux-riscv64@0.14.47: 538 | resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} 539 | engines: {node: '>=12'} 540 | cpu: [riscv64] 541 | os: [linux] 542 | requiresBuild: true 543 | dev: true 544 | optional: true 545 | 546 | /esbuild-linux-s390x@0.14.47: 547 | resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} 548 | engines: {node: '>=12'} 549 | cpu: [s390x] 550 | os: [linux] 551 | requiresBuild: true 552 | dev: true 553 | optional: true 554 | 555 | /esbuild-netbsd-64@0.14.47: 556 | resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} 557 | engines: {node: '>=12'} 558 | cpu: [x64] 559 | os: [netbsd] 560 | requiresBuild: true 561 | dev: true 562 | optional: true 563 | 564 | /esbuild-openbsd-64@0.14.47: 565 | resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} 566 | engines: {node: '>=12'} 567 | cpu: [x64] 568 | os: [openbsd] 569 | requiresBuild: true 570 | dev: true 571 | optional: true 572 | 573 | /esbuild-sunos-64@0.14.47: 574 | resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} 575 | engines: {node: '>=12'} 576 | cpu: [x64] 577 | os: [sunos] 578 | requiresBuild: true 579 | dev: true 580 | optional: true 581 | 582 | /esbuild-windows-32@0.14.47: 583 | resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} 584 | engines: {node: '>=12'} 585 | cpu: [ia32] 586 | os: [win32] 587 | requiresBuild: true 588 | dev: true 589 | optional: true 590 | 591 | /esbuild-windows-64@0.14.47: 592 | resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} 593 | engines: {node: '>=12'} 594 | cpu: [x64] 595 | os: [win32] 596 | requiresBuild: true 597 | dev: true 598 | optional: true 599 | 600 | /esbuild-windows-arm64@0.14.47: 601 | resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} 602 | engines: {node: '>=12'} 603 | cpu: [arm64] 604 | os: [win32] 605 | requiresBuild: true 606 | dev: true 607 | optional: true 608 | 609 | /esbuild@0.14.47: 610 | resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} 611 | engines: {node: '>=12'} 612 | hasBin: true 613 | requiresBuild: true 614 | optionalDependencies: 615 | esbuild-android-64: 0.14.47 616 | esbuild-android-arm64: 0.14.47 617 | esbuild-darwin-64: 0.14.47 618 | esbuild-darwin-arm64: 0.14.47 619 | esbuild-freebsd-64: 0.14.47 620 | esbuild-freebsd-arm64: 0.14.47 621 | esbuild-linux-32: 0.14.47 622 | esbuild-linux-64: 0.14.47 623 | esbuild-linux-arm: 0.14.47 624 | esbuild-linux-arm64: 0.14.47 625 | esbuild-linux-mips64le: 0.14.47 626 | esbuild-linux-ppc64le: 0.14.47 627 | esbuild-linux-riscv64: 0.14.47 628 | esbuild-linux-s390x: 0.14.47 629 | esbuild-netbsd-64: 0.14.47 630 | esbuild-openbsd-64: 0.14.47 631 | esbuild-sunos-64: 0.14.47 632 | esbuild-windows-32: 0.14.47 633 | esbuild-windows-64: 0.14.47 634 | esbuild-windows-arm64: 0.14.47 635 | dev: true 636 | 637 | /escape-string-regexp@4.0.0: 638 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 639 | engines: {node: '>=10'} 640 | dev: true 641 | 642 | /eslint-scope@5.1.1: 643 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 644 | engines: {node: '>=8.0.0'} 645 | dependencies: 646 | esrecurse: 4.3.0 647 | estraverse: 4.3.0 648 | dev: true 649 | 650 | /eslint-scope@7.2.2: 651 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 652 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 653 | dependencies: 654 | esrecurse: 4.3.0 655 | estraverse: 5.3.0 656 | dev: true 657 | 658 | /eslint-utils@3.0.0(eslint@8.48.0): 659 | resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} 660 | engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} 661 | peerDependencies: 662 | eslint: '>=5' 663 | dependencies: 664 | eslint: 8.48.0 665 | eslint-visitor-keys: 2.1.0 666 | dev: true 667 | 668 | /eslint-visitor-keys@2.1.0: 669 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} 670 | engines: {node: '>=10'} 671 | dev: true 672 | 673 | /eslint-visitor-keys@3.4.3: 674 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 675 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 676 | dev: true 677 | 678 | /eslint@8.48.0: 679 | resolution: {integrity: sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==} 680 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 681 | hasBin: true 682 | dependencies: 683 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) 684 | '@eslint-community/regexpp': 4.8.0 685 | '@eslint/eslintrc': 2.1.2 686 | '@eslint/js': 8.48.0 687 | '@humanwhocodes/config-array': 0.11.10 688 | '@humanwhocodes/module-importer': 1.0.1 689 | '@nodelib/fs.walk': 1.2.8 690 | ajv: 6.12.6 691 | chalk: 4.1.2 692 | cross-spawn: 7.0.3 693 | debug: 4.3.4 694 | doctrine: 3.0.0 695 | escape-string-regexp: 4.0.0 696 | eslint-scope: 7.2.2 697 | eslint-visitor-keys: 3.4.3 698 | espree: 9.6.1 699 | esquery: 1.5.0 700 | esutils: 2.0.3 701 | fast-deep-equal: 3.1.3 702 | file-entry-cache: 6.0.1 703 | find-up: 5.0.0 704 | glob-parent: 6.0.2 705 | globals: 13.21.0 706 | graphemer: 1.4.0 707 | ignore: 5.2.4 708 | imurmurhash: 0.1.4 709 | is-glob: 4.0.3 710 | is-path-inside: 3.0.3 711 | js-yaml: 4.1.0 712 | json-stable-stringify-without-jsonify: 1.0.1 713 | levn: 0.4.1 714 | lodash.merge: 4.6.2 715 | minimatch: 3.1.2 716 | natural-compare: 1.4.0 717 | optionator: 0.9.3 718 | strip-ansi: 6.0.1 719 | text-table: 0.2.0 720 | transitivePeerDependencies: 721 | - supports-color 722 | dev: true 723 | 724 | /espree@9.6.1: 725 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 726 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 727 | dependencies: 728 | acorn: 8.10.0 729 | acorn-jsx: 5.3.2(acorn@8.10.0) 730 | eslint-visitor-keys: 3.4.3 731 | dev: true 732 | 733 | /esquery@1.5.0: 734 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 735 | engines: {node: '>=0.10'} 736 | dependencies: 737 | estraverse: 5.3.0 738 | dev: true 739 | 740 | /esrecurse@4.3.0: 741 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 742 | engines: {node: '>=4.0'} 743 | dependencies: 744 | estraverse: 5.3.0 745 | dev: true 746 | 747 | /estraverse@4.3.0: 748 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 749 | engines: {node: '>=4.0'} 750 | dev: true 751 | 752 | /estraverse@5.3.0: 753 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 754 | engines: {node: '>=4.0'} 755 | dev: true 756 | 757 | /esutils@2.0.3: 758 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 759 | engines: {node: '>=0.10.0'} 760 | dev: true 761 | 762 | /fast-deep-equal@3.1.3: 763 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 764 | dev: true 765 | 766 | /fast-glob@3.3.1: 767 | resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} 768 | engines: {node: '>=8.6.0'} 769 | dependencies: 770 | '@nodelib/fs.stat': 2.0.5 771 | '@nodelib/fs.walk': 1.2.8 772 | glob-parent: 5.1.2 773 | merge2: 1.4.1 774 | micromatch: 4.0.5 775 | dev: true 776 | 777 | /fast-json-stable-stringify@2.1.0: 778 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 779 | dev: true 780 | 781 | /fast-levenshtein@2.0.6: 782 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 783 | dev: true 784 | 785 | /fastq@1.15.0: 786 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 787 | dependencies: 788 | reusify: 1.0.4 789 | dev: true 790 | 791 | /file-entry-cache@6.0.1: 792 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 793 | engines: {node: ^10.12.0 || >=12.0.0} 794 | dependencies: 795 | flat-cache: 3.1.0 796 | dev: true 797 | 798 | /fill-range@7.0.1: 799 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 800 | engines: {node: '>=8'} 801 | dependencies: 802 | to-regex-range: 5.0.1 803 | dev: true 804 | 805 | /find-up@5.0.0: 806 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 807 | engines: {node: '>=10'} 808 | dependencies: 809 | locate-path: 6.0.0 810 | path-exists: 4.0.0 811 | dev: true 812 | 813 | /flat-cache@3.1.0: 814 | resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} 815 | engines: {node: '>=12.0.0'} 816 | dependencies: 817 | flatted: 3.2.7 818 | keyv: 4.5.3 819 | rimraf: 3.0.2 820 | dev: true 821 | 822 | /flatted@3.2.7: 823 | resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} 824 | dev: true 825 | 826 | /fs.realpath@1.0.0: 827 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 828 | dev: true 829 | 830 | /functional-red-black-tree@1.0.1: 831 | resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} 832 | dev: true 833 | 834 | /glob-parent@5.1.2: 835 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 836 | engines: {node: '>= 6'} 837 | dependencies: 838 | is-glob: 4.0.3 839 | dev: true 840 | 841 | /glob-parent@6.0.2: 842 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 843 | engines: {node: '>=10.13.0'} 844 | dependencies: 845 | is-glob: 4.0.3 846 | dev: true 847 | 848 | /glob@7.2.3: 849 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 850 | dependencies: 851 | fs.realpath: 1.0.0 852 | inflight: 1.0.6 853 | inherits: 2.0.4 854 | minimatch: 3.1.2 855 | once: 1.4.0 856 | path-is-absolute: 1.0.1 857 | dev: true 858 | 859 | /globals@13.21.0: 860 | resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} 861 | engines: {node: '>=8'} 862 | dependencies: 863 | type-fest: 0.20.2 864 | dev: true 865 | 866 | /globby@11.1.0: 867 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 868 | engines: {node: '>=10'} 869 | dependencies: 870 | array-union: 2.1.0 871 | dir-glob: 3.0.1 872 | fast-glob: 3.3.1 873 | ignore: 5.2.4 874 | merge2: 1.4.1 875 | slash: 3.0.0 876 | dev: true 877 | 878 | /graphemer@1.4.0: 879 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 880 | dev: true 881 | 882 | /has-flag@4.0.0: 883 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 884 | engines: {node: '>=8'} 885 | dev: true 886 | 887 | /ignore@5.2.4: 888 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 889 | engines: {node: '>= 4'} 890 | dev: true 891 | 892 | /import-fresh@3.3.0: 893 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 894 | engines: {node: '>=6'} 895 | dependencies: 896 | parent-module: 1.0.1 897 | resolve-from: 4.0.0 898 | dev: true 899 | 900 | /imurmurhash@0.1.4: 901 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 902 | engines: {node: '>=0.8.19'} 903 | dev: true 904 | 905 | /inflight@1.0.6: 906 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 907 | dependencies: 908 | once: 1.4.0 909 | wrappy: 1.0.2 910 | dev: true 911 | 912 | /inherits@2.0.4: 913 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 914 | dev: true 915 | 916 | /is-extglob@2.1.1: 917 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 918 | engines: {node: '>=0.10.0'} 919 | dev: true 920 | 921 | /is-glob@4.0.3: 922 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 923 | engines: {node: '>=0.10.0'} 924 | dependencies: 925 | is-extglob: 2.1.1 926 | dev: true 927 | 928 | /is-number@7.0.0: 929 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 930 | engines: {node: '>=0.12.0'} 931 | dev: true 932 | 933 | /is-path-inside@3.0.3: 934 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 935 | engines: {node: '>=8'} 936 | dev: true 937 | 938 | /isexe@2.0.0: 939 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 940 | dev: true 941 | 942 | /js-yaml@4.1.0: 943 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 944 | hasBin: true 945 | dependencies: 946 | argparse: 2.0.1 947 | dev: true 948 | 949 | /json-buffer@3.0.1: 950 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 951 | dev: true 952 | 953 | /json-schema-traverse@0.4.1: 954 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 955 | dev: true 956 | 957 | /json-stable-stringify-without-jsonify@1.0.1: 958 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 959 | dev: true 960 | 961 | /keyv@4.5.3: 962 | resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} 963 | dependencies: 964 | json-buffer: 3.0.1 965 | dev: true 966 | 967 | /levn@0.4.1: 968 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 969 | engines: {node: '>= 0.8.0'} 970 | dependencies: 971 | prelude-ls: 1.2.1 972 | type-check: 0.4.0 973 | dev: true 974 | 975 | /locate-path@6.0.0: 976 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 977 | engines: {node: '>=10'} 978 | dependencies: 979 | p-locate: 5.0.0 980 | dev: true 981 | 982 | /lodash.merge@4.6.2: 983 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 984 | dev: true 985 | 986 | /lodash@4.17.21: 987 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 988 | dev: false 989 | 990 | /lru-cache@6.0.0: 991 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 992 | engines: {node: '>=10'} 993 | dependencies: 994 | yallist: 4.0.0 995 | dev: true 996 | 997 | /merge2@1.4.1: 998 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 999 | engines: {node: '>= 8'} 1000 | dev: true 1001 | 1002 | /micromatch@4.0.5: 1003 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1004 | engines: {node: '>=8.6'} 1005 | dependencies: 1006 | braces: 3.0.2 1007 | picomatch: 2.3.1 1008 | dev: true 1009 | 1010 | /minimatch@3.1.2: 1011 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1012 | dependencies: 1013 | brace-expansion: 1.1.11 1014 | dev: true 1015 | 1016 | /moment@2.29.4: 1017 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 1018 | dev: true 1019 | 1020 | /monkey-around@2.3.0: 1021 | resolution: {integrity: sha512-QWcCUWjqE/MCk9cXlSKZ1Qc486LD439xw/Ak8Nt6l2PuL9+yrc9TJakt7OHDuOqPRYY4nTWBAEFKn32PE/SfXA==} 1022 | dev: false 1023 | 1024 | /ms@2.1.2: 1025 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1026 | dev: true 1027 | 1028 | /natural-compare@1.4.0: 1029 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1030 | dev: true 1031 | 1032 | /obsidian@1.4.4(@codemirror/state@6.2.1)(@codemirror/view@6.16.0): 1033 | resolution: {integrity: sha512-q2V5GNT/M40uYOENdVw5kovPSoaO6vppiiyBCkIqWgKp4oN654jA/GQ0OaNBA7p5NdfS245QCeRgCFQ42wOZiw==} 1034 | peerDependencies: 1035 | '@codemirror/state': ^6.0.0 1036 | '@codemirror/view': ^6.0.0 1037 | dependencies: 1038 | '@codemirror/state': 6.2.1 1039 | '@codemirror/view': 6.16.0 1040 | '@types/codemirror': 5.60.8 1041 | moment: 2.29.4 1042 | dev: true 1043 | 1044 | /once@1.4.0: 1045 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1046 | dependencies: 1047 | wrappy: 1.0.2 1048 | dev: true 1049 | 1050 | /optionator@0.9.3: 1051 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1052 | engines: {node: '>= 0.8.0'} 1053 | dependencies: 1054 | '@aashutoshrathi/word-wrap': 1.2.6 1055 | deep-is: 0.1.4 1056 | fast-levenshtein: 2.0.6 1057 | levn: 0.4.1 1058 | prelude-ls: 1.2.1 1059 | type-check: 0.4.0 1060 | dev: true 1061 | 1062 | /p-limit@3.1.0: 1063 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1064 | engines: {node: '>=10'} 1065 | dependencies: 1066 | yocto-queue: 0.1.0 1067 | dev: true 1068 | 1069 | /p-locate@5.0.0: 1070 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1071 | engines: {node: '>=10'} 1072 | dependencies: 1073 | p-limit: 3.1.0 1074 | dev: true 1075 | 1076 | /parent-module@1.0.1: 1077 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1078 | engines: {node: '>=6'} 1079 | dependencies: 1080 | callsites: 3.1.0 1081 | dev: true 1082 | 1083 | /path-exists@4.0.0: 1084 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1085 | engines: {node: '>=8'} 1086 | dev: true 1087 | 1088 | /path-is-absolute@1.0.1: 1089 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1090 | engines: {node: '>=0.10.0'} 1091 | dev: true 1092 | 1093 | /path-key@3.1.1: 1094 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1095 | engines: {node: '>=8'} 1096 | dev: true 1097 | 1098 | /path-type@4.0.0: 1099 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1100 | engines: {node: '>=8'} 1101 | dev: true 1102 | 1103 | /picomatch@2.3.1: 1104 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1105 | engines: {node: '>=8.6'} 1106 | dev: true 1107 | 1108 | /prelude-ls@1.2.1: 1109 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1110 | engines: {node: '>= 0.8.0'} 1111 | dev: true 1112 | 1113 | /punycode@2.3.0: 1114 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 1115 | engines: {node: '>=6'} 1116 | dev: true 1117 | 1118 | /queue-microtask@1.2.3: 1119 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1120 | dev: true 1121 | 1122 | /regexpp@3.2.0: 1123 | resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} 1124 | engines: {node: '>=8'} 1125 | dev: true 1126 | 1127 | /resolve-from@4.0.0: 1128 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1129 | engines: {node: '>=4'} 1130 | dev: true 1131 | 1132 | /reusify@1.0.4: 1133 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1134 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1135 | dev: true 1136 | 1137 | /rimraf@3.0.2: 1138 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1139 | hasBin: true 1140 | dependencies: 1141 | glob: 7.2.3 1142 | dev: true 1143 | 1144 | /run-parallel@1.2.0: 1145 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1146 | dependencies: 1147 | queue-microtask: 1.2.3 1148 | dev: true 1149 | 1150 | /semver@7.5.4: 1151 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1152 | engines: {node: '>=10'} 1153 | hasBin: true 1154 | dependencies: 1155 | lru-cache: 6.0.0 1156 | dev: true 1157 | 1158 | /shebang-command@2.0.0: 1159 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1160 | engines: {node: '>=8'} 1161 | dependencies: 1162 | shebang-regex: 3.0.0 1163 | dev: true 1164 | 1165 | /shebang-regex@3.0.0: 1166 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1167 | engines: {node: '>=8'} 1168 | dev: true 1169 | 1170 | /slash@3.0.0: 1171 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1172 | engines: {node: '>=8'} 1173 | dev: true 1174 | 1175 | /strip-ansi@6.0.1: 1176 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1177 | engines: {node: '>=8'} 1178 | dependencies: 1179 | ansi-regex: 5.0.1 1180 | dev: true 1181 | 1182 | /strip-json-comments@3.1.1: 1183 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1184 | engines: {node: '>=8'} 1185 | dev: true 1186 | 1187 | /style-mod@4.1.0: 1188 | resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} 1189 | dev: true 1190 | 1191 | /supports-color@7.2.0: 1192 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1193 | engines: {node: '>=8'} 1194 | dependencies: 1195 | has-flag: 4.0.0 1196 | dev: true 1197 | 1198 | /text-table@0.2.0: 1199 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1200 | dev: true 1201 | 1202 | /to-regex-range@5.0.1: 1203 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1204 | engines: {node: '>=8.0'} 1205 | dependencies: 1206 | is-number: 7.0.0 1207 | dev: true 1208 | 1209 | /tslib@1.14.1: 1210 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 1211 | dev: true 1212 | 1213 | /tslib@2.4.0: 1214 | resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} 1215 | dev: true 1216 | 1217 | /tsutils@3.21.0(typescript@4.7.4): 1218 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 1219 | engines: {node: '>= 6'} 1220 | peerDependencies: 1221 | 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' 1222 | dependencies: 1223 | tslib: 1.14.1 1224 | typescript: 4.7.4 1225 | dev: true 1226 | 1227 | /type-check@0.4.0: 1228 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1229 | engines: {node: '>= 0.8.0'} 1230 | dependencies: 1231 | prelude-ls: 1.2.1 1232 | dev: true 1233 | 1234 | /type-fest@0.20.2: 1235 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1236 | engines: {node: '>=10'} 1237 | dev: true 1238 | 1239 | /typescript@4.7.4: 1240 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} 1241 | engines: {node: '>=4.2.0'} 1242 | hasBin: true 1243 | dev: true 1244 | 1245 | /uri-js@4.4.1: 1246 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1247 | dependencies: 1248 | punycode: 2.3.0 1249 | dev: true 1250 | 1251 | /w3c-keyname@2.2.8: 1252 | resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} 1253 | dev: true 1254 | 1255 | /which@2.0.2: 1256 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1257 | engines: {node: '>= 8'} 1258 | hasBin: true 1259 | dependencies: 1260 | isexe: 2.0.0 1261 | dev: true 1262 | 1263 | /wrappy@1.0.2: 1264 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1265 | dev: true 1266 | 1267 | /yallist@4.0.0: 1268 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1269 | dev: true 1270 | 1271 | /yocto-queue@0.1.0: 1272 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1273 | engines: {node: '>=10'} 1274 | dev: true 1275 | -------------------------------------------------------------------------------- /src/CanvasStyle.ts: -------------------------------------------------------------------------------- 1 | import { CanvasNode, Component } from "obsidian"; 2 | import { modifyClassOnElements } from "./utils"; 3 | 4 | export default class CanvasStyle extends Component { 5 | private node: CanvasNode; 6 | 7 | constructor(node: CanvasNode, menuConfig: MenuItem[]) { 8 | super(); 9 | 10 | this.node = node; 11 | this.types = menuConfig.map((menuItem) => menuItem.type); 12 | 13 | this.types.forEach((type) => { 14 | this[type] = this.node.unknownData[type] || ''; 15 | }) 16 | } 17 | 18 | onload() { 19 | this.updateNode(); 20 | } 21 | 22 | onunload() { 23 | super.onunload(); 24 | } 25 | 26 | setStyle(cat: string, selector: string, type: string, cssClass: string) { 27 | if (this.node.canvas.readonly) return; 28 | if (this[type] === cssClass) return; 29 | 30 | if (this[type] !== '' || this[type] !== undefined) { 31 | this.oldType = this[type]; 32 | } 33 | if (cssClass !== false || cssClass !== undefined) { 34 | this[type] = cssClass; 35 | this.node.unknownData[type] = cssClass; 36 | } 37 | 38 | this.updateNode(cat, selector, type); 39 | } 40 | 41 | async updateNode(cat: string, selector: string, type: string) { 42 | if (this.oldType) { 43 | if (cat === 'edge') { 44 | this.node.lineGroupEl.removeClass(this.oldType); 45 | this.node.lineEndGroupEl.removeClass(this.oldType); 46 | this.node.render() 47 | } else { 48 | try { 49 | this.node.nodeEl.removeClass(this.oldType); 50 | modifyClassOnElements('remove', this.node.contentEl, 'markdown-preview-view', this.oldType); 51 | } finally {} 52 | } 53 | } 54 | if (this[type]) { 55 | if (cat === 'edge') { 56 | this.node.lineGroupEl.addClass(this[type]); 57 | this.node.lineEndGroupEl.addClass(this[type]); 58 | this.node.render() 59 | } else { 60 | if (selector === 'cc') { 61 | await modifyClassOnElements('add', this.node.contentEl, 'markdown-preview-view', this[type]); 62 | } else this.node.nodeEl.addClass(this[type]); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { addIcon, Canvas, CanvasNode, CanvasView, Menu, Plugin, setIcon, setTooltip } from 'obsidian'; 2 | import { around } from 'monkey-around'; 3 | import CanvasStyle from "./CanvasStyle"; 4 | import { defaultConfigs, csIcons } from "./memuConfigs" 5 | import { 6 | handleMenu, 7 | handleMultiNodesViaNodes, 8 | handleNodeContextMenu, 9 | handleSelectionContextMenu, 10 | handleSingleNode, 11 | refreshAllCanvasView, 12 | getToggleMenuItemsClass, 13 | getItemProperty, 14 | modifyClassOnElements, 15 | createElbowPath, 16 | parseOldSettingsItems, 17 | sortByProperty, 18 | groupItemsByProperty, 19 | transformSubMenuItems 20 | } from "./utils"; 21 | import CanvasStyleMenuSettingTab from "./setting"; 22 | 23 | interface MyPluginSettings { 24 | currentConfig: string; 25 | configs: Config; 26 | savedConfigs: Config; 27 | } 28 | 29 | const DEFAULT_SETTINGS: MyPluginSettings = { 30 | currentConfig: "default", 31 | configs: defaultConfigs, 32 | savedConfigs: {}, 33 | } 34 | 35 | export const defaultSettings = JSON.parse(JSON.stringify(defaultConfigs)); 36 | export let savedSettings = {}; 37 | export let csIconList: string[] = []; 38 | 39 | export default class CanvasStyleMenuPlugin extends Plugin { 40 | settings: MyPluginSettings; 41 | nodePatched: boolean; 42 | groupPatched: boolean; 43 | edgePatched: boolean; 44 | 45 | async checkOldSettings() { 46 | // Migration of settings from version 0.0.5 and earlier. 47 | if (this.settings.menuItems || this.settings.subMenuItems || this.settings.customIcons) { 48 | this.settings.configs.custom1 = { 49 | name: "custom1", 50 | menuItems: [], 51 | subMenuItems: {}, 52 | customIcons: [], 53 | }; 54 | 55 | this.settings.savedConfigs.custom1 = { 56 | name: "custom1", 57 | menuItems: [], 58 | subMenuItems: {}, 59 | customIcons: [], 60 | }; 61 | 62 | const menuItemsNew = parseOldSettingsItems(this.settings.menuItems); 63 | if (menuItemsNew) { 64 | menuItemsNew.forEach(item => { 65 | item.name = item.title; 66 | item.cat = item.cat ? item.cat : ''; 67 | item.selector = item.selector ? item.selector : ''; 68 | item.enable = true; 69 | delete item.title; 70 | }); 71 | this.settings.configs.custom1.menuItems = sortByProperty(menuItemsNew, 'cat'); 72 | this.settings.savedConfigs.custom1.menuItems = JSON.parse(JSON.stringify(this.settings.configs.custom1.menuItems)); 73 | delete this.settings.menuItems; 74 | } 75 | 76 | const subMenuItemsNew = parseOldSettingsItems(this.settings.subMenuItems); 77 | if (subMenuItemsNew) { 78 | subMenuItemsNew.forEach(item => { 79 | item.name = item.title; 80 | item.selector = item.selector ? item.selector : ''; 81 | item.enable = true; 82 | delete item.title; 83 | }); 84 | this.settings.configs.custom1.subMenuItems = groupItemsByProperty(subMenuItemsNew, 'type'); 85 | this.settings.savedConfigs.custom1.subMenuItems = JSON.parse(JSON.stringify(this.settings.configs.custom1.subMenuItems)); 86 | delete this.settings.subMenuItems; 87 | } 88 | 89 | const typeValues = this.settings.configs.custom1.menuItems.map(item => item.type); 90 | typeValues.forEach(type => { 91 | if (!this.settings.configs.custom1.subMenuItems[type]) { 92 | this.settings.configs.custom1.subMenuItems[type] = []; 93 | this.settings.savedConfigs.custom1.subMenuItems[type] = []; 94 | } 95 | }); 96 | 97 | if (this.settings.customIcons) { 98 | this.settings.customIcons.forEach(icon => { 99 | icon.name = icon.iconName; 100 | delete icon.iconName; 101 | }); 102 | this.settings.configs.custom1.customIcons = this.settings.customIcons; 103 | this.settings.savedConfigs.custom1.customIcons = JSON.parse(JSON.stringify(this.settings.configs.custom1.customIcons)); 104 | delete this.settings.customIcons; 105 | } 106 | 107 | await this.saveSettings(); 108 | } 109 | } 110 | 111 | savedSettings() { 112 | savedSettings = JSON.parse(JSON.stringify(this.settings.savedConfigs)); 113 | } 114 | 115 | async onload() { 116 | await this.loadSettings(); 117 | await this.checkOldSettings(); 118 | this.savedSettings(); 119 | 120 | this.registerCanvasEvents(); 121 | this.registerCustomIcons(csIcons); 122 | 123 | this.patchCanvasMenu(); 124 | this.patchCanvasNode(); 125 | 126 | // This adds a settings tab so the user can configure various aspects of the plugin 127 | this.addSettingTab(new CanvasStyleMenuSettingTab(this.app, this)); 128 | } 129 | 130 | async loadSettings() { 131 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 132 | } 133 | 134 | async saveSettings() { 135 | await this.saveData(this.settings); 136 | } 137 | 138 | onunload() { 139 | console.log('unloading plugin'); 140 | refreshAllCanvasView(this.app); 141 | } 142 | 143 | registerCanvasEvents() { 144 | const menuConfig = this.settings.configs[this.settings.currentConfig].menuItems; 145 | const subMenuConfig = transformSubMenuItems(this.settings.configs[this.settings.currentConfig].subMenuItems); 146 | const menuTypes = menuConfig.map(item => item.type); 147 | const subMenuTypes = subMenuConfig.map(item => item.type); 148 | const toggleMenu = menuTypes.filter(type => !subMenuTypes.includes(type)); 149 | 150 | this.registerEvent(this.app.workspace.on("canvas-style-menu:patched-canvas", () => { 151 | refreshAllCanvasView(this.app); 152 | })); 153 | this.registerEvent(this.app.workspace.on("canvas-style-menu:patch-canvas-node", () => { 154 | this.patchCanvasNode(); 155 | refreshAllCanvasView(this.app); 156 | })); 157 | this.registerEvent(this.app.workspace.on("canvas:selection-menu", (menu, canvas) => { 158 | handleSelectionContextMenu(this, menu, canvas, menuConfig, subMenuConfig, toggleMenu); 159 | })); 160 | this.registerEvent(this.app.workspace.on("canvas:node-menu", (menu, node) => { 161 | handleNodeContextMenu(this, menu, node, menuConfig, subMenuConfig, toggleMenu); 162 | })); 163 | } 164 | 165 | registerCustomIcons(csIcons: CustomIcon[]) { 166 | let customIcons: CustomIcon[] = []; 167 | if (csIcons) { 168 | customIcons = csIcons.concat(this.settings.configs[this.settings.currentConfig].customIcons); 169 | } else { 170 | customIcons = this.settings.configs[this.settings.currentConfig].customIcons; 171 | } 172 | const jsonIcons = JSON.stringify(customIcons); 173 | const parsedIcons: CustomIcon[] = JSON.parse(jsonIcons); 174 | parsedIcons.forEach((icon: string) => { 175 | const name = icon.name; 176 | const svgContent = icon.svgContent.replace(/( width| height)="(\d+)"/g, '$1="100%"'); 177 | addIcon(name, svgContent); 178 | csIconList.push(name); 179 | }) 180 | } 181 | 182 | patchCanvasMenu(refreshed: boolean) { 183 | if (refreshed) { 184 | this.nodePatched = false; 185 | this.groupPatched = false; 186 | this.edgePatched = false; 187 | } 188 | 189 | let nodePatched = this.nodePatched; 190 | let groupPatched = this.groupPatched; 191 | let edgePatched = this.edgePatched; 192 | 193 | const patchMenu = () => { 194 | const canvasView = this.app.workspace.getLeavesOfType("canvas").first()?.view; 195 | if (!canvasView) return false; 196 | 197 | const menu = (canvasView as CanvasView)?.canvas.menu; 198 | if (!menu) return false; 199 | 200 | const selection = menu.selection; 201 | if (!selection) return false; 202 | 203 | const menuConfig = this.settings.configs[this.settings.currentConfig].menuItems 204 | const subMenuConfig = transformSubMenuItems(this.settings.configs[this.settings.currentConfig].subMenuItems); 205 | const menuTypes = menuConfig.map(item => item.type); 206 | const subMenuTypes = subMenuConfig.map(item => item.type); 207 | const toggleMenu = menuTypes.filter(type => !subMenuTypes.includes(type)); 208 | 209 | const menuUninstaller = around(menu.constructor.prototype, { 210 | render: (next: any) => 211 | function (...args: any) { 212 | const result = next.call(this, ...args); 213 | const currentSelection = this.canvas.selection; 214 | 215 | if (!nodePatched) { 216 | if (this.canvas.nodes.size > 0) { 217 | this.canvas.app.workspace.trigger("canvas-style-menu:patch-canvas-node"); 218 | nodePatched = true; 219 | } 220 | } 221 | 222 | const nodes = this.canvas.nodes.values(); 223 | if (!groupPatched) { 224 | for (const node of nodes) { 225 | if (node?.unknownData.type === "group") { 226 | this.canvas.app.workspace.trigger("canvas-style-menu:patch-canvas-node"); 227 | groupPatched = true; 228 | break; 229 | } 230 | } 231 | } 232 | 233 | const edge = this.canvas.edges.values().next().value; 234 | if (!edgePatched) { 235 | if (this.canvas.edges.size > 0 && edge?.unknownData?.id) { 236 | this.canvas.app.workspace.trigger("canvas-style-menu:patch-canvas-node"); 237 | edgePatched = true; 238 | } 239 | } 240 | 241 | const menuClasses = menuConfig.map(item => item.class) 242 | const oldMenuItems = this.menuEl.querySelectorAll('.clickable-icon[class$=-menu-item]'); 243 | oldMenuItems.forEach(item => { 244 | const itemClass = item.className.match(/^(.+?)(?=-menu-item)/)[1]; 245 | if (!menuClasses.includes(itemClass)) { 246 | item.remove() 247 | } 248 | }) 249 | 250 | const createMenuButton = (category: string, cssClass: string, tooltip: string, icon: string) => { 251 | const currentSelectionArray = Array.from(currentSelection); 252 | const allFalse = currentSelectionArray.every((value: number) => { 253 | return !value.nodeEl; 254 | }); 255 | if (allFalse) { 256 | if (category === 'edge') { 257 | if (this.menuEl.querySelector(`.${cssClass}-menu-item`)) return result; 258 | const buttonEl = createEl("button", `clickable-icon ${cssClass}-menu-item`); 259 | setTooltip(buttonEl, tooltip, { 260 | placement: "top", 261 | }); 262 | setIcon(buttonEl, icon); 263 | return buttonEl; 264 | } 265 | } else { 266 | if (category !== 'edge') { 267 | if (this.menuEl.querySelector(`.${cssClass}-menu-item`)) return result; 268 | const buttonEl = createEl("button", `clickable-icon ${cssClass}-menu-item`); 269 | setTooltip(buttonEl, tooltip, { 270 | placement: "top", 271 | }); 272 | setIcon(buttonEl, icon); 273 | return buttonEl; 274 | } 275 | } 276 | }; 277 | 278 | const handleButtonClick = (buttonEl: HTMLElement, clickHandler: () => void) => { 279 | const pos = buttonEl.getBoundingClientRect(); 280 | if (!buttonEl.hasClass("has-active-menu")) { 281 | buttonEl.toggleClass("has-active-menu", true); 282 | const menu = new Menu(); 283 | const containingNodes = this.canvas.getContainingNodes(this.selection.bbox); 284 | 285 | clickHandler(menu, containingNodes); 286 | 287 | buttonEl.toggleClass("has-active-menu", false); 288 | menu.setParentElement(this.menuEl).showAtPosition({ 289 | x: pos.x, 290 | y: pos.bottom, 291 | width: pos.width, 292 | overlap: true 293 | }); 294 | } 295 | }; 296 | 297 | const createAndSetupMenuButton = (category: string, cssClass: string, toggleMenu: string[], tooltip: string, icon: string, clickHandler: (menu: Menu, containingNodes: any[]) => void) => { 298 | const buttonEl = createMenuButton(category, cssClass, tooltip, icon); 299 | if (buttonEl) { 300 | const toggleMenuItemsClass = getToggleMenuItemsClass(toggleMenu, menuConfig); 301 | const currentSelection = this.canvas.selection; 302 | const containingNodes = this.canvas.getContainingNodes(this.selection.bbox); 303 | const menuItemType = getItemProperty(cssClass, menuConfig, 'type'); 304 | if (toggleMenuItemsClass.includes(cssClass)) { 305 | buttonEl.addEventListener("click", () => { 306 | currentSelection.size === 1 307 | ? handleSingleNode(Array.from(currentSelection)?.first(), null, menuItemType, cssClass, false) 308 | : (containingNodes.length > 1 309 | ? handleMultiNodesViaNodes(this.canvas, containingNodes, null, menuItemType, cssClass, false) 310 | : (currentSelection 311 | ? handleSingleNode(Array.from(currentSelection)?.first(), null, menuItemType, cssClass, false) 312 | : "")); 313 | }); 314 | } else buttonEl.addEventListener("click", () => handleButtonClick(buttonEl, clickHandler)); 315 | buttonEl.addEventListener("contextmenu", () => { 316 | currentSelection.size === 1 317 | ? handleSingleNode(Array.from(currentSelection)?.first(), null, menuItemType, cssClass, true) 318 | : (containingNodes.length > 1 319 | ? handleMultiNodesViaNodes(this.canvas, containingNodes, null, menuItemType, cssClass, true) 320 | : (currentSelection 321 | ? handleSingleNode(Array.from(currentSelection)?.first(), null, menuItemType, cssClass, true) 322 | : "")); 323 | }); 324 | this.menuEl.appendChild(buttonEl); 325 | } 326 | }; 327 | 328 | menuConfig.forEach((memuItem) => { 329 | if (memuItem.enable === true) { 330 | createAndSetupMenuButton(memuItem.cat, memuItem.class, toggleMenu, memuItem.name, memuItem.icon, (menu, containingNodes) => { 331 | handleMenu(menu, subMenuConfig, async (cssClass: string) => { 332 | const currentSelection = this.canvas.selection; 333 | currentSelection.size === 1 334 | ? handleSingleNode(Array.from(currentSelection)?.first(), subMenuConfig, null, cssClass, false) 335 | : (containingNodes.length > 1 336 | ? handleMultiNodesViaNodes(this.canvas, containingNodes, subMenuConfig, null, cssClass, false) 337 | : (currentSelection 338 | ? handleSingleNode(Array.from(currentSelection)?.first(), subMenuConfig, null, cssClass, false) 339 | : "")); 340 | }, memuItem.type); 341 | }); 342 | } 343 | }); 344 | 345 | return result; 346 | }, 347 | }); 348 | 349 | this.register(menuUninstaller); 350 | this.app.workspace.trigger("canvas-style-menu:patched-canvas"); 351 | 352 | console.log("Canvas-Style-Menu: canvas menu patched"); 353 | return true; 354 | 355 | }; 356 | 357 | this.app.workspace.onLayoutReady(() => { 358 | if (!patchMenu()) { 359 | const evt = this.app.workspace.on("layout-change", () => { 360 | patchMenu() && this.app.workspace.offref(evt); 361 | }); 362 | this.registerEvent(evt); 363 | } 364 | }); 365 | } 366 | 367 | patchCanvasNode() { 368 | const menuConfig = this.settings.configs[this.settings.currentConfig].menuItems 369 | const subMenuConfig = transformSubMenuItems(this.settings.configs[this.settings.currentConfig].subMenuItems); 370 | const allMenuConfig = menuConfig.concat(subMenuConfig) 371 | 372 | const initCanvasStyle = (node: any) => { 373 | return new CanvasStyle(node, menuConfig); 374 | }; 375 | 376 | const patchNode = () => { 377 | let groupPatched: boolean = false; 378 | if (this.nodePatched && this.groupPatched) return; 379 | 380 | const canvasView = this.app.workspace.getLeavesOfType("canvas").first()?.view; 381 | if (!canvasView) return false; 382 | 383 | const canvas: Canvas = (canvasView as CanvasView)?.canvas; 384 | if (!canvas) return false; 385 | 386 | let node = (this.app.workspace.getLeavesOfType("canvas").first()?.view as any).canvas.nodes.values().next().value; 387 | const nodes = (this.app.workspace.getLeavesOfType("canvas").first()?.view as any).canvas.nodes.values(); 388 | 389 | for (const group of nodes) { 390 | if (group?.unknownData.type === "group") { 391 | node = group; 392 | groupPatched = true; 393 | break; 394 | } 395 | } 396 | 397 | if (!node) return false; 398 | let prototypeNode = Object.getPrototypeOf(node); 399 | 400 | while (prototypeNode && prototypeNode !== Object.prototype) { 401 | prototypeNode = Object.getPrototypeOf(prototypeNode); 402 | // @ts-expected-error Find the parent prototype 403 | if (prototypeNode.renderZIndex) { 404 | break; 405 | } 406 | } 407 | 408 | if (!prototypeNode) return false; 409 | 410 | const uninstallerNode = around(prototypeNode, { 411 | render: (next: any) => 412 | function (...args: any) { 413 | const result = next.call(this, ...args); 414 | 415 | this.nodeCSSclass = initCanvasStyle(this); 416 | 417 | const typeToPropertyMap = menuConfig.reduce((acc, item) => { 418 | acc[item.type] = `unknownData.${item.type}`; 419 | return acc; 420 | }, {} as { [key: string]: string }); 421 | menuConfig.forEach(async (item) => { 422 | const propertyKey = typeToPropertyMap[item.type]; 423 | const propertyValue = new Function(`return this.${propertyKey}`).call(this); 424 | if (propertyValue) { 425 | if (getItemProperty(propertyValue, allMenuConfig, 'selector') === 'cc') { 426 | await modifyClassOnElements('add', this.contentEl, 'markdown-preview-view', propertyValue); 427 | } else this.nodeEl.classList.add(propertyValue); 428 | } 429 | }); 430 | 431 | return result; 432 | }, 433 | setData: (next: any) => 434 | function (data: any) { 435 | const typeToPropertyMap = menuConfig.reduce((acc, item) => { 436 | acc[item.type] = `${item.type}`; 437 | return acc; 438 | }, {} as { [key: string]: string }); 439 | menuConfig.forEach((item) => { 440 | const propertyKey = typeToPropertyMap[item.type]; 441 | const propertyValue = data[propertyKey]; 442 | const selector = getItemProperty(propertyValue, allMenuConfig, 'selector'); 443 | this.nodeCSSclass?.setStyle(item.cat, selector, item.type, propertyValue); 444 | }); 445 | 446 | return next.call(this, data); 447 | } 448 | }); 449 | 450 | this.register(uninstallerNode); 451 | 452 | if (!this.nodePatched) { 453 | this.nodePatched = true; 454 | console.log("Canvas-Style-Menu: canvas node patched"); 455 | } 456 | 457 | if (groupPatched) { 458 | if (!this.groupPatched) { 459 | this.groupPatched = true; 460 | console.log("Canvas-Style-Menu: canvas group patched"); 461 | } 462 | } 463 | return true; 464 | }; 465 | 466 | const patchEdge = () => { 467 | if (this.edgePatched) return; 468 | const canvasView = this.app.workspace.getLeavesOfType("canvas").first()?.view; 469 | if (!canvasView) return false; 470 | 471 | const canvas: Canvas = (canvasView as CanvasView)?.canvas; 472 | if (!canvas) return false; 473 | 474 | const edge = (this.app.workspace.getLeavesOfType("canvas").first()?.view as any).canvas.edges.values().next().value; 475 | 476 | if (!edge) return false; 477 | let prototypeEdge = Object.getPrototypeOf(edge); 478 | if (!prototypeEdge) { 479 | return false; 480 | } else { 481 | const uninstallerEdge = around(prototypeEdge, { 482 | render: (next: any) => 483 | function (...args: any) { 484 | const result = next.call(this, ...args); 485 | 486 | this.nodeCSSclass = initCanvasStyle(this); 487 | 488 | let fromOffsetH: number = 0; 489 | let fromOffsetV: number = 0; 490 | let toOffsetH: number = 0; 491 | let toOffsetV: number = 0; 492 | 493 | if (this.from.side === 'right') fromOffsetH = -8; 494 | if (this.from.side === 'left') fromOffsetH = 8; 495 | if (this.from.side === 'top') fromOffsetV = 8; 496 | if (this.from.side === 'bottom') fromOffsetV = -8; 497 | if (this.to.side === 'right') toOffsetH = -8; 498 | if (this.to.side === 'left') toOffsetH = 8; 499 | if (this.to.side === 'top') toOffsetV = 8; 500 | if (this.to.side === 'bottom') toOffsetV = -8; 501 | 502 | function createLinePath(x1: number, y1: number, x2: number, y2: number): string { 503 | const pathData = `M${x1},${y1} L${x2},${y2}`; 504 | return pathData; 505 | } 506 | const linePath = createLinePath(this.bezier.from.x + fromOffsetH, this.bezier.from.y + fromOffsetV, this.bezier.to.x + toOffsetH, this.bezier.to.y + toOffsetV); 507 | 508 | function calculateAngle(x1: number, y1: number, x2: number, y2: number): number { 509 | const angleRad = Math.atan2(y2 - y1, x2 - x1); 510 | const angleDeg = (angleRad * 180) / Math.PI; 511 | return angleDeg; 512 | } 513 | const angle = calculateAngle(this.bezier.from.x + fromOffsetH, this.bezier.from.y + fromOffsetV, this.bezier.to.x + toOffsetH, this.bezier.to.y + toOffsetV); 514 | const fromRotateAngle = angle - 90; 515 | const toRotateAngle = angle - 270; 516 | 517 | const elbowPath = createElbowPath(this.from.side, this.to.side, this.bezier.from.x + fromOffsetH, this.bezier.from.y + fromOffsetV, this.bezier.to.x + toOffsetH, this.bezier.to.y + toOffsetV); 518 | 519 | const typeToPropertyMap = menuConfig.reduce((acc, item) => { 520 | acc[item.type] = `unknownData.${item.type}`; 521 | return acc; 522 | }, {} as { [key: string]: string }); 523 | menuConfig.forEach((item) => { 524 | const propertyKey = typeToPropertyMap[item.type]; 525 | const propertyValue = new Function(`return this.${propertyKey}`).call(this); 526 | if (propertyValue) { 527 | this.lineGroupEl.classList.add(propertyValue); 528 | this.lineEndGroupEl.classList.add(propertyValue); 529 | if (propertyValue === 'cs-line-straight') { 530 | const displayPath = this.lineGroupEl.querySelector('.canvas-display-path'); 531 | const interactionPath = this.lineGroupEl.querySelector('.canvas-interaction-path'); 532 | displayPath.setAttribute('d', linePath); 533 | interactionPath.setAttribute('d', linePath); 534 | if (this.toLineEnd && !this.fromLineEnd) { 535 | const toNewTransform = this.toLineEnd.el.style.transform.replace(/rotate\([-\d]+deg\)/, `rotate(${toRotateAngle}deg)`); 536 | this.toLineEnd.el.style.transform = toNewTransform; 537 | } 538 | if (this.fromLineEnd) { 539 | const fromNewTransform = this.fromLineEnd.el.style.transform.replace(/rotate\([-\d]+deg\)/, `rotate(${fromRotateAngle}deg)`); 540 | const toNewTransform = this.toLineEnd.el.style.transform.replace(/rotate\([-\d]+deg\)/, `rotate(${toRotateAngle}deg)`); 541 | this.fromLineEnd.el.style.transform = fromNewTransform; 542 | this.toLineEnd.el.style.transform = toNewTransform; 543 | } 544 | } 545 | if (propertyValue === 'cs-line-elbow') { 546 | const displayPath = this.lineGroupEl.querySelector('.canvas-display-path'); 547 | const interactionPath = this.lineGroupEl.querySelector('.canvas-interaction-path'); 548 | displayPath.setAttribute('d', elbowPath); 549 | interactionPath.setAttribute('d', elbowPath); 550 | } 551 | } 552 | }); 553 | 554 | return result; 555 | }, 556 | setData: (next: any) => 557 | function (data: any) { 558 | const typeToPropertyMap = menuConfig.reduce((acc, item) => { 559 | acc[item.type] = `${item.type}`; 560 | return acc; 561 | }, {} as { [key: string]: string }); 562 | menuConfig.forEach((item) => { 563 | const propertyKey = typeToPropertyMap[item.type]; 564 | const propertyValue = data[propertyKey]; 565 | const selector = getItemProperty(propertyValue, allMenuConfig, 'selector'); 566 | this.nodeCSSclass?.setStyle(item.cat, selector, item.type, propertyValue); 567 | }); 568 | 569 | return next.call(this, data); 570 | } 571 | }); 572 | 573 | this.register(uninstallerEdge); 574 | } 575 | 576 | if (!this.edgePatched) { 577 | this.edgePatched = true; 578 | console.log("Canvas-Style-Menu: canvas edge patched"); 579 | } 580 | return true; 581 | }; 582 | 583 | this.app.workspace.onLayoutReady(() => { 584 | if (!patchNode()) { 585 | const evt = this.app.workspace.on("layout-change", () => { 586 | this.nodePatched && this.app.workspace.offref(evt); 587 | }); 588 | this.registerEvent(evt); 589 | } 590 | if (!patchEdge()) { 591 | const evt = this.app.workspace.on("layout-change", () => { 592 | this.edgePatched && this.app.workspace.offref(evt); 593 | }); 594 | this.registerEvent(evt); 595 | } 596 | }); 597 | } 598 | 599 | } 600 | -------------------------------------------------------------------------------- /src/memuConfigs.ts: -------------------------------------------------------------------------------- 1 | interface MenuItem { 2 | class: string; 3 | type: string; 4 | icon: string; 5 | name: string; 6 | cat: string; 7 | selector: string; 8 | ctxmenu: boolean; 9 | enable: boolean; 10 | expanded: boolean; 11 | } 12 | 13 | interface SubMenuItem { 14 | class: string; 15 | type: string; 16 | icon: string; 17 | name: string; 18 | selector: string; 19 | enable: boolean; 20 | } 21 | 22 | interface CustomIcon { 23 | name: string; 24 | svgContent: string; 25 | } 26 | 27 | interface Config { 28 | [key: string]: { 29 | name: string; 30 | menuItems: MenuItem[]; 31 | subMenuItems: { 32 | [key: string]: SubMenuItem[]; 33 | }; 34 | customIcons: CustomIcon[]; 35 | }; 36 | } 37 | 38 | interface TransformedSubMenuItems extends SubMenuItem { 39 | type: string; 40 | } 41 | 42 | export const defaultConfigs: Config = { 43 | "default": { 44 | name: "Default", 45 | menuItems: [ 46 | { 47 | class: "cs-border", 48 | type: "border", 49 | icon: "cs-style-border", 50 | name: "Border", 51 | cat: "", 52 | selector: "", 53 | ctxmenu: false, 54 | enable: true 55 | }, 56 | { 57 | class: "cs-bg", 58 | type: "bg", 59 | icon: "cs-background", 60 | name: "Background", 61 | cat: "", 62 | selector: "", 63 | ctxmenu: false, 64 | enable: true 65 | }, 66 | { 67 | class: "cs-rotate", 68 | type: "rotate", 69 | icon: "rotate-cw", 70 | name: "Rotate", 71 | cat: "", 72 | selector: "", 73 | ctxmenu: false, 74 | enable: true 75 | }, 76 | { 77 | class: "cs-shape", 78 | type: "shape", 79 | icon: "diamond", 80 | name: "Shape", 81 | cat: "", 82 | selector: "", 83 | ctxmenu: false, 84 | enable: true 85 | }, 86 | { 87 | class: "cs-highlight", 88 | type: "highlight", 89 | icon: "star", 90 | name: "Highlight", 91 | cat: "", 92 | selector: "", 93 | ctxmenu: false, 94 | enable: true 95 | }, 96 | { 97 | class: "cs-extra", 98 | type: "extra", 99 | icon: "more-horizontal", 100 | name: "Extra", 101 | cat: "", 102 | selector: "", 103 | ctxmenu: false, 104 | enable: true 105 | }, 106 | { 107 | class: "cs-line-type", 108 | type: "lineType", 109 | icon: "cs-border-corner-pill", 110 | name: "Line type", 111 | cat: "edge", 112 | selector: "", 113 | ctxmenu: false, 114 | enable: true 115 | }, 116 | { 117 | class: "cs-line-style", 118 | type: "lineStyle", 119 | icon: "cs-line-style", 120 | name: "Line style", 121 | cat: "edge", 122 | selector: "", 123 | ctxmenu: false, 124 | enable: true 125 | }, 126 | { 127 | class: "cs-line-thickness", 128 | type: "lineThickness", 129 | icon: "equal", 130 | name: "Line thickness", 131 | cat: "edge", 132 | selector: "", 133 | ctxmenu: false, 134 | enable: true 135 | } 136 | ], 137 | subMenuItems: { 138 | border: [ 139 | { 140 | class: "cs-border-none", 141 | type: "border", 142 | icon: "cs-no-border", 143 | name: "No border", 144 | selector: "", 145 | enable: true 146 | }, 147 | { 148 | class: "cs-border-dashed", 149 | type: "border", 150 | icon: "box-select", 151 | name: "Dashed", 152 | selector: "", 153 | enable: true 154 | } 155 | ], 156 | bg: [ 157 | { 158 | class: "cs-bg-transparent", 159 | type: "bg", 160 | icon: "cs-transparent", 161 | name: "Transparent", 162 | selector: "", 163 | enable: true 164 | }, 165 | { 166 | class: "cs-bg-opacity-0", 167 | type: "bg", 168 | icon: "cs-opacity", 169 | name: "Opacity 0", 170 | selector: "", 171 | enable: true 172 | } 173 | ], 174 | rotate: [ 175 | { 176 | class: "cs-rotate-right-45", 177 | type: "rotate", 178 | icon: "redo", 179 | name: "Right 45", 180 | selector: "", 181 | enable: true 182 | }, 183 | { 184 | class: "cs-rotate-right-90", 185 | type: "rotate", 186 | icon: "redo", 187 | name: "Right 90", 188 | selector: "", 189 | enable: true 190 | }, 191 | { 192 | class: "cs-rotate-left-45", 193 | type: "rotate", 194 | icon: "undo", 195 | name: "Left 45", 196 | selector: "", 197 | enable: true 198 | }, 199 | { 200 | class: "cs-rotate-left-90", 201 | type: "rotate", 202 | icon: "undo", 203 | name: "Left 90", 204 | selector: "", 205 | enable: true 206 | } 207 | ], 208 | shape: [ 209 | { 210 | class: "cs-shape-circle", 211 | type: "shape", 212 | icon: "circle", 213 | name: "Circle", 214 | selector: "", 215 | enable: true 216 | }, 217 | { 218 | class: "cs-shape-parallelogram-right", 219 | type: "shape", 220 | icon: "cs-parallelogram-right", 221 | name: "Parallelogram right", 222 | selector: "", 223 | enable: true 224 | }, 225 | { 226 | class: "cs-shape-parallelogram-left", 227 | type: "shape", 228 | icon: "cs-parallelogram-left", 229 | name: "Parallelogram left", 230 | selector: "", 231 | enable: true 232 | } 233 | ], 234 | highlight: [], 235 | extra: [], 236 | lineType: [ 237 | { 238 | class: "cs-line-straight", 239 | type: "lineType", 240 | icon: "minus", 241 | name: "Straight", 242 | selector: "", 243 | enable: true 244 | }, 245 | { 246 | class: "cs-line-elbow", 247 | type: "lineType", 248 | icon: "cs-elbow", 249 | name: "Elbow", 250 | selector: "", 251 | enable: true 252 | } 253 | ], 254 | lineStyle: [ 255 | { 256 | class: "cs-line-dashed", 257 | type: "lineStyle", 258 | icon: "cs-line-dashed", 259 | name: "Dashed", 260 | selector: "", 261 | enable: true 262 | }, 263 | { 264 | class: "cs-line-dashed-round", 265 | type: "lineStyle", 266 | icon: "cs-line-dashed", 267 | name: "Dashed round", 268 | selector: "", 269 | enable: true 270 | }, 271 | { 272 | class: "cs-line-dotted", 273 | type: "lineStyle", 274 | icon: "cs-line-dotted", 275 | name: "Dotted", 276 | selector: "", 277 | enable: true 278 | }, 279 | { 280 | class: "cs-line-dotted-line", 281 | type: "lineStyle", 282 | icon: "cs-dotted-line", 283 | name: "Dotted line", 284 | selector: "", 285 | enable: true 286 | } 287 | ], 288 | lineThickness: [ 289 | { 290 | class: "cs-line-thick", 291 | type: "lineThickness", 292 | icon: "cs-thicker", 293 | name: "Thicker", 294 | selector: "", 295 | enable: true 296 | }, 297 | { 298 | class: "cs-line-thicker", 299 | type: "lineThickness", 300 | icon: "cs-thicker++", 301 | name: "Thicker++", 302 | selector: "", 303 | enable: true 304 | } 305 | ] 306 | }, 307 | customIcons: [] 308 | } 309 | }; 310 | 311 | export const csIcons: CustomIcon[] = [ 312 | // Lucide Icons 313 | { 314 | name: "cs-no-border", 315 | svgContent: `` 316 | }, 317 | { 318 | name: "cs-thicker", 319 | svgContent: `` 320 | }, 321 | { 322 | name: "cs-thicker++", 323 | svgContent: `` 324 | }, 325 | { 326 | name: "cs-circle-dashed", 327 | svgContent: `` 328 | }, 329 | // Tabler Icons 330 | { 331 | name: "cs-background", 332 | svgContent: `` 333 | }, 334 | { 335 | name: "cs-transparent", 336 | svgContent: `` 337 | }, 338 | { 339 | name: "cs-opacity", 340 | svgContent: `` 341 | }, 342 | { 343 | name: "cs-border-corner-pill", 344 | svgContent: `` 345 | }, 346 | { 347 | name: "cs-line-style", 348 | svgContent: `` 349 | }, 350 | { 351 | name: "cs-line-dashed", 352 | svgContent: `` 353 | }, 354 | { 355 | name: "cs-line-dotted", 356 | svgContent: `` 357 | }, 358 | { 359 | name: "cs-dotted-line", 360 | svgContent: `` 361 | }, 362 | { 363 | name: "cs-input-check", 364 | svgContent: `` 365 | }, 366 | // Custom Icons 367 | { 368 | name: "cs-style-border", 369 | svgContent: `` 370 | }, 371 | { 372 | name: "cs-parallelogram-right", 373 | svgContent: `` 374 | }, 375 | { 376 | name: "cs-parallelogram-left", 377 | svgContent: `` 378 | }, 379 | { 380 | name: "cs-elbow", 381 | svgContent: `` 382 | }, 383 | { 384 | name: "cs-badge-cc", 385 | svgContent: `` 386 | }, 387 | //{name: "xxx", svgContent: `yyy`}, 388 | ]; 389 | -------------------------------------------------------------------------------- /src/setting.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { App, PluginSettingTab, Setting, Notice, Modal, getIconIds, setIcon, removeIcon, setTooltip } from 'obsidian'; 4 | import CanvasStyleMenuPlugin, { defaultSettings, savedSettings, csIconList } from "./main"; 5 | import { sortByProperty, renameKey, getFilesInDirectory } from "./utils"; 6 | 7 | interface MenuItemTypeChanged { 8 | oldType: string; 9 | newType: string; 10 | } 11 | 12 | class CanvasStyleMenuSetting extends Setting { 13 | settings: [] | {}; 14 | key: string; 15 | } 16 | 17 | export default class CanvasStyleMenuSettingTab extends PluginSettingTab { 18 | plugin: CanvasStyleMenuPlugin; 19 | menuItemsContainerEl: HTMLDivElement; 20 | subMenuItemsContainerEl: HTMLDivElement; 21 | customIconsContainerEl: HTMLDivElement; 22 | configTabContainerEl: HTMLDivElement; 23 | menuItemsTabContainerEl: HTMLDivElement; 24 | customIconsTabContainerEl: HTMLDivElement; 25 | previousConfig: string; 26 | 27 | devMode: boolean = false; 28 | menuItemTypeChanged: MenuItemTypeChanged = { oldType: "", newType: "" }; 29 | 30 | constructor(app: App, plugin: CanvasStyleMenuPlugin) { 31 | super(app, plugin); 32 | this.plugin = plugin; 33 | } 34 | 35 | display(): void { 36 | const {containerEl} = this; 37 | 38 | containerEl.empty(); 39 | 40 | const configs = this.plugin.settings.configs; 41 | const currentConfig = this.plugin.settings.currentConfig; 42 | const config = configs[currentConfig]; 43 | 44 | const defaultConfigs = JSON.parse(JSON.stringify(defaultSettings)); 45 | const savedConfigs = JSON.parse(JSON.stringify(savedSettings)); 46 | 47 | const vaultDir = this.app.vault.adapter.basePath; 48 | const pluginDir = this.plugin.manifest.dir; 49 | const packagesFolder = path.join(vaultDir, pluginDir, 'packages'); 50 | 51 | // settings header 52 | this.settingsHeaderContainerEl = containerEl.createDiv({ 53 | cls: "settings-header-container", 54 | }); 55 | 56 | // Style Pack Config 57 | new Setting(this.settingsHeaderContainerEl) 58 | .setName("Style Package") 59 | .setDesc("Manage installed style package and browse community style packages.") 60 | .addText(text => text 61 | .setPlaceholder('New Package Name') 62 | ) 63 | .addExtraButton((component) => 64 | component 65 | .setIcon("save") 66 | .setTooltip("Save as new package") 67 | .onClick(async () => { 68 | this.removePreviousIcons(currentConfig, savedConfigs); 69 | const packageName = this.settingsHeaderContainerEl.querySelector('input').value; 70 | if (packageName) { 71 | await this.saveAsNewPackage(config, packageName); 72 | new Notice('Package successfully saved'); 73 | } 74 | }) 75 | ) 76 | .addExtraButton((component) => 77 | component 78 | .setIcon("rotate-ccw") 79 | .setTooltip("Restore the current package defaults") 80 | .onClick(async () => { 81 | this.removePreviousIcons(currentConfig, savedConfigs); 82 | if (currentConfig in savedConfigs) { 83 | configs[currentConfig] = savedConfigs[currentConfig]; 84 | } else { 85 | configs[currentConfig] = defaultConfigs[currentConfig]; 86 | } 87 | await this.plugin.saveSettings(); 88 | this.plugin.registerCustomIcons(); 89 | this.plugin.patchCanvasMenu(true); 90 | this.display(); 91 | }) 92 | ) 93 | .addDropdown((dropdown) => { 94 | Object.keys(configs).forEach((config) => { 95 | const group = configs[config]; 96 | dropdown.addOption(config, group.name); 97 | }); 98 | dropdown.setValue(currentConfig); 99 | dropdown.onChange(async (value) => { 100 | this.removePreviousIcons(currentConfig, savedConfigs); 101 | this.plugin.settings.currentConfig = value; 102 | await this.plugin.saveSettings(); 103 | this.plugin.registerCustomIcons(); 104 | this.plugin.patchCanvasMenu(true); 105 | this.display(); 106 | }); 107 | }) 108 | .addExtraButton((component) => 109 | component 110 | .setIcon("folder-open") 111 | .setTooltip("Open packages folder") 112 | .onClick(() => { 113 | const { exec } = require('child_process'); 114 | fs.mkdir(packagesFolder, (err) => { 115 | if (err) { 116 | if (err.code === 'EEXIST') { 117 | exec(`start explorer ${packagesFolder}`); 118 | } else { 119 | console.error('Failed to create style packages folder:', err); 120 | } 121 | } else { 122 | exec(`start explorer ${packagesFolder}`); 123 | } 124 | }); 125 | }) 126 | ) 127 | .addExtraButton((component) => 128 | component 129 | .setIcon("refresh-cw") 130 | .setTooltip("Reload packages") 131 | .onClick(() => { 132 | fs.mkdir(packagesFolder, (err) => { 133 | if (err) { 134 | if (err.code === 'EEXIST') { 135 | } else { 136 | console.error(`Failed to create style packages folder: ${err}`); 137 | } 138 | } else {} 139 | }); 140 | const packageFiles = getFilesInDirectory(packagesFolder); 141 | if (packageFiles) { 142 | packageFiles.forEach(packageFile => { 143 | const packageFilePath = path.join(packagesFolder, packageFile); 144 | let importedPackage = {}; 145 | fs.readFile(packageFilePath, 'utf-8', async (err, contentAsync) => { 146 | if (err) { 147 | console.error(err); 148 | return; 149 | } 150 | importedPackage = JSON.parse(contentAsync); 151 | const importedConfig = Object.keys(importedPackage)[0]; 152 | if (!(importedConfig in this.plugin.settings.configs)) { 153 | this.plugin.settings.configs = { ...this.plugin.settings.configs, ...importedPackage }; 154 | this.plugin.settings.savedConfigs = JSON.parse(JSON.stringify({ ...this.plugin.settings.savedConfigs, ...importedPackage })); 155 | await this.plugin.saveSettings(); 156 | this.plugin.savedSettings(); 157 | this.display(); 158 | 159 | } 160 | }); 161 | }); 162 | new Notice('Reloaded style packages'); 163 | } 164 | }) 165 | ) 166 | .addExtraButton((component) => 167 | component 168 | .setIcon("upload") 169 | .setTooltip("Export package") 170 | .onClick(() => { 171 | fs.mkdir(packagesFolder, (err) => { 172 | if (err) { 173 | if (err.code === 'EEXIST') { 174 | } else { 175 | console.error(`Failed to create style packages folder: ${err}`); 176 | } 177 | } else {} 178 | }); 179 | const exportedPackage = {[currentConfig]: config}; 180 | const jsonString = JSON.stringify(exportedPackage, null, 2); 181 | const packageFile = `${currentConfig}.json`; 182 | const packageFilePath = path.join(packagesFolder, packageFile); 183 | fs.writeFile(packageFilePath, jsonString, (err) => { 184 | if (err) { 185 | new Notice(`Error writing JSON file: ${err}`); 186 | } else { 187 | new Notice('Package successfully exported'); 188 | } 189 | }); 190 | }) 191 | ) 192 | .addExtraButton((component) => 193 | component 194 | .setIcon("trash") 195 | .setTooltip("Delete package") 196 | .onClick(async () => { 197 | this.removePreviousIcons(currentConfig, savedConfigs); 198 | if (currentConfig in savedConfigs) { 199 | //if (currentConfig !== 'default') { 200 | delete this.plugin.settings.savedConfigs[currentConfig]; 201 | delete configs[currentConfig]; 202 | this.plugin.settings.currentConfig = 'default'; 203 | await this.plugin.saveSettings(); 204 | this.plugin.registerCustomIcons(); 205 | this.plugin.patchCanvasMenu(true); 206 | this.display(); 207 | } 208 | }) 209 | .then(cb => { 210 | if (currentConfig in savedConfigs) { 211 | //if (currentConfig !== 'default') { 212 | cb.extraSettingsEl.addClass("mod-warning"); 213 | } else { 214 | cb.extraSettingsEl.addClass("disabled"); 215 | } 216 | }) 217 | ) 218 | //.addButton((component) => 219 | // component 220 | // .setButtonText("Manage") 221 | // .setClass("mod-cta") 222 | // .onClick(() => { 223 | // }) 224 | //); 225 | 226 | // config tab 227 | this.configTabContainerEl = containerEl.createDiv({ 228 | cls: "config-tab-container", 229 | }); 230 | 231 | new Setting(this.configTabContainerEl) 232 | .addExtraButton((component) => 233 | component 234 | .setIcon("settings-2") 235 | .onClick(() => { 236 | if (this.customIconsTabContainerEl.classList.contains("actived")) { 237 | this.customIconsTabContainerEl.removeClass("actived"); 238 | } 239 | this.menuItemsTabContainerEl.addClass("actived"); 240 | if (this.configTabContainerEl.querySelector(".custom-icons-tab.actived")) { 241 | this.configTabContainerEl.querySelector(".custom-icons-tab.actived").removeClass("actived"); 242 | } 243 | component.extraSettingsEl.addClass("actived"); 244 | }) 245 | .then(cb => { 246 | cb.extraSettingsEl.addClass("menu-items-tab"); 247 | cb.extraSettingsEl.addClass("actived"); 248 | cb.extraSettingsEl.setAttr("data-label", "Menu Items"); 249 | }) 250 | ) 251 | .addExtraButton((component) => 252 | component 253 | .setIcon("shapes") 254 | .onClick(() => { 255 | if (this.menuItemsTabContainerEl.classList.contains("actived")) { 256 | this.menuItemsTabContainerEl.removeClass("actived"); 257 | } 258 | this.customIconsTabContainerEl.addClass("actived"); 259 | if (this.configTabContainerEl.querySelector(".menu-items-tab.actived")) { 260 | this.configTabContainerEl.querySelector(".menu-items-tab.actived").removeClass("actived"); 261 | } 262 | component.extraSettingsEl.addClass("actived"); 263 | }) 264 | .then(cb => { 265 | cb.extraSettingsEl.addClass("custom-icons-tab"); 266 | cb.extraSettingsEl.setAttr("data-label", "Custom Icons"); 267 | }) 268 | ) 269 | 270 | // Menu Items Config 271 | this.menuItemsTabContainerEl = containerEl.createDiv({ 272 | cls: "setting-tab-container menu-items-tab-container actived", 273 | }); 274 | 275 | new Setting(this.menuItemsTabContainerEl) 276 | .setName("Menu Items") 277 | .setDesc("Set menu items config.") 278 | .addExtraButton((component) => 279 | component 280 | .setIcon("chevrons-up-down") 281 | .setTooltip("Expand all") 282 | .onClick(() => { 283 | config.menuItems.forEach(menuItem => { 284 | if (!menuItem.expanded) { 285 | menuItem.expanded = true; 286 | } 287 | }) 288 | this.display(); 289 | }) 290 | ) 291 | .addExtraButton((component) => 292 | component 293 | .setIcon("chevrons-down-up") 294 | .setTooltip("Collapse All") 295 | .onClick(() => { 296 | config.menuItems.forEach(menuItem => { 297 | if (menuItem.expanded) { 298 | menuItem.expanded = false; 299 | } 300 | }) 301 | this.display(); 302 | }) 303 | ) 304 | .addButton((component) => 305 | component 306 | .setButtonText("Add Menu Item") 307 | .setClass("mod-cta") 308 | .onClick(() => { 309 | newMenuItemSettingContainerEl.addClass("add-setting"); 310 | }) 311 | ); 312 | 313 | this.menuItemsContainerEl = this.menuItemsTabContainerEl.createDiv({ 314 | cls: "setting-item-container menu-items-container", 315 | }); 316 | 317 | for (let menuItem of config.menuItems) { 318 | this.renderMenuItemsSetting( 319 | config, 320 | menuItem, 321 | config.menuItems 322 | ); 323 | } 324 | 325 | const newMenuItemSettingContainerEl = this.menuItemsContainerEl.createDiv({ 326 | cls: "setting-item-container new-menu-item-setting-container", 327 | }); 328 | 329 | this.buildNewMenuItemSetting(newMenuItemSettingContainerEl, async (menuItem) => { 330 | config.menuItems.push(menuItem); 331 | config.menuItems = sortByProperty(config.menuItems, 'cat') 332 | if (!Object.keys(config.subMenuItems).contains(menuItem.type)) { 333 | config.subMenuItems[menuItem.type] = []; 334 | } 335 | await this.plugin.saveSettings(); 336 | this.plugin.patchCanvasMenu(true); 337 | 338 | // Re-draw. 339 | this.display(); 340 | }); 341 | 342 | // Custom Icons Config 343 | this.customIconsTabContainerEl = containerEl.createDiv({ 344 | cls: "setting-tab-container custom-icons-tab-container", 345 | }); 346 | 347 | new Setting(this.customIconsTabContainerEl) 348 | .setName("Custom Icons") 349 | .setDesc("Add custom icons.") 350 | .addButton((component) => 351 | component 352 | .setButtonText("Add Custom Icon") 353 | .setClass("mod-cta") 354 | .onClick(() => { 355 | newCustomIconSettingContainerEl.addClass("add-setting"); 356 | }) 357 | ); 358 | 359 | 360 | this.customIconsContainerEl = this.customIconsTabContainerEl.createDiv({ 361 | cls: "setting-item-container custom-icons-container", 362 | }); 363 | 364 | config.customIcons.forEach(icon => { 365 | this.renderCustomIconsSetting( 366 | config, 367 | icon 368 | ); 369 | }); 370 | 371 | const newCustomIconSettingContainerEl = this.customIconsContainerEl.createDiv({ 372 | cls: "setting-item-container new-custom-icon-setting-container", 373 | }); 374 | 375 | this.buildNewIconSetting(newCustomIconSettingContainerEl, async (icon) => { 376 | config.customIcons.push(icon); 377 | await this.plugin.saveSettings(); 378 | this.plugin.registerCustomIcons(); 379 | 380 | // Re-draw. 381 | this.display(); 382 | this.menuItemsTabContainerEl.removeClass("actived"); 383 | this.customIconsTabContainerEl.addClass("actived"); 384 | this.configTabContainerEl.querySelector(".menu-items-tab.actived").removeClass("actived"); 385 | this.configTabContainerEl.querySelector(".custom-icons-tab").addClass("actived"); 386 | }); 387 | } 388 | 389 | renderMenuItemsSetting(config, menuItem: MenuItem, settings: MenuItem) { 390 | const cls = menuItem.class; 391 | const type = menuItem.type; 392 | const icon = menuItem.icon; 393 | const name = menuItem.name; 394 | const cat = menuItem.cat; 395 | const selector = menuItem.selector; 396 | const index = config.menuItems.indexOf(menuItem); 397 | if (this.devMode) { 398 | if (type === this.menuItemTypeChanged.newType) { 399 | renameKey(config.subMenuItems, this.menuItemTypeChanged.oldType, this.menuItemTypeChanged.newType); 400 | this.plugin.saveSettings(); 401 | } 402 | } 403 | const subMenuItems = config.subMenuItems[type]; 404 | const setting = new CanvasStyleMenuSetting(this.menuItemsContainerEl) 405 | .addExtraButton((component) => 406 | component 407 | .setIcon("chevron-right") 408 | .onClick(async () => { 409 | config.menuItems[index].expanded = !config.menuItems[index].expanded; 410 | await this.plugin.saveSettings(); 411 | if (config.menuItems[index].expanded) { 412 | component.setIcon("chevron-down"); 413 | component.extraSettingsEl.removeClass("setting-expanded-false"); 414 | component.extraSettingsEl.addClass("setting-expanded-true"); 415 | } 416 | else { 417 | component.setIcon("chevron-right"); 418 | component.extraSettingsEl.removeClass("setting-expanded-true"); 419 | component.extraSettingsEl.addClass("setting-expanded-false"); 420 | } 421 | }) 422 | .then(cb => { 423 | if (subMenuItems.length === 0) cb.extraSettingsEl.addClass("nosub"); 424 | cb.extraSettingsEl.addClass("setting-expanded-true"); 425 | if (!config.menuItems[index].expanded) { 426 | cb.extraSettingsEl.removeClass("setting-expanded-true"); 427 | cb.extraSettingsEl.addClass("setting-expanded-false"); 428 | cb.setIcon("chevron-right"); 429 | } else cb.setIcon("chevron-down"); 430 | }) 431 | ) 432 | .addExtraButton((component) => 433 | component 434 | .setIcon(icon) 435 | .onClick(() => { 436 | new SelectIconModal(this.app, this.plugin, component, 'menuItem', config, index).open(); 437 | }) 438 | ) 439 | .addText(text => text 440 | .setPlaceholder('CSS Class') 441 | .setValue(cls) 442 | .onChange(async (value) => { 443 | config.menuItems[index].class = value; 444 | await this.plugin.saveSettings(); 445 | })) 446 | .addText(text => text 447 | .setPlaceholder('Type') 448 | .setValue(type) 449 | .onChange(async (value) => { 450 | config.menuItems[index].type = value; 451 | if (this.devMode) { 452 | config.subMenuItems[type].forEach(subMenuItem => { 453 | subMenuItem.type = value; 454 | }) 455 | await this.plugin.saveSettings(); 456 | this.menuItemTypeChanged = {oldType: type, newType: value}; 457 | this.plugin.patchCanvasMenu(true); 458 | } else { 459 | await this.plugin.saveSettings(); 460 | } 461 | }) 462 | .setDisabled(true) 463 | .inputEl.addClass("disabled") 464 | ) 465 | .addText(text => text 466 | .setPlaceholder('Name') 467 | .setValue(name) 468 | .onChange(async (value) => { 469 | config.menuItems[index].name = value; 470 | await this.plugin.saveSettings(); 471 | })) 472 | .addExtraButton((component) => { 473 | component 474 | .setIcon("git-commit-horizontal") 475 | .setTooltip("For connection line") 476 | .onClick(async () => { 477 | if (this.devMode) { 478 | const ctxmenu = setting.components.find(obj => obj.extraSettingsEl?.classList.contains("setting-ctxmenu-check")); 479 | if (!config.menuItems[index].cat) { 480 | config.menuItems[index].cat = 'edge'; 481 | component.extraSettingsEl.removeClass("setting-edge-check"); 482 | component.extraSettingsEl.addClass("setting-edge-checked"); 483 | ctxmenu.extraSettingsEl.addClass("disabled"); 484 | } else { 485 | config.menuItems[index].cat = ''; 486 | component.extraSettingsEl.removeClass("setting-edge-checked"); 487 | component.extraSettingsEl.addClass("setting-edge-check"); 488 | ctxmenu.extraSettingsEl.removeClass("disabled"); 489 | } 490 | await this.plugin.saveSettings(); 491 | } 492 | }) 493 | .then(cb => { 494 | cb.extraSettingsEl.addClass("setting-edge-check"); 495 | if (config.menuItems[index].cat) { 496 | cb.extraSettingsEl.removeClass("setting-edge-check"); 497 | cb.extraSettingsEl.addClass("setting-edge-checked"); 498 | } 499 | if (config.menuItems[index].ctxmenu) { 500 | cb.extraSettingsEl.addClass("disabled"); 501 | } 502 | cb.extraSettingsEl.addClass("disabled"); 503 | }); 504 | }) 505 | .addExtraButton((component) => { 506 | component 507 | .setIcon("cs-badge-cc") 508 | .setTooltip("Use cssclasses styling") 509 | .onClick(async () => { 510 | if (this.devMode) { 511 | if (!config.menuItems[index].selector) { 512 | config.menuItems[index].selector = 'cc'; 513 | component.extraSettingsEl.removeClass("setting-cc-check"); 514 | component.extraSettingsEl.addClass("setting-cc-checked"); 515 | } else { 516 | config.menuItems[index].selector = ''; 517 | component.extraSettingsEl.removeClass("setting-cc-checked"); 518 | component.extraSettingsEl.addClass("setting-cc-check"); 519 | } 520 | await this.plugin.saveSettings(); 521 | } 522 | }) 523 | .then(cb => { 524 | cb.extraSettingsEl.addClass("setting-cc-check"); 525 | if (config.menuItems[index].selector) { 526 | cb.extraSettingsEl.removeClass("setting-cc-check"); 527 | cb.extraSettingsEl.addClass("setting-cc-checked"); 528 | } 529 | cb.extraSettingsEl.addClass("disabled"); 530 | }); 531 | }) 532 | .addExtraButton((component) => { 533 | component 534 | .setIcon("cs-input-check") 535 | .setTooltip("Show in context menu") 536 | .onClick(async () => { 537 | //const edge = setting.components.find(obj => obj.extraSettingsEl?.classList.contains("setting-edge-check")); 538 | config.menuItems[index].ctxmenu = !config.menuItems[index].ctxmenu; 539 | if (config.menuItems[index].ctxmenu) { 540 | component.extraSettingsEl.removeClass("setting-ctxmenu-check"); 541 | component.extraSettingsEl.addClass("setting-ctxmenu-checked"); 542 | //edge.extraSettingsEl.addClass("disabled"); 543 | } else { 544 | component.extraSettingsEl.removeClass("setting-ctxmenu-checked"); 545 | component.extraSettingsEl.addClass("setting-ctxmenu-check"); 546 | //edge.extraSettingsEl.removeClass("disabled"); 547 | } 548 | await this.plugin.saveSettings(); 549 | }) 550 | .then(cb => { 551 | if (subMenuItems.length === 0) cb.extraSettingsEl.addClass("nosub"); 552 | cb.extraSettingsEl.addClass("setting-ctxmenu-check"); 553 | if (config.menuItems[index].ctxmenu) { 554 | cb.extraSettingsEl.removeClass("setting-ctxmenu-check"); 555 | cb.extraSettingsEl.addClass("setting-ctxmenu-checked"); 556 | } 557 | if (config.menuItems[index].cat === 'edge') { 558 | cb.extraSettingsEl.addClass("disabled"); 559 | } 560 | }); 561 | }) 562 | .addExtraButton((component) => 563 | component 564 | .setIcon("plus-circle") 565 | .setTooltip("Add sub menu item") 566 | .onClick(() => { 567 | if (!config.menuItems[index].expanded) { 568 | config.menuItems[index].expanded = true; 569 | setting.components[0].extraSettingsEl.removeClass("setting-expanded-false"); 570 | setting.components[0].extraSettingsEl.addClass("setting-expanded-true"); 571 | setting.components[0].setIcon("chevron-down"); 572 | newSubMenuItemSettingContainerEl.addClass("add-setting"); 573 | } 574 | newSubMenuItemSettingContainerEl.addClass("add-setting"); 575 | }) 576 | ) 577 | .addExtraButton((component) => 578 | component 579 | .setIcon("arrow-up") 580 | .onClick(() => { 581 | this.moveSetting(setting, false); 582 | }) 583 | .then(cb => { 584 | cb.extraSettingsEl.addClass("setting-item-controller"); 585 | }) 586 | ) 587 | .addExtraButton((component) => 588 | component 589 | .setIcon("arrow-down") 590 | .onClick(() => { 591 | this.moveSetting(setting, true); 592 | }) 593 | .then(cb => { 594 | cb.extraSettingsEl.addClass("setting-item-controller"); 595 | }) 596 | ) 597 | .addExtraButton((component) => 598 | component 599 | .setIcon('x') 600 | .onClick(async () => { 601 | config.menuItems.splice(index, 1); 602 | delete config.subMenuItems[type]; 603 | //if (subMenuItems.length === 0) delete config.subMenuItems[type]; 604 | await this.plugin.saveSettings(); 605 | this.plugin.patchCanvasMenu(true); 606 | this.display(); 607 | }) 608 | .then(cb => { 609 | cb.extraSettingsEl.addClass("mod-warning"); 610 | }) 611 | ) 612 | .addToggle((toggle) => { 613 | toggle 614 | .setValue(config.menuItems[index].enable) 615 | .onChange(async (value) => { 616 | config.menuItems[index].enable = value; 617 | await this.plugin.saveSettings(); 618 | }); 619 | }) 620 | .then((mn: CanvasStyleMenuSetting) => { 621 | mn.settings = settings; 622 | mn.settingEl.addClass("menu-item-setting"); 623 | }); 624 | 625 | this.subMenuItemsContainerEl = setting.settingEl.createDiv({ 626 | cls: `setting-item-container submenu-items-container ${type}-items-container`, 627 | }); 628 | 629 | for (let subMenuItem of subMenuItems) { 630 | this.renderSubMenuItemsSetting( 631 | config, 632 | subMenuItem, 633 | subMenuItems 634 | ); 635 | } 636 | 637 | const newSubMenuItemSettingContainerEl = this.subMenuItemsContainerEl.createDiv({ 638 | cls: "setting-item-container new-submenu-item-setting-container", 639 | }); 640 | 641 | this.buildNewSubMenuItemSetting(newSubMenuItemSettingContainerEl, type, async (subMenuItem) => { 642 | subMenuItems.push(subMenuItem); 643 | await this.plugin.saveSettings(); 644 | this.plugin.patchCanvasMenu(true); 645 | 646 | // Re-draw. 647 | this.display(); 648 | }); 649 | 650 | return setting; 651 | } 652 | 653 | renderSubMenuItemsSetting(config, subMenuItem: SubMenuItem, settings: SubMenuItem) { 654 | const cls = subMenuItem.class; 655 | const type = subMenuItem.type; 656 | const icon = subMenuItem.icon; 657 | const name = subMenuItem.name; 658 | const selector = subMenuItem.selector; 659 | const index = settings.indexOf(subMenuItem); 660 | const subMenuItems = config.subMenuItems[type]; 661 | const setting = new CanvasStyleMenuSetting(this.subMenuItemsContainerEl) 662 | .addExtraButton((component) => 663 | component 664 | .setIcon("grip-vertical") 665 | ) 666 | .addExtraButton((component) => 667 | component 668 | .setIcon(icon) 669 | .onClick(() => { 670 | new SelectIconModal(this.app, this.plugin, component, 'subMenuItem', subMenuItems, index).open(); 671 | }) 672 | ) 673 | .addText(text => text 674 | .setPlaceholder('CSS Class') 675 | .setValue(cls) 676 | .onChange(async (value) => { 677 | subMenuItems[index].class = value; 678 | await this.plugin.saveSettings(); 679 | })) 680 | .addText(text => text 681 | .setValue(type) 682 | .setDisabled(true) 683 | .inputEl.addClass("hidden") 684 | ) 685 | .addText(text => text 686 | .setPlaceholder('Name') 687 | .setValue(name) 688 | .onChange(async (value) => { 689 | subMenuItems[index].name = value; 690 | await this.plugin.saveSettings(); 691 | })) 692 | .addExtraButton((component) => { 693 | component 694 | .then(cb => { 695 | cb.extraSettingsEl.addClass("hidden"); 696 | }); 697 | }) 698 | .addExtraButton((component) => { 699 | component 700 | .setIcon("cs-badge-cc") 701 | .setTooltip("Use cssclasses styling") 702 | .onClick(async () => { 703 | if (this.devMode) { 704 | if (!subMenuItems[index].selector) { 705 | subMenuItems[index].selector = 'cc'; 706 | component.extraSettingsEl.removeClass("setting-cc-check"); 707 | component.extraSettingsEl.addClass("setting-cc-checked"); 708 | } else { 709 | subMenuItems[index].selector = ''; 710 | component.extraSettingsEl.removeClass("setting-cc-checked"); 711 | component.extraSettingsEl.addClass("setting-cc-check"); 712 | } 713 | await this.plugin.saveSettings(); 714 | } 715 | }) 716 | .then(cb => { 717 | cb.extraSettingsEl.addClass("setting-cc-check"); 718 | if (subMenuItems[index].selector) { 719 | cb.extraSettingsEl.removeClass("setting-cc-check"); 720 | cb.extraSettingsEl.addClass("setting-cc-checked"); 721 | } 722 | cb.extraSettingsEl.addClass("disabled"); 723 | }); 724 | }) 725 | .addExtraButton((component) => { 726 | component 727 | .then(cb => { 728 | cb.extraSettingsEl.addClass("hidden"); 729 | }); 730 | }) 731 | .addExtraButton((component) => { 732 | component 733 | .then(cb => { 734 | cb.extraSettingsEl.addClass("hidden"); 735 | }); 736 | }) 737 | .addExtraButton((component) => 738 | component 739 | .setIcon("arrow-up") 740 | .onClick(() => { 741 | this.moveSetting(setting, false); 742 | }) 743 | .then(cb => { 744 | cb.extraSettingsEl.addClass("setting-item-controller"); 745 | }) 746 | ) 747 | .addExtraButton((component) => 748 | component 749 | .setIcon("arrow-down") 750 | .onClick(() => { 751 | this.moveSetting(setting, true); 752 | }) 753 | .then(cb => { 754 | cb.extraSettingsEl.addClass("setting-item-controller"); 755 | }) 756 | ) 757 | .addExtraButton((component) => 758 | component 759 | .setIcon('x') 760 | .onClick(async () => { 761 | subMenuItems.splice(index, 1); 762 | await this.plugin.saveSettings(); 763 | this.plugin.patchCanvasMenu(true); 764 | this.display(); 765 | }) 766 | .then(cb => { 767 | cb.extraSettingsEl.addClass("mod-warning"); 768 | }) 769 | ) 770 | .addToggle((toggle) => { 771 | toggle 772 | .setValue(subMenuItems[index].enable) 773 | .onChange(async (value) => { 774 | subMenuItems[index].enable = value; 775 | await this.plugin.saveSettings(); 776 | }); 777 | }) 778 | .then((mn: CanvasStyleMenuSetting) => { 779 | mn.settings = settings; 780 | mn.settingEl.addClass("submenu-item-setting"); 781 | }); 782 | 783 | return setting; 784 | } 785 | 786 | renderCustomIconsSetting(config, icon: CustomIcon) { 787 | const name = icon.name; 788 | const svgContent = icon.svgContent.replace(/^`|`$/g, ''); 789 | const index = config.customIcons.indexOf(icon); 790 | const setting = new CanvasStyleMenuSetting(this.customIconsContainerEl) 791 | .addExtraButton((component) => 792 | component 793 | .setIcon(name) 794 | ) 795 | .addText(text => text 796 | .setPlaceholder('Icon Name') 797 | .setValue(name) 798 | .onChange(async (value) => { 799 | config.customIcons[index].name = value; 800 | await this.plugin.saveSettings(); 801 | })) 802 | .addText(text => text 803 | .setPlaceholder('SVG Content') 804 | .setValue(svgContent) 805 | .onChange(async (value) => { 806 | config.customIcons[index].svgContent = `${value}`; 807 | await this.plugin.saveSettings(); 808 | this.plugin.registerCustomIcons(); 809 | this.display(); 810 | })) 811 | .addExtraButton((component) => 812 | component 813 | .setIcon('x') 814 | .onClick(async () => { 815 | config.customIcons.splice(index, 1); 816 | await this.plugin.saveSettings(); 817 | removeIcon(name); 818 | this.display(); 819 | this.menuItemsTabContainerEl.removeClass("actived"); 820 | this.customIconsTabContainerEl.addClass("actived"); 821 | this.configTabContainerEl.querySelector(".menu-items-tab.actived").removeClass("actived"); 822 | this.configTabContainerEl.querySelector(".custom-icons-tab").addClass("actived"); 823 | }) 824 | .then(cb => { 825 | cb.extraSettingsEl.addClass("mod-warning"); 826 | }) 827 | ); 828 | 829 | return setting; 830 | } 831 | 832 | buildNewMenuItemSetting(containerEl: HTMLElement, onSubmit: (menuItem: MenuItem) => void) { 833 | const menuItem: MenuItem = { 834 | class: '', 835 | type: '', 836 | icon: '', 837 | name: '', 838 | cat: '', 839 | selector: '', 840 | ctxmenu: false, 841 | enable: true, 842 | }; 843 | 844 | const setting = new Setting(containerEl) 845 | .addExtraButton((component) => 846 | component 847 | .setIcon('plus') 848 | .onClick(() => { 849 | onSubmit(menuItem); 850 | }) 851 | .then(cb => { 852 | cb.extraSettingsEl.addClass("mod-cta"); 853 | }) 854 | ) 855 | .addExtraButton((component) => 856 | component 857 | .setIcon('cs-circle-dashed') 858 | .onClick(() => { 859 | const callback = (iconName: string) => { 860 | menuItem.icon = iconName; 861 | }; 862 | new SelectIconModal(this.app, this.plugin, component, 'addNew', null, null, callback).open(); 863 | }) 864 | .then(cb => { 865 | cb.extraSettingsEl.addClass("icon-selector"); 866 | }) 867 | ) 868 | .addText(text => text 869 | .setPlaceholder('CSS Class') 870 | .setValue('') 871 | .onChange((value) => { 872 | menuItem.class = value; 873 | })) 874 | .addText(text => text 875 | .setPlaceholder('Type') 876 | .setValue('') 877 | .onChange((value) => { 878 | menuItem.type = value; 879 | })) 880 | .addText(text => text 881 | .setPlaceholder('Name') 882 | .setValue('') 883 | .onChange((value) => { 884 | menuItem.name = value; 885 | })) 886 | .addExtraButton((component) => { 887 | component 888 | .setIcon("git-commit-horizontal") 889 | .setTooltip("For connection line") 890 | .onClick(() => { 891 | const cc = setting.components.find(obj => obj.extraSettingsEl?.classList.contains("setting-cc-check")); 892 | if (!menuItem.cat && cc) { 893 | menuItem.cat = 'edge'; 894 | component.extraSettingsEl.removeClass("setting-edge-check"); 895 | component.extraSettingsEl.addClass("setting-edge-checked"); 896 | cc.extraSettingsEl.addClass("disabled"); 897 | } else { 898 | menuItem.cat = ''; 899 | component.extraSettingsEl.removeClass("setting-edge-checked"); 900 | component.extraSettingsEl.addClass("setting-edge-check"); 901 | if (cc) cc.extraSettingsEl.removeClass("disabled"); 902 | } 903 | }) 904 | .then(cb => { 905 | cb.extraSettingsEl.addClass("setting-edge-check"); 906 | if (menuItem.cat) { 907 | cb.extraSettingsEl.removeClass("setting-edge-check"); 908 | cb.extraSettingsEl.addClass("setting-edge-checked"); 909 | } 910 | }); 911 | }) 912 | .addExtraButton((component) => { 913 | component 914 | .setIcon("cs-badge-cc") 915 | .setTooltip("Use cssclasses styling") 916 | .onClick(() => { 917 | const edge = setting.components.find(obj => obj.extraSettingsEl?.classList.contains("setting-edge-check")); 918 | if (!menuItem.selector && edge) { 919 | menuItem.selector = 'cc'; 920 | component.extraSettingsEl.removeClass("setting-cc-check"); 921 | component.extraSettingsEl.addClass("setting-cc-checked"); 922 | edge.extraSettingsEl.addClass("disabled"); 923 | } else { 924 | menuItem.selector = ''; 925 | component.extraSettingsEl.removeClass("setting-cc-checked"); 926 | component.extraSettingsEl.addClass("setting-cc-check"); 927 | if (edge) edge.extraSettingsEl.removeClass("disabled"); 928 | } 929 | }) 930 | .then(cb => { 931 | cb.extraSettingsEl.addClass("setting-cc-check"); 932 | if (menuItem.selector) { 933 | cb.extraSettingsEl.removeClass("setting-cc-check"); 934 | cb.extraSettingsEl.addClass("setting-cc-checked"); 935 | } 936 | }); 937 | }) 938 | .addExtraButton((component) => { 939 | component 940 | .then(cb => { 941 | cb.extraSettingsEl.addClass("hidden"); 942 | }); 943 | }) 944 | .addExtraButton((component) => { 945 | component 946 | .then(cb => { 947 | cb.extraSettingsEl.addClass("hidden"); 948 | }); 949 | }) 950 | .addExtraButton((component) => { 951 | component 952 | .then(cb => { 953 | cb.extraSettingsEl.addClass("hidden"); 954 | }); 955 | }) 956 | .addExtraButton((component) => { 957 | component 958 | .then(cb => { 959 | cb.extraSettingsEl.addClass("hidden"); 960 | }); 961 | }) 962 | .addExtraButton((component) => 963 | component 964 | .setIcon('x') 965 | .onClick(() => { 966 | setting.settingEl.parentElement.removeClass("add-setting"); 967 | }) 968 | .then(cb => { 969 | cb.extraSettingsEl.addClass("mod-warning"); 970 | }) 971 | ) 972 | .addToggle(toggle => toggle 973 | .toggleEl.addClass("hidden") 974 | ) 975 | 976 | return setting; 977 | } 978 | 979 | buildNewSubMenuItemSetting(containerEl: HTMLElement, type: string, onSubmit: (subMenuItem: SubMenuItem) => void) { 980 | const subMenuItem: SubMenuItem = { 981 | class: '', 982 | type: type, 983 | icon: '', 984 | name: '', 985 | selector: '', 986 | enable: true, 987 | }; 988 | 989 | const setting = new Setting(containerEl) 990 | .addExtraButton((component) => 991 | component 992 | .setIcon('plus') 993 | .onClick(() => { 994 | onSubmit(subMenuItem); 995 | }) 996 | .then(cb => { 997 | cb.extraSettingsEl.addClass("mod-cta"); 998 | }) 999 | ) 1000 | .addExtraButton((component) => 1001 | component 1002 | .setIcon('cs-circle-dashed') 1003 | .onClick(() => { 1004 | const callback = (iconName: string) => { 1005 | subMenuItem.icon = iconName; 1006 | }; 1007 | new SelectIconModal(this.app, this.plugin, component, 'addNew', null, null, callback).open(); 1008 | }) 1009 | .then(cb => { 1010 | cb.extraSettingsEl.addClass("icon-selector"); 1011 | }) 1012 | ) 1013 | .addText(text => text 1014 | .setPlaceholder('CSS Class') 1015 | .setValue('') 1016 | .onChange((value) => { 1017 | subMenuItem.class = value; 1018 | })) 1019 | .addText(text => text 1020 | .setValue(type) 1021 | .setDisabled(true) 1022 | .inputEl.addClass("hidden") 1023 | ) 1024 | .addText(text => text 1025 | .setPlaceholder('Name') 1026 | .setValue('') 1027 | .onChange((value) => { 1028 | subMenuItem.name = value; 1029 | })) 1030 | .addExtraButton((component) => { 1031 | component 1032 | .then(cb => { 1033 | cb.extraSettingsEl.addClass("hidden"); 1034 | }); 1035 | }) 1036 | .addExtraButton((component) => { 1037 | component 1038 | .setIcon("cs-badge-cc") 1039 | .setTooltip("Use cssclasses styling") 1040 | .onClick(() => { 1041 | if (!subMenuItem.selector) { 1042 | subMenuItem.selector = 'cc'; 1043 | component.extraSettingsEl.addClass("setting-cc-checked"); 1044 | } else { 1045 | subMenuItem.selector = ''; 1046 | component.extraSettingsEl.removeClass("setting-cc-checked"); 1047 | } 1048 | }) 1049 | }) 1050 | .addExtraButton((component) => { 1051 | component 1052 | .then(cb => { 1053 | cb.extraSettingsEl.addClass("hidden"); 1054 | }); 1055 | }) 1056 | .addExtraButton((component) => { 1057 | component 1058 | .then(cb => { 1059 | cb.extraSettingsEl.addClass("hidden"); 1060 | }); 1061 | }) 1062 | .addExtraButton((component) => { 1063 | component 1064 | .then(cb => { 1065 | cb.extraSettingsEl.addClass("hidden"); 1066 | }); 1067 | }) 1068 | .addExtraButton((component) => { 1069 | component 1070 | .then(cb => { 1071 | cb.extraSettingsEl.addClass("hidden"); 1072 | }); 1073 | }) 1074 | .addExtraButton((component) => 1075 | component 1076 | .setIcon('x') 1077 | .onClick(() => { 1078 | setting.settingEl.parentElement.removeClass("add-setting"); 1079 | }) 1080 | .then(cb => { 1081 | cb.extraSettingsEl.addClass("mod-warning"); 1082 | }) 1083 | ) 1084 | .addToggle(toggle => toggle 1085 | .toggleEl.addClass("hidden") 1086 | ) 1087 | 1088 | return setting; 1089 | } 1090 | 1091 | buildNewIconSetting(containerEl: HTMLElement, onSubmit: (icon: CustomIcon) => void) { 1092 | const icon: CustomIcon = { 1093 | name: '', 1094 | svgContent: ``, 1095 | }; 1096 | 1097 | const setting = new Setting(containerEl) 1098 | .addExtraButton((component) => 1099 | component 1100 | .setIcon('plus') 1101 | .onClick(() => { 1102 | onSubmit(icon); 1103 | }) 1104 | .then(cb => { 1105 | cb.extraSettingsEl.addClass("mod-cta"); 1106 | }) 1107 | ) 1108 | .addText(text => text 1109 | .setPlaceholder('Icon Name') 1110 | .setValue('') 1111 | .onChange((value) => { 1112 | icon.name = value; 1113 | })) 1114 | .addText(text => text 1115 | .setPlaceholder('SVG Content') 1116 | .setValue(``) 1117 | .onChange((value) => { 1118 | icon.svgContent = `${value}`; 1119 | })) 1120 | .addExtraButton((component) => 1121 | component 1122 | .setIcon('x') 1123 | .onClick(() => { 1124 | setting.settingEl.parentElement.removeClass("add-setting"); 1125 | }) 1126 | .then(cb => { 1127 | cb.extraSettingsEl.addClass("mod-warning"); 1128 | }) 1129 | ); 1130 | 1131 | return setting; 1132 | } 1133 | 1134 | async saveAsNewPackage(config, packageName: string) { 1135 | this.plugin.settings.configs[packageName] = { 1136 | name: packageName, 1137 | menuItems: [], 1138 | subMenuItems: {}, 1139 | customIcons: [], 1140 | }; 1141 | 1142 | this.plugin.settings.savedConfigs[packageName] = { 1143 | name: packageName, 1144 | menuItems: [], 1145 | subMenuItems: {}, 1146 | customIcons: [], 1147 | }; 1148 | 1149 | this.plugin.settings.configs[packageName].menuItems = config.menuItems; 1150 | this.plugin.settings.configs[packageName].subMenuItems = config.subMenuItems; 1151 | this.plugin.settings.configs[packageName].customIcons = config.customIcons; 1152 | 1153 | this.plugin.settings.savedConfigs[packageName].menuItems = JSON.parse(JSON.stringify(config.menuItems)); 1154 | this.plugin.settings.savedConfigs[packageName].subMenuItems = JSON.parse(JSON.stringify(config.subMenuItems)); 1155 | this.plugin.settings.savedConfigs[packageName].customIcons = JSON.parse(JSON.stringify(config.customIcons)); 1156 | 1157 | this.plugin.settings.currentConfig = packageName; 1158 | await this.plugin.saveSettings(); 1159 | this.plugin.savedSettings(); 1160 | this.plugin.registerCustomIcons(); 1161 | this.plugin.patchCanvasMenu(true); 1162 | this.display(); 1163 | } 1164 | 1165 | async moveSetting(setting: CanvasStyleMenuSetting, isMoveDown: boolean) { 1166 | const settings = setting.settings; 1167 | if (settings instanceof Array) { 1168 | const settingEl = setting.settingEl; 1169 | 1170 | const parentEl = settingEl.parentElement; 1171 | 1172 | if (parentEl == null) return; 1173 | 1174 | const index = Array.from(parentEl.children).indexOf(settingEl); 1175 | 1176 | if (isMoveDown) { 1177 | if (index == settings.length - 1) return; 1178 | 1179 | parentEl.insertAfter(settingEl, settingEl.nextElementSibling); 1180 | settings.splice(index + 1, 0, settings.splice(index, 1)[0]); 1181 | } else { 1182 | if (index <= 0) return; 1183 | 1184 | parentEl.insertBefore(settingEl, settingEl.previousElementSibling); 1185 | settings.splice(index - 1, 0, settings.splice(index, 1)[0]); 1186 | } 1187 | 1188 | await this.plugin.saveSettings(); 1189 | this.plugin.patchCanvasMenu(); 1190 | this.display(); 1191 | } 1192 | } 1193 | 1194 | removePreviousIcons(currentConfig: string, savedConfigs) { 1195 | const previousConfig = currentConfig.slice(); 1196 | let previousIcons = []; 1197 | if (currentConfig in savedConfigs) { 1198 | previousIcons = this.plugin.settings.savedConfigs[previousConfig].customIcons.map(icon => icon.name); 1199 | } else { 1200 | previousIcons = this.plugin.settings.configs[previousConfig].customIcons.map(icon => icon.name); 1201 | } 1202 | previousIcons.forEach((icon) => { 1203 | removeIcon(icon); 1204 | }); 1205 | } 1206 | } 1207 | 1208 | class SelectIconModal extends Modal { 1209 | constructor(app: App, plugin: CanvasStyleMenuPlugin, component: HTMLElement, container: string, config, index, callback: (iconName: string) => Promise) { 1210 | super(app, plugin); 1211 | this.plugin = plugin; 1212 | this.component = component; 1213 | this.container = container; 1214 | this.config = config; 1215 | this.index = index; 1216 | this.callback = callback; 1217 | } 1218 | 1219 | onOpen() { 1220 | const {contentEl} = this; 1221 | const iconList = contentEl.createDiv('icon-list-container', (el) => { 1222 | el.createDiv( 1223 | { 1224 | cls: 'clickable-icon cs-icons actived', 1225 | attr: { 1226 | 'data-label': 'csIcons', 1227 | }, 1228 | }, 1229 | (item) => { 1230 | setIcon(item, "shapes"); 1231 | item.onClickEvent(() => { 1232 | if (el.querySelector(".builtin-icons.actived")) { 1233 | el.querySelector(".builtin-icons.actived").removeClass("actived"); 1234 | }; 1235 | item.addClass("actived"); 1236 | if (this.contentEl.querySelector(".builtin-icons-container.actived")) { 1237 | this.contentEl.querySelector(".builtin-icons-container.actived").removeClass("actived"); 1238 | }; 1239 | this.contentEl.querySelector(".cs-icons-container").addClass("actived"); 1240 | }); 1241 | item.createEl("div", { text: "Custom Icons", cls: 'icon-list-name' }); 1242 | } 1243 | ); 1244 | el.createDiv( 1245 | { 1246 | cls: 'clickable-icon builtin-icons', 1247 | attr: { 1248 | 'data-label': 'built-in Icons', 1249 | }, 1250 | }, 1251 | (item) => { 1252 | setIcon(item, "blocks"); 1253 | item.onClickEvent(() => { 1254 | if (el.querySelector(".cs-icons.actived")) { 1255 | el.querySelector(".cs-icons.actived").removeClass("actived"); 1256 | }; 1257 | item.addClass("actived"); 1258 | if (this.contentEl.querySelector(".cs-icons-container.actived")) { 1259 | this.contentEl.querySelector(".cs-icons-container.actived").removeClass("actived"); 1260 | }; 1261 | this.contentEl.querySelector(".builtin-icons-container").addClass("actived"); 1262 | }); 1263 | item.createEl("div", { text: "Built-in Icons", cls: 'icon-list-name' }); 1264 | } 1265 | ); 1266 | }); 1267 | 1268 | const csIcons = this.createIconContainer('cs-icons-container actived', csIconList); 1269 | const builtInIcons = this.createIconContainer('builtin-icons-container', getIconIds().filter(item => !csIconList.includes(item))); 1270 | } 1271 | 1272 | onClose() { 1273 | const {contentEl} = this; 1274 | contentEl.empty(); 1275 | } 1276 | 1277 | createIconContainer(containerId: string, iconList: string[]) { 1278 | const container = this.contentEl.createDiv(containerId, (el) => { 1279 | iconList.forEach((icon) => { 1280 | el.createDiv( 1281 | { 1282 | cls: 'clickable-icon', 1283 | attr: { 1284 | 'data-icon': icon, 1285 | }, 1286 | }, 1287 | (item) => { 1288 | setIcon(item, icon); 1289 | setTooltip(item, icon); 1290 | item.onClickEvent(async () => { 1291 | if (this.container === 'menuItem') { 1292 | this.config.menuItems[this.index].icon = icon; 1293 | await this.plugin.saveSettings(); 1294 | } 1295 | if (this.container === 'subMenuItem') { 1296 | this.config[this.index].icon = icon; 1297 | await this.plugin.saveSettings(); 1298 | } 1299 | if (this.container === 'addNew') { 1300 | await this.callback(icon) 1301 | } 1302 | this.component.setIcon(icon); 1303 | this.close(); 1304 | }); 1305 | } 1306 | ); 1307 | }); 1308 | }); 1309 | 1310 | return container; 1311 | } 1312 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { App, Canvas, CanvasNode, Menu, MenuItem } from "obsidian"; 4 | import CanvasStyleMenuPlugin from "./main"; 5 | 6 | const handleMultiNodes = (canvas: Canvas, allNodes: boolean, subMenuConfig: SubMenuItem[], menuItemType: string, cssClass: string, onRightClick: boolean) => { 7 | const nodes = allNodes ? Array.from(canvas.nodes.values()) : Array.from(canvas.selection) as any[]; 8 | const canvasData = canvas.getData(); 9 | if (nodes && nodes.length > 0) { 10 | for (const node of nodes) { 11 | const nodeData = canvasData.nodes.find((t: any) => t.id === node.id); 12 | if (subMenuConfig === null) { 13 | property = menuItemType; 14 | } else property = generateClassToPropertyMap(subMenuConfig, cssClass); 15 | 16 | if (property && nodeData) { 17 | if (onRightClick) { 18 | // nodeData[property] = false; //Keep the properties in the canvas file 19 | delete nodeData[property]; //Remove the corresponding property from the canvas file 20 | } else nodeData[property] = cssClass; 21 | } 22 | } 23 | canvas.setData(canvasData); 24 | } 25 | canvas.requestSave(true, true); 26 | canvas.requestFrame(); 27 | }; 28 | 29 | export const handleMultiNodesViaNodes = (canvas: Canvas, nodes: CanvasNode[], subMenuConfig: SubMenuItem[], menuItemType: string, cssClass: string, onRightClick: boolean) => { 30 | const canvasData = canvas.getData(); 31 | if (nodes && nodes.length > 0) { 32 | for (const node of nodes) { 33 | const nodeData = canvasData.nodes.find((t: any) => t.id === node.id); 34 | if (subMenuConfig === null) { 35 | property = menuItemType; 36 | } else property = generateClassToPropertyMap(subMenuConfig, cssClass); 37 | 38 | if (property && nodeData) { 39 | if (onRightClick) { 40 | // nodeData[property] = false; //Keep the properties in the canvas file 41 | delete nodeData[property]; //Remove the corresponding property from the canvas file 42 | } else nodeData[property] = cssClass; 43 | } 44 | } 45 | canvas.setData(canvasData); 46 | } 47 | canvas.requestSave(true, true); 48 | }; 49 | 50 | export const handleSingleNode = (node: CanvasNode, subMenuConfig: SubMenuItem[], menuItemType: string, cssClass: string, onRightClick: boolean) => { 51 | const canvasData = node.canvas.getData(); 52 | const nodeData = canvasData.nodes.find((t: any) => t.id === node.id); 53 | const edgeData = canvasData.edges.find((t: any) => t.id === node.id); 54 | if (subMenuConfig === null) { 55 | property = menuItemType; 56 | } else property = generateClassToPropertyMap(subMenuConfig, cssClass); 57 | 58 | if (property && nodeData) { 59 | if (onRightClick) { 60 | // nodeData[property] = false; //Keep the properties in the canvas file 61 | delete nodeData[property]; //Remove the corresponding property from the canvas file 62 | } else nodeData[property] = cssClass; 63 | } 64 | if (property && edgeData) { 65 | if (onRightClick) { 66 | // edgeData[property] = false; //Keep the properties in the canvas file 67 | delete edgeData[property]; //Remove the corresponding property from the canvas file 68 | } else edgeData[property] = cssClass; 69 | } 70 | node.canvas.setData(canvasData); 71 | node.canvas.requestSave(true, true); 72 | }; 73 | 74 | const createHandleContextMenu = (section: string, menuConfig: MenuItem[], subMenuConfig: SubMenuItem[], toggleMenu: string[], callback: (cssClass: string) => Promise) => { 75 | return (menu: Menu) => { 76 | menuConfig.forEach((menuItem) => { 77 | if (toggleMenu.includes(menuItem.type) && menuItem.cat !== 'edge' && menuItem.ctxmenu === true && menuItem.enable === true) { 78 | menu.addItem((item: MenuItem) => { 79 | item 80 | .setIcon(menuItem.icon) 81 | .setTitle(menuItem.name) 82 | .onClick(async () => { 83 | await callback(menuItem.class); 84 | }); 85 | }); 86 | } 87 | if (!toggleMenu.includes(menuItem.type) && menuItem.cat !== 'edge' && menuItem.ctxmenu === true && menuItem.enable === true) { 88 | menu.addItem((item: MenuItem) => { 89 | const subMenu = item.setSection(section).setTitle(menuItem.name).setIcon(menuItem.icon).setSubmenu(); 90 | handleMenu(subMenu, subMenuConfig, callback, menuItem.type); 91 | }); 92 | } 93 | }); 94 | }; 95 | }; 96 | 97 | export const handleMenu = (subMenu: Menu, subMenuConfig: SubMenuItem[], callback: (cssClass: string) => Promise, type: string) => { 98 | const filteredMenuItems = subMenuConfig.filter((item) => item.type === type) || []; 99 | filteredMenuItems.forEach((menuItem) => { 100 | if (menuItem.enable === true) { 101 | subMenu.addItem((item: MenuItem) => { 102 | item 103 | .setIcon(menuItem.icon) 104 | .setTitle(menuItem.name) 105 | .onClick(async () => { 106 | await callback(menuItem.class); 107 | }); 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | export const handleSelectionContextMenu = (plugin: CanvasStyleMenuPlugin, menu: Menu, canvas: Canvas, menuConfig: MenuItem[], subMenuConfig: SubMenuItem[], toggleMenu: string[], menuItemType: string) => { 114 | const callback = async (cssClass: string) => { 115 | handleMultiNodes(canvas, false, subMenuConfig, menuItemType, cssClass, false); 116 | }; 117 | createHandleContextMenu('action', menuConfig, subMenuConfig, toggleMenu, callback)(menu); 118 | }; 119 | 120 | export const handleNodeContextMenu = (plugin: CanvasStyleMenuPlugin, menu: Menu, node: CanvasNode, menuConfig: MenuItem[], subMenuConfig: SubMenuItem[], toggleMenu: string[], menuItemType: string) => { 121 | const callback = async (cssClass: string) => { 122 | handleSingleNode(node, subMenuConfig, menuItemType, cssClass, false); 123 | }; 124 | createHandleContextMenu('canvas', menuConfig, subMenuConfig, toggleMenu, callback)(menu); 125 | }; 126 | 127 | export const refreshAllCanvasView = (app: App) => { 128 | const cavasLeaves = app.workspace.getLeavesOfType("canvas"); 129 | if (!cavasLeaves || cavasLeaves.length === 0) return; 130 | for (const leaf of cavasLeaves) { 131 | leaf.rebuildView(); 132 | } 133 | }; 134 | 135 | export function parseOldSettingsItems(items: string[] | undefined): void { 136 | if (items) { 137 | // Replace single quotes with double quotes, and remove extra spaces 138 | const formattedStrings = items.map(str => 139 | str.replace(/'/g, '"').replace(/([\w-]+):/g, '"$1":').replace(/'/g, '"') 140 | ); 141 | // Convert a string to a JSON object 142 | const jsonObjectArray = formattedStrings.map(str => JSON.parse(str)); 143 | return jsonObjectArray; 144 | } 145 | } 146 | 147 | export function setAttributes(element: any, attributes: any) { 148 | for (let key in attributes) { 149 | element.setAttribute(key, attributes[key]); 150 | } 151 | } 152 | 153 | export function toObjectArray(array: MenuItem[]) { 154 | return array.map((str) => { 155 | const matches = str.match(/\{(.+?)\}/); 156 | const objStr = `{${matches[1]}}`; 157 | return new Function(`return ${objStr}`)(); 158 | }); 159 | } 160 | 161 | export function generateClassToPropertyMap(subMenuConfig: SubMenuItem[], cssClass: string): { [key: string]: string } { 162 | const classToPropertyMap: { [key: string]: string } = {}; 163 | subMenuConfig.forEach((subMenuItem) => { 164 | if (subMenuItem.type && !classToPropertyMap[subMenuItem.class]) { 165 | classToPropertyMap[subMenuItem.class] = subMenuItem.type; 166 | } 167 | }); 168 | 169 | return classToPropertyMap[cssClass]; 170 | } 171 | 172 | export const getToggleMenuItemsClass = (types: string[], items: MenuItem[]): (string | null)[] => { 173 | return types.map((type) => { 174 | const toggleMenuItem = items.find((item) => item.type === type); 175 | return toggleMenuItem ? toggleMenuItem.class : null; 176 | }); 177 | }; 178 | 179 | export const getItemProperty = (cssClass: string, items: MenuItem[], property: string): string | null => { 180 | const menuItem = items.find((item) => item.class === cssClass); 181 | return menuItem ? menuItem[property] : null; 182 | }; 183 | 184 | export async function modifyClassOnElements(addOrRemove: string, contentEl: HTMLElement, className: string, propertyValue: string): Promise { 185 | await new Promise(resolve => setTimeout(resolve, 1)); 186 | const elements = contentEl.getElementsByClassName(className) as HTMLCollectionOf; 187 | 188 | for (let i = 0; i < elements.length; i++) { 189 | const element = elements[i]; 190 | if (addOrRemove === 'add') { 191 | element.classList.add(propertyValue); 192 | } 193 | if (addOrRemove === 'remove') { 194 | element.classList.remove(propertyValue); 195 | } 196 | } 197 | } 198 | 199 | export function sortByProperty(array: T[], property: keyof T): T[] { 200 | const grouped: { [key: string]: T[] } = {}; 201 | 202 | // Grouping based on incoming attributes 203 | for (const item of array) { 204 | const key = item[property]; 205 | if (!grouped[key]) { 206 | grouped[key] = []; 207 | } 208 | grouped[key].push(item); 209 | } 210 | 211 | // Merge the sorted arrays in the order of the original arrays 212 | const result = Object.values(grouped).reduce((acc, current) => acc.concat(current), []); 213 | 214 | return result; 215 | } 216 | 217 | export function groupItemsByProperty(items: Item[], property: string): { [key: string]: Item[] } { 218 | const groupedItems: { [key: string]: Item[] } = {}; 219 | 220 | items.forEach((item) => { 221 | const propValue = item[property]; 222 | 223 | if (!groupedItems[propValue]) { 224 | groupedItems[propValue] = []; 225 | } 226 | 227 | groupedItems[propValue].push(item); 228 | }); 229 | 230 | return groupedItems; 231 | } 232 | 233 | export function transformSubMenuItems(subMenuItems: SubMenuItems): TransformedSubMenuItems[] { 234 | const transformedSubMenuItems: TransformedSubMenuItems[] = []; 235 | 236 | for (const type in subMenuItems) { 237 | if (subMenuItems.hasOwnProperty(type)) { 238 | const items = subMenuItems[type]; 239 | transformedSubMenuItems.push(...items.map(item => ({ ...item, type }))); 240 | } 241 | } 242 | 243 | return transformedSubMenuItems; 244 | } 245 | 246 | export function renameKey(obj: Record, oldKey: string, newKey: string): void { 247 | // Check if the key to be modified exists 248 | if (obj.hasOwnProperty(oldKey)) { 249 | // Creates a new object, keeping the reference to the original object, but changing the key name to newKey. 250 | obj[newKey] = obj[oldKey]; 251 | 252 | // Delete old key-value pairs 253 | delete obj[oldKey]; 254 | } 255 | } 256 | 257 | export function getFilesInDirectory(directoryPath: string): string[] { 258 | const filesAndFolders = fs.readdirSync(directoryPath); 259 | const files = filesAndFolders.filter(fileOrFolder => { 260 | const fullPath = path.join(directoryPath, fileOrFolder); 261 | return fs.statSync(fullPath).isFile(); 262 | }); 263 | 264 | return files; 265 | } 266 | 267 | export function createElbowPath(from: string, to: string, x1: number, y1: number, x2: number, y2: number): string { 268 | if (from === 'left' && to === 'right') { 269 | if (x1 > x2) { 270 | const control1X = x1 - (x1 - x2) / 2; 271 | const control1Y = y1; 272 | const control2X = control1X; 273 | const control2Y = y2; 274 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 275 | return pathData; 276 | } 277 | if (x1 < x2) { 278 | const control1X = x1 - 50; 279 | const control1Y = y1; 280 | const control2X = control1X; 281 | const control2Y = y1 + (y2 - y1) / 2; 282 | const control3X = x2 + 50; 283 | const control3Y = control2Y; 284 | const control4X = control3X; 285 | const control4Y = y2; 286 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${control3Y} H${control4X} V${y2} H${x2}`; 287 | //const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${control3Y} H${control4X} V${control4Y} H${x2}`; 288 | return pathData; 289 | } 290 | } 291 | if (from === 'right' && to === 'left') { 292 | if (x1 < x2) { 293 | const control1X = x1 + (x2 - x1) / 2; 294 | const control1Y = y1; 295 | const control2X = control1X; 296 | const control2Y = y2; 297 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 298 | return pathData; 299 | } 300 | if (x1 > x2) { 301 | const control1X = x1 + 30; 302 | const control1Y = y1; 303 | const control2X = control1X; 304 | const control2Y = y1 - (y1 - y2) / 2; 305 | const control3X = x2 - 30; 306 | const control3Y = control2Y; 307 | const control4X = control3X; 308 | const control4Y = y2; 309 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${control3Y} H${control4X} V${y2} H${x2}`; 310 | return pathData; 311 | } 312 | } 313 | if (from === 'right' && to === 'right') { 314 | if (x1 >= x2) { 315 | const control1X = x1 + 50; 316 | const control1Y = y1; 317 | const control2X = control1X; 318 | const control2Y = y2; 319 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 320 | return pathData; 321 | } 322 | if (x1 < x2) { 323 | const control1X = x2 + 50; 324 | const control1Y = y1; 325 | const control2X = control1X; 326 | const control2Y = y2; 327 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 328 | return pathData; 329 | } 330 | } 331 | if (from === 'left' && to === 'left') { 332 | if (x1 >= x2) { 333 | const control1X = x2 - 50; 334 | const control1Y = y1; 335 | const control2X = control1X; 336 | const control2Y = y2; 337 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 338 | return pathData; 339 | } 340 | if (x1 < x2) { 341 | const control1X = x1 - 50; 342 | const control1Y = y1; 343 | const control2X = control1X; 344 | const control2Y = y2; 345 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 346 | return pathData; 347 | } 348 | } 349 | if (from === 'top' && to === 'bottom') { 350 | if (y1 > y2) { 351 | const control1X = x1; 352 | const control1Y = y1 - (y1 - y2) / 2; 353 | const control2X = x2; 354 | const control2Y = control1Y; 355 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 356 | return pathData; 357 | } 358 | if (y1 < y2) { 359 | const control1X = x1; 360 | const control1Y = y1 - 50 ; 361 | const control2X = x1 + (x2 - x1) / 2; 362 | const control2Y = control1Y; 363 | const control3X = control2X; 364 | const control3Y = y2 + 50; 365 | const control4X = x2; 366 | const control4Y = control3Y; 367 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${control3Y} H${control4X} V${y2} H${x2}`; 368 | return pathData; 369 | } 370 | } 371 | if (from === 'bottom' && to === 'top') { 372 | if (y1 < y2) { 373 | const control1X = x1; 374 | const control1Y = y1 + (y2 - y1) / 2; 375 | const control2X = x2; 376 | const control2Y = control1Y; 377 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 378 | return pathData; 379 | } 380 | if (y1 > y2) { 381 | const control1X = x1; 382 | const control1Y = y1 + 50 ; 383 | const control2X = x1 - (x1 - x2) / 2; 384 | const control2Y = control1Y; 385 | const control3X = control2X; 386 | const control3Y = y2 - 50; 387 | const control4X = x2; 388 | const control4Y = control3Y; 389 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${control3Y} H${control4X} V${y2} H${x2}`; 390 | return pathData; 391 | } 392 | } 393 | if (from === 'top' && to === 'top') { 394 | if (y1 >= y2) { 395 | const control1X = x1; 396 | const control1Y = y2 - 50; 397 | const control2X = x2; 398 | const control2Y = control1Y; 399 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 400 | return pathData; 401 | } 402 | if (y1 < y2) { 403 | const control1X = x1; 404 | const control1Y = y1 - 50; 405 | const control2X = x2; 406 | const control2Y = control1Y; 407 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 408 | return pathData; 409 | } 410 | } 411 | if (from === 'bottom' && to === 'bottom') { 412 | if (y1 >= y2) { 413 | const control1X = x1; 414 | const control1Y = y1 + 50; 415 | const control2X = x2; 416 | const control2Y = control1Y; 417 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 418 | return pathData; 419 | } 420 | if (y1 < y2) { 421 | const control1X = x1; 422 | const control1Y = y2 + 50; 423 | const control2X = x2; 424 | const control2Y = control1Y; 425 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${y2} H${x2}`; 426 | return pathData; 427 | } 428 | } 429 | if (from === 'left' && to === 'top') { 430 | if (x1 > x2 && y1 < y2) { 431 | const controlX = x2; 432 | const controlY = y2; 433 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 434 | return pathData; 435 | } 436 | if ((x1 > x2 && y1 > y2) || (x1 < x2)) { 437 | const control1X = x1 - 50; 438 | const control1Y = y1; 439 | const control2X = control1X; 440 | const control2Y = y2 - 50; 441 | const control3X = x2; 442 | const control3Y = control2Y; 443 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 444 | return pathData; 445 | } 446 | } 447 | if (from === 'top' && to === 'left') { 448 | if (x1 < x2 && y1 > y2) { 449 | const controlX = x1; 450 | const controlY = y2; 451 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 452 | return pathData; 453 | } 454 | if ((x1 < x2 && y1 < y2) || (x1 > x2)) { 455 | const control1X = x1; 456 | const control1Y = y1 - 50; 457 | const control2X = x2 - 50; 458 | const control2Y = control1Y; 459 | const control3X = control2X; 460 | const control3Y = y2; 461 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 462 | return pathData; 463 | } 464 | } 465 | if (from === 'right' && to === 'top') { 466 | if (x1 < x2 && y1 < y2) { 467 | const controlX = x2; 468 | const controlY = y2; 469 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 470 | return pathData; 471 | } 472 | if ((x1 < x2 && y1 > y2) || (x1 > x2)) { 473 | const control1X = x1 + 50; 474 | const control1Y = y1; 475 | const control2X = control1X; 476 | const control2Y = y2 - 50; 477 | const control3X = x2; 478 | const control3Y = control2Y; 479 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 480 | return pathData; 481 | } 482 | } 483 | if (from === 'top' && to === 'right') { 484 | if (x1 > x2 && y1 > y2) { 485 | const controlX = x1; 486 | const controlY = y2; 487 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 488 | return pathData; 489 | } 490 | if ((x1 > x2 && y1 < y2) || (x1 < x2)) { 491 | const control1X = x1; 492 | const control1Y = y1 - 50; 493 | const control2X = x2 + 50; 494 | const control2Y = control1Y; 495 | const control3X = control2X; 496 | const control3Y = y2; 497 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 498 | return pathData; 499 | } 500 | } 501 | if (from === 'left' && to === 'bottom') { 502 | if (x1 > x2 && y1 > y2) { 503 | const controlX = x2; 504 | const controlY = y2; 505 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 506 | return pathData; 507 | } 508 | if ((x1 > x2 && y1 < y2) || (x1 < x2)) { 509 | const control1X = x1 - 50; 510 | const control1Y = y1; 511 | const control2X = control1X; 512 | const control2Y = y2 + 50; 513 | const control3X = x2; 514 | const control3Y = control2Y; 515 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 516 | return pathData; 517 | } 518 | } 519 | if (from === 'bottom' && to === 'left') { 520 | if (x1 < x2 && y1 < y2) { 521 | const controlX = x1; 522 | const controlY = y2; 523 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 524 | return pathData; 525 | } 526 | if ((x1 < x2 && y1 > y2) || (x1 > x2)) { 527 | const control1X = x1; 528 | const control1Y = y1 + 50; 529 | const control2X = x2 - 50; 530 | const control2Y = control1Y; 531 | const control3X = control2X; 532 | const control3Y = y2; 533 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 534 | return pathData; 535 | } 536 | } 537 | if (from === 'right' && to === 'bottom') { 538 | if (x1 < x2 && y1 > y2) { 539 | const controlX = x2; 540 | const controlY = y2; 541 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 542 | return pathData; 543 | } 544 | if ((x1 < x2 && y1 < y2) || (x1 > x2)) { 545 | const control1X = x1 + 50; 546 | const control1Y = y1; 547 | const control2X = control1X; 548 | const control2Y = y2 + 50; 549 | const control3X = x2; 550 | const control3Y = control2Y; 551 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 552 | return pathData; 553 | } 554 | } 555 | if (from === 'bottom' && to === 'right') { 556 | if (x1 > x2 && y1 < y2) { 557 | const controlX = x1; 558 | const controlY = y2; 559 | const pathData = `M${x1},${y1} H${controlX} V${controlY} H${x2}`; 560 | return pathData; 561 | } 562 | if ((x1 > x2 && y1 > y2) || (x1 < x2)) { 563 | const control1X = x1; 564 | const control1Y = y1 + 50; 565 | const control2X = x2 + 50; 566 | const control2Y = control1Y; 567 | const control3X = control2X; 568 | const control3Y = y2; 569 | const pathData = `M${x1},${y1} H${control1X} V${control1Y} H${control2X} V${control2Y} H${control3X} V${y2} H${x2}`; 570 | return pathData; 571 | } 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* cs-border-none */ 2 | .canvas-node.cs-border-none .canvas-node-container { 3 | border: none; 4 | box-shadow: none; 5 | } 6 | .canvas-node.cs-border-none.is-themed .canvas-node-container { 7 | box-shadow: none; 8 | } 9 | .canvas-node.cs-border-none .canvas-node-container:hover { 10 | border: 2px dashed rgba(var(--canvas-color), 0.9); 11 | } 12 | .canvas-node.cs-border-none.is-selected .canvas-node-container, 13 | .canvas-node.cs-border-none.is-focused .canvas-node-container { 14 | border: 4px dashed var(--background-modifier-border-focus); 15 | box-shadow: none; 16 | } 17 | .canvas-node.cs-border-none.is-themed.is-selected .canvas-node-container, 18 | .canvas-node.cs-border-none.is-themed.is-focused .canvas-node-container { 19 | border: 4px dashed rgb(var(--canvas-color)); 20 | box-shadow: none; 21 | } 22 | /* cs-border-dashed */ 23 | .canvas-node.cs-border-dashed .canvas-node-container { 24 | border: 2px dashed rgba(var(--canvas-color), 0.5); 25 | box-shadow: none; 26 | } 27 | .canvas-node.cs-border-dashed.is-themed .canvas-node-container { 28 | border: 2px dashed rgba(var(--canvas-color), 0.5); 29 | box-shadow: none; 30 | } 31 | .canvas-node.cs-border-dashed .canvas-node-container:hover { 32 | border: 2px dashed rgb(var(--canvas-color)); 33 | } 34 | .canvas-node.cs-border-dashed.is-themed .canvas-node-container:hover { 35 | border: 2px dashed rgba(var(--canvas-color), 0.9); 36 | } 37 | .canvas-node.cs-border-dashed.is-selected .canvas-node-container, 38 | .canvas-node.cs-border-dashed.is-focused .canvas-node-container { 39 | border: 4px dashed var(--background-modifier-border-focus); 40 | box-shadow: none; 41 | } 42 | .canvas-node.cs-border-dashed.is-themed.is-selected .canvas-node-container, 43 | .canvas-node.cs-border-dashed.is-themed.is-focused .canvas-node-container { 44 | border: 4px dashed rgb(var(--canvas-color)); 45 | box-shadow: none; 46 | } 47 | 48 | /* cs-bg-transparent */ 49 | .canvas-node.cs-bg-transparent .canvas-node-container { 50 | background-color: transparent; 51 | } 52 | /* cs-bg-opacity-0 */ 53 | .canvas-node.cs-bg-opacity-0 .canvas-node-container { 54 | background-color: transparent; 55 | } 56 | .canvas-node.cs-bg-opacity-0.is-themed .canvas-node-content { 57 | background-color: rgba(var(--canvas-color), 0); 58 | } 59 | 60 | /* cs-rotate-right-45 */ 61 | .canvas-node.cs-rotate-right-45 .canvas-node-container { 62 | transform: rotate(45deg); 63 | } 64 | /* cs-rotate-right-90 */ 65 | .canvas-node.cs-rotate-right-90 .canvas-node-container { 66 | transform: rotate(90deg); 67 | } 68 | /* cs-rotate-left-45 */ 69 | .canvas-node.cs-rotate-left-45 .canvas-node-container { 70 | transform: rotate(-45deg); 71 | } 72 | /* cs-rotate-left-90 */ 73 | .canvas-node.cs-rotate-left-90 .canvas-node-container { 74 | transform: rotate(-90deg); 75 | } 76 | 77 | .canvas-node .canvas-node-label { 78 | display: none; 79 | } 80 | .canvas-node.is-editing .canvas-node-container { 81 | transform: rotate(0); 82 | } 83 | 84 | /* cs-highlight */ 85 | .canvas-node.cs-highlight .canvas-node-container { 86 | box-shadow: 0 0 5px 5px rgb(var(--canvas-color)) !important; 87 | } 88 | 89 | /* cs-shape-circle */ 90 | .canvas-node.cs-shape-circle .canvas-node-container { 91 | border-radius: 50%; 92 | } 93 | .canvas-node.cs-shape-circle .canvas-node-container .markdown-preview-section { 94 | display: flex; 95 | justify-content: center; 96 | align-items: center; 97 | } 98 | /* cs-shape-parallelogram-right */ 99 | .canvas-node.cs-shape-parallelogram-right .canvas-node-container { 100 | transform: skew(-20deg); 101 | } 102 | .canvas-node.cs-shape-parallelogram-right .canvas-node-container .markdown-preview-section { 103 | transform: skew(20deg); 104 | } 105 | /* cs-shape-parallelogram-left */ 106 | .canvas-node.cs-shape-parallelogram-left .canvas-node-container { 107 | transform: skew(20deg); 108 | } 109 | .canvas-node.cs-shape-parallelogram-left .canvas-node-container .markdown-preview-section { 110 | transform: skew(-20deg); 111 | } 112 | 113 | .canvas-node.cs-shape-parallelogram-right .canvas-node-container .markdown-preview-section, 114 | .canvas-node.cs-shape-parallelogram-left .canvas-node-container .markdown-preview-section { 115 | display: flex; 116 | justify-content: center; 117 | align-items: center; 118 | } 119 | .canvas-node.cs-shape-parallelogram-right.is-editing .canvas-node-container, 120 | .canvas-node.cs-shape-parallelogram-left.is-editing .canvas-node-container { 121 | transform: skew(0); 122 | } 123 | 124 | .canvas-node.is-editing .canvas-node-container { 125 | border-radius: var(--radius-m); 126 | } 127 | 128 | /* cs-line-dashed */ 129 | .canvas-edges g.cs-line-dashed { 130 | stroke-dasharray: 10; 131 | } 132 | 133 | /* cs-line-dashed-round */ 134 | .canvas-edges g.cs-line-dashed-round { 135 | stroke-dasharray: 15; 136 | stroke-linecap: round; 137 | } 138 | 139 | /* cs-line-dotted */ 140 | .canvas-edges g.cs-line-dotted { 141 | stroke-dasharray: 5; 142 | } 143 | 144 | /* cs-line-dotted-line */ 145 | .canvas-edges g.cs-line-dotted-line { 146 | stroke-dasharray: 20 5 5 5; /* double-dotted-line 20 10 5 5 5 10 */ 147 | } 148 | 149 | /* cs-line-thick */ 150 | .canvas-edges g.cs-line-thick .canvas-display-path { 151 | stroke-width: calc(5.5px * var(--zoom-multiplier)); 152 | } 153 | .canvas-edges g.cs-line-thick.is-focused path.canvas-display-path, 154 | .canvas:not(.is-connecting) .canvas-edges g.cs-line-thick:hover path.canvas-display-path { 155 | stroke-width: calc(8px * var(--zoom-multiplier)); 156 | } 157 | .canvas-edges g.cs-line-thick polygon.canvas-path-end { 158 | transform: scale(calc(1.5 * var(--zoom-multiplier))); 159 | } 160 | 161 | /* cs-line-thicker */ 162 | .canvas-edges g.cs-line-thicker .canvas-display-path { 163 | stroke-width: calc(8.5px * var(--zoom-multiplier)); 164 | } 165 | .canvas-edges g.cs-line-thicker.is-focused path.canvas-display-path, 166 | .canvas:not(.is-connecting) .canvas-edges g.cs-line-thicker:hover path.canvas-display-path { 167 | stroke-width: calc(11px * var(--zoom-multiplier)); 168 | } 169 | .canvas-edges g.cs-line-thicker polygon.canvas-path-end { 170 | transform: scale(calc(2 * var(--zoom-multiplier))); 171 | } 172 | 173 | .canvas-edges path.canvas-path-end, 174 | .canvas-edges polygon.canvas-path-end { 175 | stroke: unset; 176 | } 177 | 178 | 179 | 180 | 181 | /* for plugings */ 182 | .menu-items-tab-container .clickable-icon:empty, 183 | .custom-icons-tab-container .clickable-icon:empty { 184 | width: 26px; 185 | height: 26px; 186 | } 187 | .settings-header-container input[type='text'] { 188 | width: 135px; 189 | } 190 | 191 | .config-tab-container .setting-item { 192 | padding-top: 0.75em; 193 | } 194 | 195 | .config-tab-container .menu-items-tab::after { 196 | content: attr(data-label); 197 | padding: var(--size-4-1); 198 | } 199 | .config-tab-container .custom-icons-tab::after { 200 | content: attr(data-label); 201 | padding: var(--size-4-1); 202 | } 203 | 204 | .custom-icons-container input[type='text'], 205 | .menu-items-container input[type='text'] { 206 | width: 110px; 207 | border: none; 208 | padding: var(--size-4-1) var(--size-2-2); 209 | background-color: transparent; 210 | } 211 | .menu-items-container .setting-edge-check::after { 212 | content: attr(data-label); 213 | } 214 | .menu-items-container .setting-cc-check::after { 215 | content: attr(data-label); 216 | } 217 | 218 | .menu-items-tab-container .setting-item.mod-toggle { 219 | flex-wrap: wrap; 220 | } 221 | .submenu-items-container { 222 | background-color: var(--background-primary-alt); 223 | flex-basis: 100%; 224 | flex-grow: 1; 225 | } 226 | .menu-items-container .setting-item-control { 227 | flex-basis: 100%; 228 | flex-grow: 1; 229 | justify-content: space-between; 230 | } 231 | 232 | .custom-icons-container { 233 | display: flex; 234 | flex-direction: column; 235 | } 236 | .custom-icons-container .setting-item { 237 | flex-wrap: wrap; 238 | } 239 | .custom-icons-container .setting-item-control { 240 | flex-basis: 100%; 241 | flex-grow: 1; 242 | justify-content: space-between; 243 | } 244 | .custom-icons-container input[type=text][placeholder='SVG Content'] { 245 | width: 100%; 246 | } 247 | .config-tab-container { 248 | display: flex; 249 | flex-wrap: wrap; 250 | } 251 | .config-tab-container .setting-item { 252 | flex-wrap: wrap; 253 | } 254 | .config-tab-container .setting-item-control { 255 | flex-basis: 100%; 256 | flex-grow: 1; 257 | justify-content: flex-start; 258 | } 259 | 260 | 261 | .submenu-items-container .setting-item { 262 | padding: 0.45em 0; 263 | } 264 | .setting-item.mod-toggle:has(.setting-expanded-true) .setting-item-control { 265 | padding-bottom: 0.75em; 266 | } 267 | .setting-item.mod-toggle:has(.setting-expanded-true) .submenu-items-container .setting-item-control { 268 | padding: 0; 269 | } 270 | 271 | .submenu-items-container .setting-item.mod-toggle { 272 | border-top: none; 273 | } 274 | .setting-item.mod-toggle:has(.setting-expanded-false) .submenu-items-container { 275 | display: none; 276 | } 277 | .setting-expanded-true.nosub, 278 | .setting-expanded-false.nosub { 279 | visibility: hidden; 280 | } 281 | .setting-item.mod-toggle:has(.nosub) .setting-ctxmenu-check { 282 | pointer-events: none; 283 | } 284 | .submenu-items-container .setting-item-control > *:first-child { 285 | visibility: hidden; 286 | } 287 | .add-setting .clickable-icon.mod-cta { 288 | visibility: visible; 289 | } 290 | 291 | 292 | .menu-items-tab-container .hidden { 293 | visibility: hidden; 294 | } 295 | .settings-header-container .disabled, 296 | .menu-items-tab-container .disabled { 297 | opacity: 0.5; 298 | pointer-events: none; 299 | } 300 | .settings-header-container .disabled svg { 301 | opacity: 0.5; 302 | } 303 | 304 | .menu-items-tab-container .setting-item-controller { 305 | opacity: 0.5; 306 | } 307 | .add-setting .clickable-icon.mod-cta { 308 | background-color: var(--interactive-accent); 309 | } 310 | .add-setting .clickable-icon.mod-cta:hover { 311 | background-color: var(--interactive-accent-hover); 312 | } 313 | .add-setting .clickable-icon.mod-cta svg { 314 | stroke: var(--text-on-accent); 315 | stroke-width: 2.5; 316 | } 317 | 318 | .new-menu-item-setting-container, 319 | .new-submenu-item-setting-container, 320 | .new-custom-icon-setting-container, 321 | .menu-items-tab-container, 322 | .custom-icons-tab-container, 323 | .cs-icons-container, 324 | .builtin-icons-container { 325 | display: none; 326 | } 327 | .actived { 328 | display: inherit; 329 | } 330 | .config-tab-container .clickable-icon.actived, 331 | .icon-list-container .clickable-icon.actived { 332 | background-color: var(--background-modifier-hover); 333 | } 334 | 335 | .new-menu-item-setting-container .setting-item { 336 | padding: 0.45em 0; 337 | } 338 | .add-setting { 339 | display: inline; 340 | } 341 | .add-setting input[type='text'] { 342 | border: var(--input-border-width) dashed var(--background-modifier-border); 343 | } 344 | .add-setting input[type='text']:focus { 345 | border: none; 346 | } 347 | .icon-selector { 348 | border: var(--input-border-width) dashed var(--background-modifier-border); 349 | } 350 | 351 | .setting-edge-check svg { 352 | opacity: 0.5; 353 | } 354 | .setting-edge-checked { 355 | background-color: var(--interactive-accent); 356 | } 357 | .setting-edge-checked:hover { 358 | background-color: var(--interactive-accent-hover); 359 | } 360 | .setting-edge-checked svg { 361 | stroke: var(--text-on-accent); 362 | stroke-width: 2.5; 363 | } 364 | .new-menu-item-setting-container.add-setting .setting-edge-check svg { 365 | opacity: unset; 366 | } 367 | .new-menu-item-setting-container.add-setting .setting-edge-check.disabled svg { 368 | opacity: 0.5; 369 | } 370 | 371 | .setting-cc-check svg { 372 | opacity: 0.5; 373 | } 374 | .setting-cc-checked { 375 | background-color: var(--interactive-accent); 376 | } 377 | .setting-cc-checked:hover { 378 | background-color: var(--interactive-accent-hover); 379 | } 380 | .setting-cc-checked svg { 381 | stroke: var(--text-on-accent); 382 | stroke-width: 2.5; 383 | } 384 | .new-menu-item-setting-container.add-setting .setting-cc-check svg { 385 | opacity: unset; 386 | } 387 | .new-menu-item-setting-container.add-setting .setting-cc-check.disabled svg { 388 | opacity: 0.5; 389 | } 390 | 391 | .setting-ctxmenu-check { 392 | opacity: 0.5; 393 | } 394 | .setting-ctxmenu-check svg { 395 | opacity: unset; 396 | } 397 | .setting-ctxmenu-check.nosub svg, 398 | .setting-ctxmenu-check.disabled svg { 399 | opacity: 0.5; 400 | } 401 | .setting-ctxmenu-checked { 402 | background-color: var(--interactive-accent); 403 | } 404 | .setting-ctxmenu-checked:hover { 405 | background-color: var(--interactive-accent-hover); 406 | } 407 | .setting-ctxmenu-checked svg { 408 | stroke: var(--text-on-accent); 409 | stroke-width: 2.5; 410 | } 411 | .setting-editor-extra-setting-button.mod-warning svg { 412 | opacity: 0.5; 413 | } 414 | .setting-editor-extra-setting-button.mod-warning:hover { 415 | background-color: var(--background-modifier-error); 416 | } 417 | .setting-editor-extra-setting-button.mod-warning:hover svg { 418 | opacity: unset; 419 | stroke: var(--text-on-accent); 420 | } 421 | 422 | .cs-icons-container, 423 | .builtin-icons-container { 424 | flex-wrap: wrap; 425 | gap: 4px; 426 | overflow-y: auto; 427 | overflow-x: hidden; 428 | flex-grow: 1; 429 | align-items: flex-start; 430 | align-content: flex-start; 431 | justify-content: flex-start; 432 | } 433 | .cs-icons-container.actived, 434 | .builtin-icons-container.actived { 435 | display: flex; 436 | } 437 | .icon-list-container { 438 | display: flex; 439 | align-items: center; 440 | padding: 0.75em 0; 441 | gap: var(--size-4-2); 442 | } 443 | .icon-list-container .icon-list-name { 444 | padding-left: var(--size-4-1); 445 | } 446 | .modal:has(.icon-list-container) { 447 | height: var(--dialog-width); 448 | } 449 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "1.1.0" 3 | } --------------------------------------------------------------------------------