├── .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 | 
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 | 
30 |
31 | 2. Second, add Sub Menu configuration:
32 | **Note: No need to add `cat: 'edge'` in the Sub Menu Config.**
33 |
34 | 
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 | 
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 | 
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 | 
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 | 
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 | 
27 |
28 | **注意:默认添加的Menu Config是卡片的样式按钮,如果要为连接线添加样式按钮,只需在Menu Config中添加`cat: 'edge'`。
29 |
30 | 
31 |
32 | 第二步,添加Sub Menu Config:
33 | **注意:Sub Menu Config无需添加`cat: 'edge'`**。
34 |
35 | 
36 |
37 | 第三步,添加您自己的css片段,其中每个样式的类名称需要跟您的菜单配置中的类名称对应上:
38 |
39 | 
40 |
41 | 第四步,恭喜,您可以在样式菜单中使用您添加的样式了!
42 |
43 | **注意:**如果您的按钮没有子菜单的话,那么按钮将会变成一个开关按钮,左键点击应用样式,右键点击取消样式。
44 |
45 | **Obsidian Canvas Style Menu**支持可选的obsidian默认的cssclasses样式方法,用户无需手写cssclasses也可实现cssclasses的效果,只需在你的Menu Config中添加`selector: 'cc'`。
46 |
47 | 
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------