├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── esbuild.config.mjs ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── block-selector │ ├── index.ts │ ├── selection-handler.ts │ └── selection-util.ts ├── commands │ ├── commands.ts │ └── index.ts ├── constants.ts ├── main.ts ├── settings │ ├── block │ │ ├── block-color.ts │ │ ├── block-selector-mobile.ts │ │ ├── block-selector.ts │ │ ├── block-transparency.ts │ │ └── index.ts │ ├── index.ts │ ├── keys │ │ ├── deselect-block.ts │ │ ├── index.ts │ │ ├── next-block.ts │ │ ├── prev-block.ts │ │ └── toggle-collapse.ts │ └── miscellaneous │ │ ├── always-on-collapse-indicator.ts │ │ ├── auto-select-top-block.ts │ │ ├── checkbox-align-with-indentation-guide.ts │ │ ├── collapse-indicators-on-the-right-side.ts │ │ ├── index.ts │ │ └── scrollable-code.ts ├── styles.ts └── utils.ts ├── 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 = 2 10 | tab_width = 2 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /.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="" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Galacsh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reading View Enhancer Plugin 2 | 3 | The Reading View Enhancer is an Obsidian plugin designed to improve the reading experience in Obsidian's reading view mode. 4 | It provides several features aimed at making the reading view more convenient and comfortable. 5 | 6 | ## Features 7 | 8 | ### Overview 9 | 10 | - **Keyboard block navigation** 11 | - Select the next block 12 | - Select the previous block 13 | - **Toggle collapse with keyboard** 14 | - **Auto-select top block on switching into reading view** 15 | - **Always-visible collapse indicators** 16 | - Keep all collapse indicators visible 17 | - **Right side collapse indicator** (experimental) 18 | - **Scrollable code blocks** 19 | - Make code blocks scrollable instead of line-wrapping 20 | - **Block highlighting in reading view** 21 | 22 | ### Keyboard Block Navigation 23 | 24 | > ⚡️ Settings > Reading View Enhancer > Enable Block Selector 25 | 26 | This feature allows you to select **blocks** in the reading view by pressing keyboard. Selected blocks will be highlighted. 27 | 28 | Paragraphs, headings, lists, tables, code blocks, quotes, media elements, 29 | and callouts are examples of blocks. 30 | 31 | - Default key binding 32 | - `ArrowDown | KeyJ`: Select the next block 33 | - `ArrowUp | KeyK`: Select the previous block 34 | 35 | > [!IMPORTANT] 36 | > If a selected block is too long, the plugin will automatically scroll to display 37 | > the block's top or bottom, loading adjacent blocks that are not yet in the DOM tree. 38 | 39 | > [!NOTE] 40 | > Additionally, there is a `Select top block in the view` command, 41 | > which allows you to assign a hotkey for keyboard control. 42 | 43 | #### Block Color 44 | 45 | > ⚡️ Settings > Reading View Enhancer > Block color 46 | 47 | You can set a custom color and transparency for the block highlight effect. 48 | 49 | ### Toggle collapse with keyboard 50 | 51 | When a block is selected and collapsible, you can use your keyboard to toggle collapse. 52 | 53 | - Default key binding 54 | - `ArrowLeft | KeyH | ArrowRight | KeyL`: Toggle collapse 55 | 56 | ### Auto-select top block on switching into reading view 57 | 58 | > ⚡️ Settings > Reading View Enhancer > Auto-select top block 59 | 60 | When you switch to reading view, the top block will be automatically selected. 61 | 62 | ### Always-visible Collapse Indicators 63 | 64 | > ⚡️ Settings > Reading View Enhancer > Always on collapse indicator 65 | 66 | By default, collapse indicators are invisible until hovered over. 67 | This option keeps indicators always visible. 68 | 69 | ### Right side collapse indicator (experimental) 70 | 71 | > ⚡️ Settings > Reading View Enhancer > [Experimental] Collapse indicator on the right side 72 | 73 | Set collapse indicators to be shown on the right side. 74 | 75 | > [!WARNING] 76 | > Since this makes some elements relative that were previously not, may lead some problems. 77 | 78 | ### Scrollable Code Blocks 79 | 80 | > ⚡️ Settings > Reading View Enhancer > Scrollable code 81 | 82 | This feature makes code blocks scrollable in reading view, 83 | rather than using line-wrapping. 84 | 85 | By default, codes get line-wrapped when it's too long. 86 | With this option, you can make code blocks scrollable instead of line break. 87 | 88 | > [!IMPORTANT] 89 | > This feature is for reading view. For the editor, 90 | > install "Style Settings" plugin and set the code block to scrollable. 91 | 92 | ### Block Highlighting in Reading View 93 | 94 | When a block is selected, you can highlight the block right away in the reading view by running the command: 95 | 96 | `Reading View Enhancer: Toggle Block Highlight`. 97 | 98 | > [!TIP] 99 | > Assign a hot key for more convenient use. 100 | > For example, I use `Ctrl + Shift + h`. 101 | 102 | > [!NOTE] 103 | > **Why it blinks during the highlight?** 104 | > This is because the plugin changes to 'editor' mode to insert the `==` syntax for highlighting and then changes back to 'reading' mode. 105 | > 106 | > This is the only way I found to toggle the highlight while **keeping the editing history**. You can always undo the highlight by pressing `Ctrl + z` in the editing mode. 107 | 108 | ## How to install manually? 109 | 110 | By using [Obsidian42-BRAT](https://obsidian.md/plugins?id=obsidian42-brat), you could easily install & update this plugin. 111 | 112 | 1. Install **Obsidian42-BRAT** 113 | 2. Enable Obsidian42-BRAT 114 | 3. Go to Obsidian42-BRAT options page 115 | 4. Beta plugins list > `Add Beta plugin` 116 | 5. Paste this `https://github.com/Galacsh/obsidian-reading-view-enhancer` 117 | 6. Go to `Settings > Community Plugins > Installed plugins` 118 | 7. Refresh the list 119 | 8. Turn on the switch of "Reading View Enhancer" to enable the plugin 120 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | `; 10 | 11 | const prod = process.argv[2] === "production"; 12 | 13 | const context = await esbuild.context({ 14 | banner: { 15 | js: banner, 16 | }, 17 | entryPoints: ["src/main.ts"], 18 | bundle: true, 19 | external: [ 20 | "obsidian", 21 | "electron", 22 | "@codemirror/autocomplete", 23 | "@codemirror/collab", 24 | "@codemirror/commands", 25 | "@codemirror/language", 26 | "@codemirror/lint", 27 | "@codemirror/search", 28 | "@codemirror/state", 29 | "@codemirror/view", 30 | "@lezer/common", 31 | "@lezer/highlight", 32 | "@lezer/lr", 33 | ...builtins, 34 | ], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | }); 42 | 43 | if (prod) { 44 | await context.rebuild(); 45 | process.exit(0); 46 | } else { 47 | await context.watch(); 48 | } 49 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "reading-view-enhancer", 3 | "name": "Reading View Enhancer", 4 | "version": "0.2.1", 5 | "minAppVersion": "1.8.0", 6 | "description": "Enhances reading view. Use arrow keys to navigate between blocks or toggle collapse.", 7 | "author": "Galacsh", 8 | "authorUrl": "https://github.com/galacsh", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reading-view-enhancer", 3 | "version": "0.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "reading-view-enhancer", 9 | "version": "0.2.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "color2k": "^2.0.3" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^16.18.125", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "^1.7.2", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | } 24 | }, 25 | "node_modules/@codemirror/state": { 26 | "version": "6.5.1", 27 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.1.tgz", 28 | "integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==", 29 | "dev": true, 30 | "license": "MIT", 31 | "peer": true, 32 | "dependencies": { 33 | "@marijn/find-cluster-break": "^1.0.0" 34 | } 35 | }, 36 | "node_modules/@codemirror/view": { 37 | "version": "6.36.2", 38 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz", 39 | "integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==", 40 | "dev": true, 41 | "license": "MIT", 42 | "peer": true, 43 | "dependencies": { 44 | "@codemirror/state": "^6.5.0", 45 | "style-mod": "^4.1.0", 46 | "w3c-keyname": "^2.2.4" 47 | } 48 | }, 49 | "node_modules/@esbuild/android-arm": { 50 | "version": "0.17.3", 51 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", 52 | "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", 53 | "cpu": [ 54 | "arm" 55 | ], 56 | "dev": true, 57 | "license": "MIT", 58 | "optional": true, 59 | "os": [ 60 | "android" 61 | ], 62 | "engines": { 63 | "node": ">=12" 64 | } 65 | }, 66 | "node_modules/@esbuild/android-arm64": { 67 | "version": "0.17.3", 68 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", 69 | "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", 70 | "cpu": [ 71 | "arm64" 72 | ], 73 | "dev": true, 74 | "license": "MIT", 75 | "optional": true, 76 | "os": [ 77 | "android" 78 | ], 79 | "engines": { 80 | "node": ">=12" 81 | } 82 | }, 83 | "node_modules/@esbuild/android-x64": { 84 | "version": "0.17.3", 85 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", 86 | "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", 87 | "cpu": [ 88 | "x64" 89 | ], 90 | "dev": true, 91 | "license": "MIT", 92 | "optional": true, 93 | "os": [ 94 | "android" 95 | ], 96 | "engines": { 97 | "node": ">=12" 98 | } 99 | }, 100 | "node_modules/@esbuild/darwin-arm64": { 101 | "version": "0.17.3", 102 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", 103 | "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", 104 | "cpu": [ 105 | "arm64" 106 | ], 107 | "dev": true, 108 | "license": "MIT", 109 | "optional": true, 110 | "os": [ 111 | "darwin" 112 | ], 113 | "engines": { 114 | "node": ">=12" 115 | } 116 | }, 117 | "node_modules/@esbuild/darwin-x64": { 118 | "version": "0.17.3", 119 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", 120 | "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", 121 | "cpu": [ 122 | "x64" 123 | ], 124 | "dev": true, 125 | "license": "MIT", 126 | "optional": true, 127 | "os": [ 128 | "darwin" 129 | ], 130 | "engines": { 131 | "node": ">=12" 132 | } 133 | }, 134 | "node_modules/@esbuild/freebsd-arm64": { 135 | "version": "0.17.3", 136 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", 137 | "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", 138 | "cpu": [ 139 | "arm64" 140 | ], 141 | "dev": true, 142 | "license": "MIT", 143 | "optional": true, 144 | "os": [ 145 | "freebsd" 146 | ], 147 | "engines": { 148 | "node": ">=12" 149 | } 150 | }, 151 | "node_modules/@esbuild/freebsd-x64": { 152 | "version": "0.17.3", 153 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", 154 | "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", 155 | "cpu": [ 156 | "x64" 157 | ], 158 | "dev": true, 159 | "license": "MIT", 160 | "optional": true, 161 | "os": [ 162 | "freebsd" 163 | ], 164 | "engines": { 165 | "node": ">=12" 166 | } 167 | }, 168 | "node_modules/@esbuild/linux-arm": { 169 | "version": "0.17.3", 170 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", 171 | "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", 172 | "cpu": [ 173 | "arm" 174 | ], 175 | "dev": true, 176 | "license": "MIT", 177 | "optional": true, 178 | "os": [ 179 | "linux" 180 | ], 181 | "engines": { 182 | "node": ">=12" 183 | } 184 | }, 185 | "node_modules/@esbuild/linux-arm64": { 186 | "version": "0.17.3", 187 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", 188 | "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", 189 | "cpu": [ 190 | "arm64" 191 | ], 192 | "dev": true, 193 | "license": "MIT", 194 | "optional": true, 195 | "os": [ 196 | "linux" 197 | ], 198 | "engines": { 199 | "node": ">=12" 200 | } 201 | }, 202 | "node_modules/@esbuild/linux-ia32": { 203 | "version": "0.17.3", 204 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", 205 | "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", 206 | "cpu": [ 207 | "ia32" 208 | ], 209 | "dev": true, 210 | "license": "MIT", 211 | "optional": true, 212 | "os": [ 213 | "linux" 214 | ], 215 | "engines": { 216 | "node": ">=12" 217 | } 218 | }, 219 | "node_modules/@esbuild/linux-loong64": { 220 | "version": "0.17.3", 221 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", 222 | "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", 223 | "cpu": [ 224 | "loong64" 225 | ], 226 | "dev": true, 227 | "license": "MIT", 228 | "optional": true, 229 | "os": [ 230 | "linux" 231 | ], 232 | "engines": { 233 | "node": ">=12" 234 | } 235 | }, 236 | "node_modules/@esbuild/linux-mips64el": { 237 | "version": "0.17.3", 238 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", 239 | "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", 240 | "cpu": [ 241 | "mips64el" 242 | ], 243 | "dev": true, 244 | "license": "MIT", 245 | "optional": true, 246 | "os": [ 247 | "linux" 248 | ], 249 | "engines": { 250 | "node": ">=12" 251 | } 252 | }, 253 | "node_modules/@esbuild/linux-ppc64": { 254 | "version": "0.17.3", 255 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", 256 | "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", 257 | "cpu": [ 258 | "ppc64" 259 | ], 260 | "dev": true, 261 | "license": "MIT", 262 | "optional": true, 263 | "os": [ 264 | "linux" 265 | ], 266 | "engines": { 267 | "node": ">=12" 268 | } 269 | }, 270 | "node_modules/@esbuild/linux-riscv64": { 271 | "version": "0.17.3", 272 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", 273 | "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", 274 | "cpu": [ 275 | "riscv64" 276 | ], 277 | "dev": true, 278 | "license": "MIT", 279 | "optional": true, 280 | "os": [ 281 | "linux" 282 | ], 283 | "engines": { 284 | "node": ">=12" 285 | } 286 | }, 287 | "node_modules/@esbuild/linux-s390x": { 288 | "version": "0.17.3", 289 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", 290 | "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", 291 | "cpu": [ 292 | "s390x" 293 | ], 294 | "dev": true, 295 | "license": "MIT", 296 | "optional": true, 297 | "os": [ 298 | "linux" 299 | ], 300 | "engines": { 301 | "node": ">=12" 302 | } 303 | }, 304 | "node_modules/@esbuild/linux-x64": { 305 | "version": "0.17.3", 306 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", 307 | "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", 308 | "cpu": [ 309 | "x64" 310 | ], 311 | "dev": true, 312 | "license": "MIT", 313 | "optional": true, 314 | "os": [ 315 | "linux" 316 | ], 317 | "engines": { 318 | "node": ">=12" 319 | } 320 | }, 321 | "node_modules/@esbuild/netbsd-x64": { 322 | "version": "0.17.3", 323 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", 324 | "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", 325 | "cpu": [ 326 | "x64" 327 | ], 328 | "dev": true, 329 | "license": "MIT", 330 | "optional": true, 331 | "os": [ 332 | "netbsd" 333 | ], 334 | "engines": { 335 | "node": ">=12" 336 | } 337 | }, 338 | "node_modules/@esbuild/openbsd-x64": { 339 | "version": "0.17.3", 340 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", 341 | "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", 342 | "cpu": [ 343 | "x64" 344 | ], 345 | "dev": true, 346 | "license": "MIT", 347 | "optional": true, 348 | "os": [ 349 | "openbsd" 350 | ], 351 | "engines": { 352 | "node": ">=12" 353 | } 354 | }, 355 | "node_modules/@esbuild/sunos-x64": { 356 | "version": "0.17.3", 357 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", 358 | "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", 359 | "cpu": [ 360 | "x64" 361 | ], 362 | "dev": true, 363 | "license": "MIT", 364 | "optional": true, 365 | "os": [ 366 | "sunos" 367 | ], 368 | "engines": { 369 | "node": ">=12" 370 | } 371 | }, 372 | "node_modules/@esbuild/win32-arm64": { 373 | "version": "0.17.3", 374 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", 375 | "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", 376 | "cpu": [ 377 | "arm64" 378 | ], 379 | "dev": true, 380 | "license": "MIT", 381 | "optional": true, 382 | "os": [ 383 | "win32" 384 | ], 385 | "engines": { 386 | "node": ">=12" 387 | } 388 | }, 389 | "node_modules/@esbuild/win32-ia32": { 390 | "version": "0.17.3", 391 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", 392 | "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", 393 | "cpu": [ 394 | "ia32" 395 | ], 396 | "dev": true, 397 | "license": "MIT", 398 | "optional": true, 399 | "os": [ 400 | "win32" 401 | ], 402 | "engines": { 403 | "node": ">=12" 404 | } 405 | }, 406 | "node_modules/@esbuild/win32-x64": { 407 | "version": "0.17.3", 408 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", 409 | "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", 410 | "cpu": [ 411 | "x64" 412 | ], 413 | "dev": true, 414 | "license": "MIT", 415 | "optional": true, 416 | "os": [ 417 | "win32" 418 | ], 419 | "engines": { 420 | "node": ">=12" 421 | } 422 | }, 423 | "node_modules/@eslint-community/eslint-utils": { 424 | "version": "4.4.1", 425 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", 426 | "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", 427 | "dev": true, 428 | "license": "MIT", 429 | "peer": true, 430 | "dependencies": { 431 | "eslint-visitor-keys": "^3.4.3" 432 | }, 433 | "engines": { 434 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 435 | }, 436 | "funding": { 437 | "url": "https://opencollective.com/eslint" 438 | }, 439 | "peerDependencies": { 440 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 441 | } 442 | }, 443 | "node_modules/@eslint-community/regexpp": { 444 | "version": "4.12.1", 445 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 446 | "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 447 | "dev": true, 448 | "license": "MIT", 449 | "peer": true, 450 | "engines": { 451 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 452 | } 453 | }, 454 | "node_modules/@eslint/eslintrc": { 455 | "version": "2.1.4", 456 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", 457 | "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", 458 | "dev": true, 459 | "license": "MIT", 460 | "peer": true, 461 | "dependencies": { 462 | "ajv": "^6.12.4", 463 | "debug": "^4.3.2", 464 | "espree": "^9.6.0", 465 | "globals": "^13.19.0", 466 | "ignore": "^5.2.0", 467 | "import-fresh": "^3.2.1", 468 | "js-yaml": "^4.1.0", 469 | "minimatch": "^3.1.2", 470 | "strip-json-comments": "^3.1.1" 471 | }, 472 | "engines": { 473 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 474 | }, 475 | "funding": { 476 | "url": "https://opencollective.com/eslint" 477 | } 478 | }, 479 | "node_modules/@eslint/js": { 480 | "version": "8.57.1", 481 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", 482 | "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", 483 | "dev": true, 484 | "license": "MIT", 485 | "peer": true, 486 | "engines": { 487 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 488 | } 489 | }, 490 | "node_modules/@humanwhocodes/config-array": { 491 | "version": "0.13.0", 492 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", 493 | "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", 494 | "deprecated": "Use @eslint/config-array instead", 495 | "dev": true, 496 | "license": "Apache-2.0", 497 | "peer": true, 498 | "dependencies": { 499 | "@humanwhocodes/object-schema": "^2.0.3", 500 | "debug": "^4.3.1", 501 | "minimatch": "^3.0.5" 502 | }, 503 | "engines": { 504 | "node": ">=10.10.0" 505 | } 506 | }, 507 | "node_modules/@humanwhocodes/module-importer": { 508 | "version": "1.0.1", 509 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 510 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 511 | "dev": true, 512 | "license": "Apache-2.0", 513 | "peer": true, 514 | "engines": { 515 | "node": ">=12.22" 516 | }, 517 | "funding": { 518 | "type": "github", 519 | "url": "https://github.com/sponsors/nzakas" 520 | } 521 | }, 522 | "node_modules/@humanwhocodes/object-schema": { 523 | "version": "2.0.3", 524 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", 525 | "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", 526 | "deprecated": "Use @eslint/object-schema instead", 527 | "dev": true, 528 | "license": "BSD-3-Clause", 529 | "peer": true 530 | }, 531 | "node_modules/@marijn/find-cluster-break": { 532 | "version": "1.0.2", 533 | "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", 534 | "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", 535 | "dev": true, 536 | "license": "MIT", 537 | "peer": true 538 | }, 539 | "node_modules/@nodelib/fs.scandir": { 540 | "version": "2.1.5", 541 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 542 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 543 | "dev": true, 544 | "license": "MIT", 545 | "dependencies": { 546 | "@nodelib/fs.stat": "2.0.5", 547 | "run-parallel": "^1.1.9" 548 | }, 549 | "engines": { 550 | "node": ">= 8" 551 | } 552 | }, 553 | "node_modules/@nodelib/fs.stat": { 554 | "version": "2.0.5", 555 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 556 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 557 | "dev": true, 558 | "license": "MIT", 559 | "engines": { 560 | "node": ">= 8" 561 | } 562 | }, 563 | "node_modules/@nodelib/fs.walk": { 564 | "version": "1.2.8", 565 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 566 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 567 | "dev": true, 568 | "license": "MIT", 569 | "dependencies": { 570 | "@nodelib/fs.scandir": "2.1.5", 571 | "fastq": "^1.6.0" 572 | }, 573 | "engines": { 574 | "node": ">= 8" 575 | } 576 | }, 577 | "node_modules/@types/codemirror": { 578 | "version": "5.60.8", 579 | "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", 580 | "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", 581 | "dev": true, 582 | "license": "MIT", 583 | "dependencies": { 584 | "@types/tern": "*" 585 | } 586 | }, 587 | "node_modules/@types/estree": { 588 | "version": "1.0.6", 589 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 590 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 591 | "dev": true, 592 | "license": "MIT" 593 | }, 594 | "node_modules/@types/json-schema": { 595 | "version": "7.0.15", 596 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 597 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 598 | "dev": true, 599 | "license": "MIT" 600 | }, 601 | "node_modules/@types/node": { 602 | "version": "16.18.125", 603 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.125.tgz", 604 | "integrity": "sha512-w7U5ojboSPfZP4zD98d+/cjcN2BDW6lKH2M0ubipt8L8vUC7qUAC6ENKGSJL4tEktH2Saw2K4y1uwSjyRGKMhw==", 605 | "dev": true, 606 | "license": "MIT" 607 | }, 608 | "node_modules/@types/tern": { 609 | "version": "0.23.9", 610 | "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", 611 | "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", 612 | "dev": true, 613 | "license": "MIT", 614 | "dependencies": { 615 | "@types/estree": "*" 616 | } 617 | }, 618 | "node_modules/@typescript-eslint/eslint-plugin": { 619 | "version": "5.29.0", 620 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", 621 | "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", 622 | "dev": true, 623 | "license": "MIT", 624 | "dependencies": { 625 | "@typescript-eslint/scope-manager": "5.29.0", 626 | "@typescript-eslint/type-utils": "5.29.0", 627 | "@typescript-eslint/utils": "5.29.0", 628 | "debug": "^4.3.4", 629 | "functional-red-black-tree": "^1.0.1", 630 | "ignore": "^5.2.0", 631 | "regexpp": "^3.2.0", 632 | "semver": "^7.3.7", 633 | "tsutils": "^3.21.0" 634 | }, 635 | "engines": { 636 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 637 | }, 638 | "funding": { 639 | "type": "opencollective", 640 | "url": "https://opencollective.com/typescript-eslint" 641 | }, 642 | "peerDependencies": { 643 | "@typescript-eslint/parser": "^5.0.0", 644 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 645 | }, 646 | "peerDependenciesMeta": { 647 | "typescript": { 648 | "optional": true 649 | } 650 | } 651 | }, 652 | "node_modules/@typescript-eslint/parser": { 653 | "version": "5.29.0", 654 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", 655 | "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", 656 | "dev": true, 657 | "license": "BSD-2-Clause", 658 | "dependencies": { 659 | "@typescript-eslint/scope-manager": "5.29.0", 660 | "@typescript-eslint/types": "5.29.0", 661 | "@typescript-eslint/typescript-estree": "5.29.0", 662 | "debug": "^4.3.4" 663 | }, 664 | "engines": { 665 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 666 | }, 667 | "funding": { 668 | "type": "opencollective", 669 | "url": "https://opencollective.com/typescript-eslint" 670 | }, 671 | "peerDependencies": { 672 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 673 | }, 674 | "peerDependenciesMeta": { 675 | "typescript": { 676 | "optional": true 677 | } 678 | } 679 | }, 680 | "node_modules/@typescript-eslint/scope-manager": { 681 | "version": "5.29.0", 682 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", 683 | "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", 684 | "dev": true, 685 | "license": "MIT", 686 | "dependencies": { 687 | "@typescript-eslint/types": "5.29.0", 688 | "@typescript-eslint/visitor-keys": "5.29.0" 689 | }, 690 | "engines": { 691 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 692 | }, 693 | "funding": { 694 | "type": "opencollective", 695 | "url": "https://opencollective.com/typescript-eslint" 696 | } 697 | }, 698 | "node_modules/@typescript-eslint/type-utils": { 699 | "version": "5.29.0", 700 | "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", 701 | "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", 702 | "dev": true, 703 | "license": "MIT", 704 | "dependencies": { 705 | "@typescript-eslint/utils": "5.29.0", 706 | "debug": "^4.3.4", 707 | "tsutils": "^3.21.0" 708 | }, 709 | "engines": { 710 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 711 | }, 712 | "funding": { 713 | "type": "opencollective", 714 | "url": "https://opencollective.com/typescript-eslint" 715 | }, 716 | "peerDependencies": { 717 | "eslint": "*" 718 | }, 719 | "peerDependenciesMeta": { 720 | "typescript": { 721 | "optional": true 722 | } 723 | } 724 | }, 725 | "node_modules/@typescript-eslint/types": { 726 | "version": "5.29.0", 727 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", 728 | "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", 729 | "dev": true, 730 | "license": "MIT", 731 | "engines": { 732 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 733 | }, 734 | "funding": { 735 | "type": "opencollective", 736 | "url": "https://opencollective.com/typescript-eslint" 737 | } 738 | }, 739 | "node_modules/@typescript-eslint/typescript-estree": { 740 | "version": "5.29.0", 741 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", 742 | "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", 743 | "dev": true, 744 | "license": "BSD-2-Clause", 745 | "dependencies": { 746 | "@typescript-eslint/types": "5.29.0", 747 | "@typescript-eslint/visitor-keys": "5.29.0", 748 | "debug": "^4.3.4", 749 | "globby": "^11.1.0", 750 | "is-glob": "^4.0.3", 751 | "semver": "^7.3.7", 752 | "tsutils": "^3.21.0" 753 | }, 754 | "engines": { 755 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 756 | }, 757 | "funding": { 758 | "type": "opencollective", 759 | "url": "https://opencollective.com/typescript-eslint" 760 | }, 761 | "peerDependenciesMeta": { 762 | "typescript": { 763 | "optional": true 764 | } 765 | } 766 | }, 767 | "node_modules/@typescript-eslint/utils": { 768 | "version": "5.29.0", 769 | "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", 770 | "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", 771 | "dev": true, 772 | "license": "MIT", 773 | "dependencies": { 774 | "@types/json-schema": "^7.0.9", 775 | "@typescript-eslint/scope-manager": "5.29.0", 776 | "@typescript-eslint/types": "5.29.0", 777 | "@typescript-eslint/typescript-estree": "5.29.0", 778 | "eslint-scope": "^5.1.1", 779 | "eslint-utils": "^3.0.0" 780 | }, 781 | "engines": { 782 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 783 | }, 784 | "funding": { 785 | "type": "opencollective", 786 | "url": "https://opencollective.com/typescript-eslint" 787 | }, 788 | "peerDependencies": { 789 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 790 | } 791 | }, 792 | "node_modules/@typescript-eslint/visitor-keys": { 793 | "version": "5.29.0", 794 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", 795 | "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", 796 | "dev": true, 797 | "license": "MIT", 798 | "dependencies": { 799 | "@typescript-eslint/types": "5.29.0", 800 | "eslint-visitor-keys": "^3.3.0" 801 | }, 802 | "engines": { 803 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 804 | }, 805 | "funding": { 806 | "type": "opencollective", 807 | "url": "https://opencollective.com/typescript-eslint" 808 | } 809 | }, 810 | "node_modules/@ungap/structured-clone": { 811 | "version": "1.3.0", 812 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", 813 | "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", 814 | "dev": true, 815 | "license": "ISC", 816 | "peer": true 817 | }, 818 | "node_modules/acorn": { 819 | "version": "8.14.0", 820 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", 821 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", 822 | "dev": true, 823 | "license": "MIT", 824 | "peer": true, 825 | "bin": { 826 | "acorn": "bin/acorn" 827 | }, 828 | "engines": { 829 | "node": ">=0.4.0" 830 | } 831 | }, 832 | "node_modules/acorn-jsx": { 833 | "version": "5.3.2", 834 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 835 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 836 | "dev": true, 837 | "license": "MIT", 838 | "peer": true, 839 | "peerDependencies": { 840 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 841 | } 842 | }, 843 | "node_modules/ajv": { 844 | "version": "6.12.6", 845 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 846 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 847 | "dev": true, 848 | "license": "MIT", 849 | "peer": true, 850 | "dependencies": { 851 | "fast-deep-equal": "^3.1.1", 852 | "fast-json-stable-stringify": "^2.0.0", 853 | "json-schema-traverse": "^0.4.1", 854 | "uri-js": "^4.2.2" 855 | }, 856 | "funding": { 857 | "type": "github", 858 | "url": "https://github.com/sponsors/epoberezkin" 859 | } 860 | }, 861 | "node_modules/ansi-regex": { 862 | "version": "5.0.1", 863 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 864 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 865 | "dev": true, 866 | "license": "MIT", 867 | "peer": true, 868 | "engines": { 869 | "node": ">=8" 870 | } 871 | }, 872 | "node_modules/ansi-styles": { 873 | "version": "4.3.0", 874 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 875 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 876 | "dev": true, 877 | "license": "MIT", 878 | "peer": true, 879 | "dependencies": { 880 | "color-convert": "^2.0.1" 881 | }, 882 | "engines": { 883 | "node": ">=8" 884 | }, 885 | "funding": { 886 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 887 | } 888 | }, 889 | "node_modules/argparse": { 890 | "version": "2.0.1", 891 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 892 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 893 | "dev": true, 894 | "license": "Python-2.0", 895 | "peer": true 896 | }, 897 | "node_modules/array-union": { 898 | "version": "2.1.0", 899 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 900 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 901 | "dev": true, 902 | "license": "MIT", 903 | "engines": { 904 | "node": ">=8" 905 | } 906 | }, 907 | "node_modules/balanced-match": { 908 | "version": "1.0.2", 909 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 910 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 911 | "dev": true, 912 | "license": "MIT", 913 | "peer": true 914 | }, 915 | "node_modules/brace-expansion": { 916 | "version": "1.1.11", 917 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 918 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 919 | "dev": true, 920 | "license": "MIT", 921 | "peer": true, 922 | "dependencies": { 923 | "balanced-match": "^1.0.0", 924 | "concat-map": "0.0.1" 925 | } 926 | }, 927 | "node_modules/braces": { 928 | "version": "3.0.3", 929 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 930 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 931 | "dev": true, 932 | "license": "MIT", 933 | "dependencies": { 934 | "fill-range": "^7.1.1" 935 | }, 936 | "engines": { 937 | "node": ">=8" 938 | } 939 | }, 940 | "node_modules/builtin-modules": { 941 | "version": "3.3.0", 942 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", 943 | "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", 944 | "dev": true, 945 | "license": "MIT", 946 | "engines": { 947 | "node": ">=6" 948 | }, 949 | "funding": { 950 | "url": "https://github.com/sponsors/sindresorhus" 951 | } 952 | }, 953 | "node_modules/callsites": { 954 | "version": "3.1.0", 955 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 956 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 957 | "dev": true, 958 | "license": "MIT", 959 | "peer": true, 960 | "engines": { 961 | "node": ">=6" 962 | } 963 | }, 964 | "node_modules/chalk": { 965 | "version": "4.1.2", 966 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 967 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 968 | "dev": true, 969 | "license": "MIT", 970 | "peer": true, 971 | "dependencies": { 972 | "ansi-styles": "^4.1.0", 973 | "supports-color": "^7.1.0" 974 | }, 975 | "engines": { 976 | "node": ">=10" 977 | }, 978 | "funding": { 979 | "url": "https://github.com/chalk/chalk?sponsor=1" 980 | } 981 | }, 982 | "node_modules/color-convert": { 983 | "version": "2.0.1", 984 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 985 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 986 | "dev": true, 987 | "license": "MIT", 988 | "peer": true, 989 | "dependencies": { 990 | "color-name": "~1.1.4" 991 | }, 992 | "engines": { 993 | "node": ">=7.0.0" 994 | } 995 | }, 996 | "node_modules/color-name": { 997 | "version": "1.1.4", 998 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 999 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1000 | "dev": true, 1001 | "license": "MIT", 1002 | "peer": true 1003 | }, 1004 | "node_modules/color2k": { 1005 | "version": "2.0.3", 1006 | "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", 1007 | "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", 1008 | "license": "MIT" 1009 | }, 1010 | "node_modules/concat-map": { 1011 | "version": "0.0.1", 1012 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1013 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1014 | "dev": true, 1015 | "license": "MIT", 1016 | "peer": true 1017 | }, 1018 | "node_modules/cross-spawn": { 1019 | "version": "7.0.6", 1020 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1021 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1022 | "dev": true, 1023 | "license": "MIT", 1024 | "peer": true, 1025 | "dependencies": { 1026 | "path-key": "^3.1.0", 1027 | "shebang-command": "^2.0.0", 1028 | "which": "^2.0.1" 1029 | }, 1030 | "engines": { 1031 | "node": ">= 8" 1032 | } 1033 | }, 1034 | "node_modules/debug": { 1035 | "version": "4.4.0", 1036 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1037 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1038 | "dev": true, 1039 | "license": "MIT", 1040 | "dependencies": { 1041 | "ms": "^2.1.3" 1042 | }, 1043 | "engines": { 1044 | "node": ">=6.0" 1045 | }, 1046 | "peerDependenciesMeta": { 1047 | "supports-color": { 1048 | "optional": true 1049 | } 1050 | } 1051 | }, 1052 | "node_modules/deep-is": { 1053 | "version": "0.1.4", 1054 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 1055 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 1056 | "dev": true, 1057 | "license": "MIT", 1058 | "peer": true 1059 | }, 1060 | "node_modules/dir-glob": { 1061 | "version": "3.0.1", 1062 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 1063 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 1064 | "dev": true, 1065 | "license": "MIT", 1066 | "dependencies": { 1067 | "path-type": "^4.0.0" 1068 | }, 1069 | "engines": { 1070 | "node": ">=8" 1071 | } 1072 | }, 1073 | "node_modules/doctrine": { 1074 | "version": "3.0.0", 1075 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 1076 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 1077 | "dev": true, 1078 | "license": "Apache-2.0", 1079 | "peer": true, 1080 | "dependencies": { 1081 | "esutils": "^2.0.2" 1082 | }, 1083 | "engines": { 1084 | "node": ">=6.0.0" 1085 | } 1086 | }, 1087 | "node_modules/esbuild": { 1088 | "version": "0.17.3", 1089 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", 1090 | "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", 1091 | "dev": true, 1092 | "hasInstallScript": true, 1093 | "license": "MIT", 1094 | "bin": { 1095 | "esbuild": "bin/esbuild" 1096 | }, 1097 | "engines": { 1098 | "node": ">=12" 1099 | }, 1100 | "optionalDependencies": { 1101 | "@esbuild/android-arm": "0.17.3", 1102 | "@esbuild/android-arm64": "0.17.3", 1103 | "@esbuild/android-x64": "0.17.3", 1104 | "@esbuild/darwin-arm64": "0.17.3", 1105 | "@esbuild/darwin-x64": "0.17.3", 1106 | "@esbuild/freebsd-arm64": "0.17.3", 1107 | "@esbuild/freebsd-x64": "0.17.3", 1108 | "@esbuild/linux-arm": "0.17.3", 1109 | "@esbuild/linux-arm64": "0.17.3", 1110 | "@esbuild/linux-ia32": "0.17.3", 1111 | "@esbuild/linux-loong64": "0.17.3", 1112 | "@esbuild/linux-mips64el": "0.17.3", 1113 | "@esbuild/linux-ppc64": "0.17.3", 1114 | "@esbuild/linux-riscv64": "0.17.3", 1115 | "@esbuild/linux-s390x": "0.17.3", 1116 | "@esbuild/linux-x64": "0.17.3", 1117 | "@esbuild/netbsd-x64": "0.17.3", 1118 | "@esbuild/openbsd-x64": "0.17.3", 1119 | "@esbuild/sunos-x64": "0.17.3", 1120 | "@esbuild/win32-arm64": "0.17.3", 1121 | "@esbuild/win32-ia32": "0.17.3", 1122 | "@esbuild/win32-x64": "0.17.3" 1123 | } 1124 | }, 1125 | "node_modules/escape-string-regexp": { 1126 | "version": "4.0.0", 1127 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1128 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1129 | "dev": true, 1130 | "license": "MIT", 1131 | "peer": true, 1132 | "engines": { 1133 | "node": ">=10" 1134 | }, 1135 | "funding": { 1136 | "url": "https://github.com/sponsors/sindresorhus" 1137 | } 1138 | }, 1139 | "node_modules/eslint": { 1140 | "version": "8.57.1", 1141 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", 1142 | "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", 1143 | "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", 1144 | "dev": true, 1145 | "license": "MIT", 1146 | "peer": true, 1147 | "dependencies": { 1148 | "@eslint-community/eslint-utils": "^4.2.0", 1149 | "@eslint-community/regexpp": "^4.6.1", 1150 | "@eslint/eslintrc": "^2.1.4", 1151 | "@eslint/js": "8.57.1", 1152 | "@humanwhocodes/config-array": "^0.13.0", 1153 | "@humanwhocodes/module-importer": "^1.0.1", 1154 | "@nodelib/fs.walk": "^1.2.8", 1155 | "@ungap/structured-clone": "^1.2.0", 1156 | "ajv": "^6.12.4", 1157 | "chalk": "^4.0.0", 1158 | "cross-spawn": "^7.0.2", 1159 | "debug": "^4.3.2", 1160 | "doctrine": "^3.0.0", 1161 | "escape-string-regexp": "^4.0.0", 1162 | "eslint-scope": "^7.2.2", 1163 | "eslint-visitor-keys": "^3.4.3", 1164 | "espree": "^9.6.1", 1165 | "esquery": "^1.4.2", 1166 | "esutils": "^2.0.2", 1167 | "fast-deep-equal": "^3.1.3", 1168 | "file-entry-cache": "^6.0.1", 1169 | "find-up": "^5.0.0", 1170 | "glob-parent": "^6.0.2", 1171 | "globals": "^13.19.0", 1172 | "graphemer": "^1.4.0", 1173 | "ignore": "^5.2.0", 1174 | "imurmurhash": "^0.1.4", 1175 | "is-glob": "^4.0.0", 1176 | "is-path-inside": "^3.0.3", 1177 | "js-yaml": "^4.1.0", 1178 | "json-stable-stringify-without-jsonify": "^1.0.1", 1179 | "levn": "^0.4.1", 1180 | "lodash.merge": "^4.6.2", 1181 | "minimatch": "^3.1.2", 1182 | "natural-compare": "^1.4.0", 1183 | "optionator": "^0.9.3", 1184 | "strip-ansi": "^6.0.1", 1185 | "text-table": "^0.2.0" 1186 | }, 1187 | "bin": { 1188 | "eslint": "bin/eslint.js" 1189 | }, 1190 | "engines": { 1191 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1192 | }, 1193 | "funding": { 1194 | "url": "https://opencollective.com/eslint" 1195 | } 1196 | }, 1197 | "node_modules/eslint-scope": { 1198 | "version": "5.1.1", 1199 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 1200 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 1201 | "dev": true, 1202 | "license": "BSD-2-Clause", 1203 | "dependencies": { 1204 | "esrecurse": "^4.3.0", 1205 | "estraverse": "^4.1.1" 1206 | }, 1207 | "engines": { 1208 | "node": ">=8.0.0" 1209 | } 1210 | }, 1211 | "node_modules/eslint-utils": { 1212 | "version": "3.0.0", 1213 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 1214 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 1215 | "dev": true, 1216 | "license": "MIT", 1217 | "dependencies": { 1218 | "eslint-visitor-keys": "^2.0.0" 1219 | }, 1220 | "engines": { 1221 | "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" 1222 | }, 1223 | "funding": { 1224 | "url": "https://github.com/sponsors/mysticatea" 1225 | }, 1226 | "peerDependencies": { 1227 | "eslint": ">=5" 1228 | } 1229 | }, 1230 | "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { 1231 | "version": "2.1.0", 1232 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 1233 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 1234 | "dev": true, 1235 | "license": "Apache-2.0", 1236 | "engines": { 1237 | "node": ">=10" 1238 | } 1239 | }, 1240 | "node_modules/eslint-visitor-keys": { 1241 | "version": "3.4.3", 1242 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 1243 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 1244 | "dev": true, 1245 | "license": "Apache-2.0", 1246 | "engines": { 1247 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1248 | }, 1249 | "funding": { 1250 | "url": "https://opencollective.com/eslint" 1251 | } 1252 | }, 1253 | "node_modules/eslint/node_modules/eslint-scope": { 1254 | "version": "7.2.2", 1255 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", 1256 | "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", 1257 | "dev": true, 1258 | "license": "BSD-2-Clause", 1259 | "peer": true, 1260 | "dependencies": { 1261 | "esrecurse": "^4.3.0", 1262 | "estraverse": "^5.2.0" 1263 | }, 1264 | "engines": { 1265 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1266 | }, 1267 | "funding": { 1268 | "url": "https://opencollective.com/eslint" 1269 | } 1270 | }, 1271 | "node_modules/eslint/node_modules/estraverse": { 1272 | "version": "5.3.0", 1273 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1274 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1275 | "dev": true, 1276 | "license": "BSD-2-Clause", 1277 | "peer": true, 1278 | "engines": { 1279 | "node": ">=4.0" 1280 | } 1281 | }, 1282 | "node_modules/espree": { 1283 | "version": "9.6.1", 1284 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", 1285 | "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", 1286 | "dev": true, 1287 | "license": "BSD-2-Clause", 1288 | "peer": true, 1289 | "dependencies": { 1290 | "acorn": "^8.9.0", 1291 | "acorn-jsx": "^5.3.2", 1292 | "eslint-visitor-keys": "^3.4.1" 1293 | }, 1294 | "engines": { 1295 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1296 | }, 1297 | "funding": { 1298 | "url": "https://opencollective.com/eslint" 1299 | } 1300 | }, 1301 | "node_modules/esquery": { 1302 | "version": "1.6.0", 1303 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 1304 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 1305 | "dev": true, 1306 | "license": "BSD-3-Clause", 1307 | "peer": true, 1308 | "dependencies": { 1309 | "estraverse": "^5.1.0" 1310 | }, 1311 | "engines": { 1312 | "node": ">=0.10" 1313 | } 1314 | }, 1315 | "node_modules/esquery/node_modules/estraverse": { 1316 | "version": "5.3.0", 1317 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1318 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1319 | "dev": true, 1320 | "license": "BSD-2-Clause", 1321 | "peer": true, 1322 | "engines": { 1323 | "node": ">=4.0" 1324 | } 1325 | }, 1326 | "node_modules/esrecurse": { 1327 | "version": "4.3.0", 1328 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1329 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1330 | "dev": true, 1331 | "license": "BSD-2-Clause", 1332 | "dependencies": { 1333 | "estraverse": "^5.2.0" 1334 | }, 1335 | "engines": { 1336 | "node": ">=4.0" 1337 | } 1338 | }, 1339 | "node_modules/esrecurse/node_modules/estraverse": { 1340 | "version": "5.3.0", 1341 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1342 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1343 | "dev": true, 1344 | "license": "BSD-2-Clause", 1345 | "engines": { 1346 | "node": ">=4.0" 1347 | } 1348 | }, 1349 | "node_modules/estraverse": { 1350 | "version": "4.3.0", 1351 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 1352 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 1353 | "dev": true, 1354 | "license": "BSD-2-Clause", 1355 | "engines": { 1356 | "node": ">=4.0" 1357 | } 1358 | }, 1359 | "node_modules/esutils": { 1360 | "version": "2.0.3", 1361 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1362 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1363 | "dev": true, 1364 | "license": "BSD-2-Clause", 1365 | "peer": true, 1366 | "engines": { 1367 | "node": ">=0.10.0" 1368 | } 1369 | }, 1370 | "node_modules/fast-deep-equal": { 1371 | "version": "3.1.3", 1372 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1373 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1374 | "dev": true, 1375 | "license": "MIT", 1376 | "peer": true 1377 | }, 1378 | "node_modules/fast-glob": { 1379 | "version": "3.3.3", 1380 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 1381 | "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 1382 | "dev": true, 1383 | "license": "MIT", 1384 | "dependencies": { 1385 | "@nodelib/fs.stat": "^2.0.2", 1386 | "@nodelib/fs.walk": "^1.2.3", 1387 | "glob-parent": "^5.1.2", 1388 | "merge2": "^1.3.0", 1389 | "micromatch": "^4.0.8" 1390 | }, 1391 | "engines": { 1392 | "node": ">=8.6.0" 1393 | } 1394 | }, 1395 | "node_modules/fast-glob/node_modules/glob-parent": { 1396 | "version": "5.1.2", 1397 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1398 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1399 | "dev": true, 1400 | "license": "ISC", 1401 | "dependencies": { 1402 | "is-glob": "^4.0.1" 1403 | }, 1404 | "engines": { 1405 | "node": ">= 6" 1406 | } 1407 | }, 1408 | "node_modules/fast-json-stable-stringify": { 1409 | "version": "2.1.0", 1410 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1411 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1412 | "dev": true, 1413 | "license": "MIT", 1414 | "peer": true 1415 | }, 1416 | "node_modules/fast-levenshtein": { 1417 | "version": "2.0.6", 1418 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1419 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 1420 | "dev": true, 1421 | "license": "MIT", 1422 | "peer": true 1423 | }, 1424 | "node_modules/fastq": { 1425 | "version": "1.19.0", 1426 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", 1427 | "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", 1428 | "dev": true, 1429 | "license": "ISC", 1430 | "dependencies": { 1431 | "reusify": "^1.0.4" 1432 | } 1433 | }, 1434 | "node_modules/file-entry-cache": { 1435 | "version": "6.0.1", 1436 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 1437 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 1438 | "dev": true, 1439 | "license": "MIT", 1440 | "peer": true, 1441 | "dependencies": { 1442 | "flat-cache": "^3.0.4" 1443 | }, 1444 | "engines": { 1445 | "node": "^10.12.0 || >=12.0.0" 1446 | } 1447 | }, 1448 | "node_modules/fill-range": { 1449 | "version": "7.1.1", 1450 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1451 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1452 | "dev": true, 1453 | "license": "MIT", 1454 | "dependencies": { 1455 | "to-regex-range": "^5.0.1" 1456 | }, 1457 | "engines": { 1458 | "node": ">=8" 1459 | } 1460 | }, 1461 | "node_modules/find-up": { 1462 | "version": "5.0.0", 1463 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1464 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1465 | "dev": true, 1466 | "license": "MIT", 1467 | "peer": true, 1468 | "dependencies": { 1469 | "locate-path": "^6.0.0", 1470 | "path-exists": "^4.0.0" 1471 | }, 1472 | "engines": { 1473 | "node": ">=10" 1474 | }, 1475 | "funding": { 1476 | "url": "https://github.com/sponsors/sindresorhus" 1477 | } 1478 | }, 1479 | "node_modules/flat-cache": { 1480 | "version": "3.2.0", 1481 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", 1482 | "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", 1483 | "dev": true, 1484 | "license": "MIT", 1485 | "peer": true, 1486 | "dependencies": { 1487 | "flatted": "^3.2.9", 1488 | "keyv": "^4.5.3", 1489 | "rimraf": "^3.0.2" 1490 | }, 1491 | "engines": { 1492 | "node": "^10.12.0 || >=12.0.0" 1493 | } 1494 | }, 1495 | "node_modules/flatted": { 1496 | "version": "3.3.2", 1497 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", 1498 | "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", 1499 | "dev": true, 1500 | "license": "ISC", 1501 | "peer": true 1502 | }, 1503 | "node_modules/fs.realpath": { 1504 | "version": "1.0.0", 1505 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1506 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1507 | "dev": true, 1508 | "license": "ISC", 1509 | "peer": true 1510 | }, 1511 | "node_modules/functional-red-black-tree": { 1512 | "version": "1.0.1", 1513 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 1514 | "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", 1515 | "dev": true, 1516 | "license": "MIT" 1517 | }, 1518 | "node_modules/glob": { 1519 | "version": "7.2.3", 1520 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1521 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1522 | "deprecated": "Glob versions prior to v9 are no longer supported", 1523 | "dev": true, 1524 | "license": "ISC", 1525 | "peer": true, 1526 | "dependencies": { 1527 | "fs.realpath": "^1.0.0", 1528 | "inflight": "^1.0.4", 1529 | "inherits": "2", 1530 | "minimatch": "^3.1.1", 1531 | "once": "^1.3.0", 1532 | "path-is-absolute": "^1.0.0" 1533 | }, 1534 | "engines": { 1535 | "node": "*" 1536 | }, 1537 | "funding": { 1538 | "url": "https://github.com/sponsors/isaacs" 1539 | } 1540 | }, 1541 | "node_modules/glob-parent": { 1542 | "version": "6.0.2", 1543 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1544 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1545 | "dev": true, 1546 | "license": "ISC", 1547 | "peer": true, 1548 | "dependencies": { 1549 | "is-glob": "^4.0.3" 1550 | }, 1551 | "engines": { 1552 | "node": ">=10.13.0" 1553 | } 1554 | }, 1555 | "node_modules/globals": { 1556 | "version": "13.24.0", 1557 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", 1558 | "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", 1559 | "dev": true, 1560 | "license": "MIT", 1561 | "peer": true, 1562 | "dependencies": { 1563 | "type-fest": "^0.20.2" 1564 | }, 1565 | "engines": { 1566 | "node": ">=8" 1567 | }, 1568 | "funding": { 1569 | "url": "https://github.com/sponsors/sindresorhus" 1570 | } 1571 | }, 1572 | "node_modules/globby": { 1573 | "version": "11.1.0", 1574 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", 1575 | "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", 1576 | "dev": true, 1577 | "license": "MIT", 1578 | "dependencies": { 1579 | "array-union": "^2.1.0", 1580 | "dir-glob": "^3.0.1", 1581 | "fast-glob": "^3.2.9", 1582 | "ignore": "^5.2.0", 1583 | "merge2": "^1.4.1", 1584 | "slash": "^3.0.0" 1585 | }, 1586 | "engines": { 1587 | "node": ">=10" 1588 | }, 1589 | "funding": { 1590 | "url": "https://github.com/sponsors/sindresorhus" 1591 | } 1592 | }, 1593 | "node_modules/graphemer": { 1594 | "version": "1.4.0", 1595 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1596 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1597 | "dev": true, 1598 | "license": "MIT", 1599 | "peer": true 1600 | }, 1601 | "node_modules/has-flag": { 1602 | "version": "4.0.0", 1603 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1604 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1605 | "dev": true, 1606 | "license": "MIT", 1607 | "peer": true, 1608 | "engines": { 1609 | "node": ">=8" 1610 | } 1611 | }, 1612 | "node_modules/ignore": { 1613 | "version": "5.3.2", 1614 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1615 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1616 | "dev": true, 1617 | "license": "MIT", 1618 | "engines": { 1619 | "node": ">= 4" 1620 | } 1621 | }, 1622 | "node_modules/import-fresh": { 1623 | "version": "3.3.0", 1624 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1625 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1626 | "dev": true, 1627 | "license": "MIT", 1628 | "peer": true, 1629 | "dependencies": { 1630 | "parent-module": "^1.0.0", 1631 | "resolve-from": "^4.0.0" 1632 | }, 1633 | "engines": { 1634 | "node": ">=6" 1635 | }, 1636 | "funding": { 1637 | "url": "https://github.com/sponsors/sindresorhus" 1638 | } 1639 | }, 1640 | "node_modules/imurmurhash": { 1641 | "version": "0.1.4", 1642 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1643 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1644 | "dev": true, 1645 | "license": "MIT", 1646 | "peer": true, 1647 | "engines": { 1648 | "node": ">=0.8.19" 1649 | } 1650 | }, 1651 | "node_modules/inflight": { 1652 | "version": "1.0.6", 1653 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1654 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1655 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 1656 | "dev": true, 1657 | "license": "ISC", 1658 | "peer": true, 1659 | "dependencies": { 1660 | "once": "^1.3.0", 1661 | "wrappy": "1" 1662 | } 1663 | }, 1664 | "node_modules/inherits": { 1665 | "version": "2.0.4", 1666 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1667 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1668 | "dev": true, 1669 | "license": "ISC", 1670 | "peer": true 1671 | }, 1672 | "node_modules/is-extglob": { 1673 | "version": "2.1.1", 1674 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1675 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1676 | "dev": true, 1677 | "license": "MIT", 1678 | "engines": { 1679 | "node": ">=0.10.0" 1680 | } 1681 | }, 1682 | "node_modules/is-glob": { 1683 | "version": "4.0.3", 1684 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1685 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1686 | "dev": true, 1687 | "license": "MIT", 1688 | "dependencies": { 1689 | "is-extglob": "^2.1.1" 1690 | }, 1691 | "engines": { 1692 | "node": ">=0.10.0" 1693 | } 1694 | }, 1695 | "node_modules/is-number": { 1696 | "version": "7.0.0", 1697 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1698 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1699 | "dev": true, 1700 | "license": "MIT", 1701 | "engines": { 1702 | "node": ">=0.12.0" 1703 | } 1704 | }, 1705 | "node_modules/is-path-inside": { 1706 | "version": "3.0.3", 1707 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 1708 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", 1709 | "dev": true, 1710 | "license": "MIT", 1711 | "peer": true, 1712 | "engines": { 1713 | "node": ">=8" 1714 | } 1715 | }, 1716 | "node_modules/isexe": { 1717 | "version": "2.0.0", 1718 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1719 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1720 | "dev": true, 1721 | "license": "ISC", 1722 | "peer": true 1723 | }, 1724 | "node_modules/js-yaml": { 1725 | "version": "4.1.0", 1726 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1727 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1728 | "dev": true, 1729 | "license": "MIT", 1730 | "peer": true, 1731 | "dependencies": { 1732 | "argparse": "^2.0.1" 1733 | }, 1734 | "bin": { 1735 | "js-yaml": "bin/js-yaml.js" 1736 | } 1737 | }, 1738 | "node_modules/json-buffer": { 1739 | "version": "3.0.1", 1740 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1741 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1742 | "dev": true, 1743 | "license": "MIT", 1744 | "peer": true 1745 | }, 1746 | "node_modules/json-schema-traverse": { 1747 | "version": "0.4.1", 1748 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1749 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1750 | "dev": true, 1751 | "license": "MIT", 1752 | "peer": true 1753 | }, 1754 | "node_modules/json-stable-stringify-without-jsonify": { 1755 | "version": "1.0.1", 1756 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1757 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1758 | "dev": true, 1759 | "license": "MIT", 1760 | "peer": true 1761 | }, 1762 | "node_modules/keyv": { 1763 | "version": "4.5.4", 1764 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1765 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1766 | "dev": true, 1767 | "license": "MIT", 1768 | "peer": true, 1769 | "dependencies": { 1770 | "json-buffer": "3.0.1" 1771 | } 1772 | }, 1773 | "node_modules/levn": { 1774 | "version": "0.4.1", 1775 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1776 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1777 | "dev": true, 1778 | "license": "MIT", 1779 | "peer": true, 1780 | "dependencies": { 1781 | "prelude-ls": "^1.2.1", 1782 | "type-check": "~0.4.0" 1783 | }, 1784 | "engines": { 1785 | "node": ">= 0.8.0" 1786 | } 1787 | }, 1788 | "node_modules/locate-path": { 1789 | "version": "6.0.0", 1790 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1791 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1792 | "dev": true, 1793 | "license": "MIT", 1794 | "peer": true, 1795 | "dependencies": { 1796 | "p-locate": "^5.0.0" 1797 | }, 1798 | "engines": { 1799 | "node": ">=10" 1800 | }, 1801 | "funding": { 1802 | "url": "https://github.com/sponsors/sindresorhus" 1803 | } 1804 | }, 1805 | "node_modules/lodash.merge": { 1806 | "version": "4.6.2", 1807 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1808 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1809 | "dev": true, 1810 | "license": "MIT", 1811 | "peer": true 1812 | }, 1813 | "node_modules/merge2": { 1814 | "version": "1.4.1", 1815 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1816 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1817 | "dev": true, 1818 | "license": "MIT", 1819 | "engines": { 1820 | "node": ">= 8" 1821 | } 1822 | }, 1823 | "node_modules/micromatch": { 1824 | "version": "4.0.8", 1825 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1826 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1827 | "dev": true, 1828 | "license": "MIT", 1829 | "dependencies": { 1830 | "braces": "^3.0.3", 1831 | "picomatch": "^2.3.1" 1832 | }, 1833 | "engines": { 1834 | "node": ">=8.6" 1835 | } 1836 | }, 1837 | "node_modules/minimatch": { 1838 | "version": "3.1.2", 1839 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1840 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1841 | "dev": true, 1842 | "license": "ISC", 1843 | "peer": true, 1844 | "dependencies": { 1845 | "brace-expansion": "^1.1.7" 1846 | }, 1847 | "engines": { 1848 | "node": "*" 1849 | } 1850 | }, 1851 | "node_modules/moment": { 1852 | "version": "2.29.4", 1853 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", 1854 | "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", 1855 | "dev": true, 1856 | "license": "MIT", 1857 | "engines": { 1858 | "node": "*" 1859 | } 1860 | }, 1861 | "node_modules/ms": { 1862 | "version": "2.1.3", 1863 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1864 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1865 | "dev": true, 1866 | "license": "MIT" 1867 | }, 1868 | "node_modules/natural-compare": { 1869 | "version": "1.4.0", 1870 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1871 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1872 | "dev": true, 1873 | "license": "MIT", 1874 | "peer": true 1875 | }, 1876 | "node_modules/obsidian": { 1877 | "version": "1.7.2", 1878 | "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.7.2.tgz", 1879 | "integrity": "sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==", 1880 | "dev": true, 1881 | "license": "MIT", 1882 | "dependencies": { 1883 | "@types/codemirror": "5.60.8", 1884 | "moment": "2.29.4" 1885 | }, 1886 | "peerDependencies": { 1887 | "@codemirror/state": "^6.0.0", 1888 | "@codemirror/view": "^6.0.0" 1889 | } 1890 | }, 1891 | "node_modules/once": { 1892 | "version": "1.4.0", 1893 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1894 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1895 | "dev": true, 1896 | "license": "ISC", 1897 | "peer": true, 1898 | "dependencies": { 1899 | "wrappy": "1" 1900 | } 1901 | }, 1902 | "node_modules/optionator": { 1903 | "version": "0.9.4", 1904 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 1905 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 1906 | "dev": true, 1907 | "license": "MIT", 1908 | "peer": true, 1909 | "dependencies": { 1910 | "deep-is": "^0.1.3", 1911 | "fast-levenshtein": "^2.0.6", 1912 | "levn": "^0.4.1", 1913 | "prelude-ls": "^1.2.1", 1914 | "type-check": "^0.4.0", 1915 | "word-wrap": "^1.2.5" 1916 | }, 1917 | "engines": { 1918 | "node": ">= 0.8.0" 1919 | } 1920 | }, 1921 | "node_modules/p-limit": { 1922 | "version": "3.1.0", 1923 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1924 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1925 | "dev": true, 1926 | "license": "MIT", 1927 | "peer": true, 1928 | "dependencies": { 1929 | "yocto-queue": "^0.1.0" 1930 | }, 1931 | "engines": { 1932 | "node": ">=10" 1933 | }, 1934 | "funding": { 1935 | "url": "https://github.com/sponsors/sindresorhus" 1936 | } 1937 | }, 1938 | "node_modules/p-locate": { 1939 | "version": "5.0.0", 1940 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1941 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1942 | "dev": true, 1943 | "license": "MIT", 1944 | "peer": true, 1945 | "dependencies": { 1946 | "p-limit": "^3.0.2" 1947 | }, 1948 | "engines": { 1949 | "node": ">=10" 1950 | }, 1951 | "funding": { 1952 | "url": "https://github.com/sponsors/sindresorhus" 1953 | } 1954 | }, 1955 | "node_modules/parent-module": { 1956 | "version": "1.0.1", 1957 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1958 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1959 | "dev": true, 1960 | "license": "MIT", 1961 | "peer": true, 1962 | "dependencies": { 1963 | "callsites": "^3.0.0" 1964 | }, 1965 | "engines": { 1966 | "node": ">=6" 1967 | } 1968 | }, 1969 | "node_modules/path-exists": { 1970 | "version": "4.0.0", 1971 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1972 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1973 | "dev": true, 1974 | "license": "MIT", 1975 | "peer": true, 1976 | "engines": { 1977 | "node": ">=8" 1978 | } 1979 | }, 1980 | "node_modules/path-is-absolute": { 1981 | "version": "1.0.1", 1982 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1983 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1984 | "dev": true, 1985 | "license": "MIT", 1986 | "peer": true, 1987 | "engines": { 1988 | "node": ">=0.10.0" 1989 | } 1990 | }, 1991 | "node_modules/path-key": { 1992 | "version": "3.1.1", 1993 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1994 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1995 | "dev": true, 1996 | "license": "MIT", 1997 | "peer": true, 1998 | "engines": { 1999 | "node": ">=8" 2000 | } 2001 | }, 2002 | "node_modules/path-type": { 2003 | "version": "4.0.0", 2004 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 2005 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 2006 | "dev": true, 2007 | "license": "MIT", 2008 | "engines": { 2009 | "node": ">=8" 2010 | } 2011 | }, 2012 | "node_modules/picomatch": { 2013 | "version": "2.3.1", 2014 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2015 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 2016 | "dev": true, 2017 | "license": "MIT", 2018 | "engines": { 2019 | "node": ">=8.6" 2020 | }, 2021 | "funding": { 2022 | "url": "https://github.com/sponsors/jonschlinkert" 2023 | } 2024 | }, 2025 | "node_modules/prelude-ls": { 2026 | "version": "1.2.1", 2027 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 2028 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 2029 | "dev": true, 2030 | "license": "MIT", 2031 | "peer": true, 2032 | "engines": { 2033 | "node": ">= 0.8.0" 2034 | } 2035 | }, 2036 | "node_modules/punycode": { 2037 | "version": "2.3.1", 2038 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 2039 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 2040 | "dev": true, 2041 | "license": "MIT", 2042 | "peer": true, 2043 | "engines": { 2044 | "node": ">=6" 2045 | } 2046 | }, 2047 | "node_modules/queue-microtask": { 2048 | "version": "1.2.3", 2049 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2050 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2051 | "dev": true, 2052 | "funding": [ 2053 | { 2054 | "type": "github", 2055 | "url": "https://github.com/sponsors/feross" 2056 | }, 2057 | { 2058 | "type": "patreon", 2059 | "url": "https://www.patreon.com/feross" 2060 | }, 2061 | { 2062 | "type": "consulting", 2063 | "url": "https://feross.org/support" 2064 | } 2065 | ], 2066 | "license": "MIT" 2067 | }, 2068 | "node_modules/regexpp": { 2069 | "version": "3.2.0", 2070 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 2071 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 2072 | "dev": true, 2073 | "license": "MIT", 2074 | "engines": { 2075 | "node": ">=8" 2076 | }, 2077 | "funding": { 2078 | "url": "https://github.com/sponsors/mysticatea" 2079 | } 2080 | }, 2081 | "node_modules/resolve-from": { 2082 | "version": "4.0.0", 2083 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 2084 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 2085 | "dev": true, 2086 | "license": "MIT", 2087 | "peer": true, 2088 | "engines": { 2089 | "node": ">=4" 2090 | } 2091 | }, 2092 | "node_modules/reusify": { 2093 | "version": "1.0.4", 2094 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 2095 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 2096 | "dev": true, 2097 | "license": "MIT", 2098 | "engines": { 2099 | "iojs": ">=1.0.0", 2100 | "node": ">=0.10.0" 2101 | } 2102 | }, 2103 | "node_modules/rimraf": { 2104 | "version": "3.0.2", 2105 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 2106 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 2107 | "deprecated": "Rimraf versions prior to v4 are no longer supported", 2108 | "dev": true, 2109 | "license": "ISC", 2110 | "peer": true, 2111 | "dependencies": { 2112 | "glob": "^7.1.3" 2113 | }, 2114 | "bin": { 2115 | "rimraf": "bin.js" 2116 | }, 2117 | "funding": { 2118 | "url": "https://github.com/sponsors/isaacs" 2119 | } 2120 | }, 2121 | "node_modules/run-parallel": { 2122 | "version": "1.2.0", 2123 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2124 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2125 | "dev": true, 2126 | "funding": [ 2127 | { 2128 | "type": "github", 2129 | "url": "https://github.com/sponsors/feross" 2130 | }, 2131 | { 2132 | "type": "patreon", 2133 | "url": "https://www.patreon.com/feross" 2134 | }, 2135 | { 2136 | "type": "consulting", 2137 | "url": "https://feross.org/support" 2138 | } 2139 | ], 2140 | "license": "MIT", 2141 | "dependencies": { 2142 | "queue-microtask": "^1.2.2" 2143 | } 2144 | }, 2145 | "node_modules/semver": { 2146 | "version": "7.7.0", 2147 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", 2148 | "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", 2149 | "dev": true, 2150 | "license": "ISC", 2151 | "bin": { 2152 | "semver": "bin/semver.js" 2153 | }, 2154 | "engines": { 2155 | "node": ">=10" 2156 | } 2157 | }, 2158 | "node_modules/shebang-command": { 2159 | "version": "2.0.0", 2160 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2161 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2162 | "dev": true, 2163 | "license": "MIT", 2164 | "peer": true, 2165 | "dependencies": { 2166 | "shebang-regex": "^3.0.0" 2167 | }, 2168 | "engines": { 2169 | "node": ">=8" 2170 | } 2171 | }, 2172 | "node_modules/shebang-regex": { 2173 | "version": "3.0.0", 2174 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2175 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2176 | "dev": true, 2177 | "license": "MIT", 2178 | "peer": true, 2179 | "engines": { 2180 | "node": ">=8" 2181 | } 2182 | }, 2183 | "node_modules/slash": { 2184 | "version": "3.0.0", 2185 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 2186 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 2187 | "dev": true, 2188 | "license": "MIT", 2189 | "engines": { 2190 | "node": ">=8" 2191 | } 2192 | }, 2193 | "node_modules/strip-ansi": { 2194 | "version": "6.0.1", 2195 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2196 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2197 | "dev": true, 2198 | "license": "MIT", 2199 | "peer": true, 2200 | "dependencies": { 2201 | "ansi-regex": "^5.0.1" 2202 | }, 2203 | "engines": { 2204 | "node": ">=8" 2205 | } 2206 | }, 2207 | "node_modules/strip-json-comments": { 2208 | "version": "3.1.1", 2209 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2210 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2211 | "dev": true, 2212 | "license": "MIT", 2213 | "peer": true, 2214 | "engines": { 2215 | "node": ">=8" 2216 | }, 2217 | "funding": { 2218 | "url": "https://github.com/sponsors/sindresorhus" 2219 | } 2220 | }, 2221 | "node_modules/style-mod": { 2222 | "version": "4.1.2", 2223 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 2224 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", 2225 | "dev": true, 2226 | "license": "MIT", 2227 | "peer": true 2228 | }, 2229 | "node_modules/supports-color": { 2230 | "version": "7.2.0", 2231 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2232 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2233 | "dev": true, 2234 | "license": "MIT", 2235 | "peer": true, 2236 | "dependencies": { 2237 | "has-flag": "^4.0.0" 2238 | }, 2239 | "engines": { 2240 | "node": ">=8" 2241 | } 2242 | }, 2243 | "node_modules/text-table": { 2244 | "version": "0.2.0", 2245 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 2246 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 2247 | "dev": true, 2248 | "license": "MIT", 2249 | "peer": true 2250 | }, 2251 | "node_modules/to-regex-range": { 2252 | "version": "5.0.1", 2253 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2254 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2255 | "dev": true, 2256 | "license": "MIT", 2257 | "dependencies": { 2258 | "is-number": "^7.0.0" 2259 | }, 2260 | "engines": { 2261 | "node": ">=8.0" 2262 | } 2263 | }, 2264 | "node_modules/tslib": { 2265 | "version": "2.4.0", 2266 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 2267 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", 2268 | "dev": true, 2269 | "license": "0BSD" 2270 | }, 2271 | "node_modules/tsutils": { 2272 | "version": "3.21.0", 2273 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", 2274 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", 2275 | "dev": true, 2276 | "license": "MIT", 2277 | "dependencies": { 2278 | "tslib": "^1.8.1" 2279 | }, 2280 | "engines": { 2281 | "node": ">= 6" 2282 | }, 2283 | "peerDependencies": { 2284 | "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" 2285 | } 2286 | }, 2287 | "node_modules/tsutils/node_modules/tslib": { 2288 | "version": "1.14.1", 2289 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 2290 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 2291 | "dev": true, 2292 | "license": "0BSD" 2293 | }, 2294 | "node_modules/type-check": { 2295 | "version": "0.4.0", 2296 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 2297 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 2298 | "dev": true, 2299 | "license": "MIT", 2300 | "peer": true, 2301 | "dependencies": { 2302 | "prelude-ls": "^1.2.1" 2303 | }, 2304 | "engines": { 2305 | "node": ">= 0.8.0" 2306 | } 2307 | }, 2308 | "node_modules/type-fest": { 2309 | "version": "0.20.2", 2310 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 2311 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 2312 | "dev": true, 2313 | "license": "(MIT OR CC0-1.0)", 2314 | "peer": true, 2315 | "engines": { 2316 | "node": ">=10" 2317 | }, 2318 | "funding": { 2319 | "url": "https://github.com/sponsors/sindresorhus" 2320 | } 2321 | }, 2322 | "node_modules/typescript": { 2323 | "version": "4.7.4", 2324 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 2325 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 2326 | "dev": true, 2327 | "license": "Apache-2.0", 2328 | "bin": { 2329 | "tsc": "bin/tsc", 2330 | "tsserver": "bin/tsserver" 2331 | }, 2332 | "engines": { 2333 | "node": ">=4.2.0" 2334 | } 2335 | }, 2336 | "node_modules/uri-js": { 2337 | "version": "4.4.1", 2338 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2339 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2340 | "dev": true, 2341 | "license": "BSD-2-Clause", 2342 | "peer": true, 2343 | "dependencies": { 2344 | "punycode": "^2.1.0" 2345 | } 2346 | }, 2347 | "node_modules/w3c-keyname": { 2348 | "version": "2.2.8", 2349 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 2350 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 2351 | "dev": true, 2352 | "license": "MIT", 2353 | "peer": true 2354 | }, 2355 | "node_modules/which": { 2356 | "version": "2.0.2", 2357 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2358 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2359 | "dev": true, 2360 | "license": "ISC", 2361 | "peer": true, 2362 | "dependencies": { 2363 | "isexe": "^2.0.0" 2364 | }, 2365 | "bin": { 2366 | "node-which": "bin/node-which" 2367 | }, 2368 | "engines": { 2369 | "node": ">= 8" 2370 | } 2371 | }, 2372 | "node_modules/word-wrap": { 2373 | "version": "1.2.5", 2374 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 2375 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 2376 | "dev": true, 2377 | "license": "MIT", 2378 | "peer": true, 2379 | "engines": { 2380 | "node": ">=0.10.0" 2381 | } 2382 | }, 2383 | "node_modules/wrappy": { 2384 | "version": "1.0.2", 2385 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2386 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2387 | "dev": true, 2388 | "license": "ISC", 2389 | "peer": true 2390 | }, 2391 | "node_modules/yocto-queue": { 2392 | "version": "0.1.0", 2393 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2394 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2395 | "dev": true, 2396 | "license": "MIT", 2397 | "peer": true, 2398 | "engines": { 2399 | "node": ">=10" 2400 | }, 2401 | "funding": { 2402 | "url": "https://github.com/sponsors/sindresorhus" 2403 | } 2404 | } 2405 | } 2406 | } 2407 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reading-view-enhancer", 3 | "version": "0.2.1", 4 | "description": "This is a Obsidian plugin that enhances reading view. Use arrow keys to navigate between blocks or toggle collapse.", 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": "Galacsh ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "color2k": "^2.0.3" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^16.18.125", 19 | "@typescript-eslint/eslint-plugin": "5.29.0", 20 | "@typescript-eslint/parser": "5.29.0", 21 | "builtin-modules": "3.3.0", 22 | "esbuild": "0.17.3", 23 | "obsidian": "^1.7.2", 24 | "tslib": "2.4.0", 25 | "typescript": "4.7.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/block-selector/index.ts: -------------------------------------------------------------------------------- 1 | import { Platform, type MarkdownPostProcessorContext } from "obsidian"; 2 | 3 | import ReadingViewEnhancer from "src/main"; 4 | import SelectionHandler from "./selection-handler"; 5 | import { BLOCKS, BLOCK_ATTR, BLOCK_SELECTOR, FRONTMATTER } from "../constants"; 6 | import { 7 | getActiveView, 8 | getReadingViewContainer, 9 | isReadingView, 10 | } from "src/utils"; 11 | 12 | /** 13 | * BlockSelector enables to navigate between blocks and toggle collapse. 14 | * 15 | * Block elements are elements that are having block level elements. 16 | * For example, a paragraph is a block element. 17 | * 18 | * You can select a block by clicking on it and then use arrow keys to navigate between blocks. 19 | * For selected block, the background color will be changed. 20 | * You can also use `ArrowLeft` and `ArrowRight` to toggle collapse. 21 | * Collapsible blocks have `collapse-indicator` or `callout-fold` class. 22 | */ 23 | export default class BlockSelector { 24 | plugin: ReadingViewEnhancer; 25 | selectionHandler: SelectionHandler; 26 | 27 | /** 28 | * Initialize BlockSelector. 29 | * Register markdown post processor to blockify some elements. 30 | * 31 | * @param plugin {ReadingViewEnhancer} Plugin instance 32 | */ 33 | constructor(plugin: ReadingViewEnhancer) { 34 | this.plugin = plugin; 35 | this.selectionHandler = new SelectionHandler(plugin); 36 | } 37 | 38 | /** 39 | * Activate BlockSelector 40 | */ 41 | activate() { 42 | this.plugin.registerMarkdownPostProcessor(this.blockify.bind(this)); 43 | this.plugin.registerEvent( 44 | this.plugin.app.workspace.on( 45 | "layout-change", 46 | this.autoSelectTopBlock.bind(this) 47 | ) 48 | ); 49 | this.plugin.registerEvent( 50 | this.plugin.app.workspace.on( 51 | "active-leaf-change", 52 | this.autoSelectTopBlock.bind(this) 53 | ) 54 | ); 55 | } 56 | 57 | autoSelectTopBlock() { 58 | if (!this.plugin.settings.autoSelectTopBlock) return; 59 | 60 | const view = getActiveView(this.plugin); 61 | if (isReadingView(view)) { 62 | const containerEl = getReadingViewContainer(view); 63 | if (containerEl) { 64 | this.selectTopBlockInTheView(containerEl); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Select top block in the view 71 | * 72 | * @param viewContainer {HTMLElement} View container element 73 | */ 74 | selectTopBlockInTheView(viewContainer: HTMLElement) { 75 | this.selectionHandler.selectTopBlockInTheView(viewContainer); 76 | } 77 | 78 | toggleBlockHighlight() { 79 | this.selectionHandler.selectedBlockHighlight(); 80 | } 81 | 82 | /** 83 | * Blockify some elements. 84 | * If container is not initialized, initialize it. 85 | * Transform some elements to block elements. 86 | */ 87 | private blockify( 88 | element: HTMLElement, 89 | context: MarkdownPostProcessorContext 90 | ) { 91 | // If block selector is disabled, do nothing 92 | if (!this.plugin.settings.enableBlockSelector) return; 93 | 94 | // If it's mobile but block selector is disabled on mobile, do nothing 95 | if ( 96 | (Platform.isMobile || Platform.isMobileApp) && 97 | this.plugin.settings.disableBlockSelectorOnMobile 98 | ) { 99 | return; 100 | } 101 | 102 | // @ts-ignore 103 | const container = context?.containerEl; 104 | if (this.isContainerNotInitialized(container)) { 105 | this.initializeContainer(container); 106 | } 107 | 108 | this.elementsToBlocks(element, context); 109 | } 110 | 111 | /** 112 | * Check if container is initialized. 113 | * 114 | * @param container Container element 115 | * @returns True if container is initialized 116 | */ 117 | private isContainerNotInitialized(container: HTMLElement) { 118 | return ( 119 | container instanceof HTMLElement && !container.hasClass(BLOCK_SELECTOR) 120 | ); 121 | } 122 | 123 | /** 124 | * Initialize container. 125 | * Add some event listeners to container. 126 | * 127 | * @param container Container element 128 | */ 129 | private initializeContainer(container: HTMLElement) { 130 | // Mark container as initialized 131 | container.addClass(BLOCK_SELECTOR); 132 | 133 | // Mouse and touch events handlers to detect tap or click event 134 | container.addEventListener( 135 | "mousedown", 136 | this.selectionHandler.handleMouseTouchStart.bind(this.selectionHandler) 137 | ); 138 | container.addEventListener( 139 | "touchstart", 140 | this.selectionHandler.handleMouseTouchStart.bind(this.selectionHandler) 141 | ); 142 | container.addEventListener( 143 | "mouseup", 144 | this.selectionHandler.handleMouseTouchEnd.bind(this.selectionHandler) 145 | ); 146 | container.addEventListener( 147 | "touchend", 148 | this.selectionHandler.handleMouseTouchEnd.bind(this.selectionHandler) 149 | ); 150 | 151 | // On focusout, deselect block element 152 | container.addEventListener( 153 | "focusout", 154 | this.selectionHandler.deselect.bind(this.selectionHandler) 155 | ); 156 | 157 | // On keydown, navigate between blocks or toggle collapse 158 | container.addEventListener( 159 | "keydown", 160 | this.selectionHandler.onKeyDown.bind(this.selectionHandler) 161 | ); 162 | } 163 | 164 | /** 165 | * Set `data-rve-block` attribute to block elements. 166 | * 167 | * @param element Element to start searching 168 | */ 169 | private elementsToBlocks( 170 | element: HTMLElement, 171 | context: MarkdownPostProcessorContext 172 | ) { 173 | const section = context.getSectionInfo(element); 174 | const elements = element.querySelectorAll(BLOCKS.join(", ")); 175 | elements.forEach((el) => { 176 | if (el.hasClass(FRONTMATTER)) return; 177 | el.setAttribute(BLOCK_ATTR, "true"); 178 | el.setAttribute("tabindex", "-1"); 179 | if (section?.lineStart && section?.lineEnd) { 180 | el.setAttribute("data-rve-line-start", section.lineStart.toString()); 181 | el.setAttribute("data-rve-line-end", section.lineEnd.toString()); 182 | } 183 | }); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/block-selector/selection-handler.ts: -------------------------------------------------------------------------------- 1 | import ReadingViewEnhancer from "src/main"; 2 | import { BLOCK_ATTR, COLLAPSE_INDICATORS, SELECTED_BLOCK } from "../constants"; 3 | import { 4 | findNextBlock, 5 | findPreviousBlock, 6 | isBottomInView, 7 | isTopInView, 8 | scrollBottomIntoView, 9 | scrollTopIntoView, 10 | } from "./selection-util"; 11 | import { getActiveView, isReadingView } from "src/utils"; 12 | import type { MarkdownViewModeType } from "obsidian"; 13 | 14 | /** 15 | * Handle block selection. 16 | * This class is used by BlockSelector. 17 | */ 18 | export default class SelectionHandler { 19 | plugin: ReadingViewEnhancer; 20 | selectedBlock: HTMLElement | null; 21 | nextBlockKeys: string[]; 22 | prevBlockKeys: string[]; 23 | toggleCollapseKeys: string[]; 24 | deselectKeys: string[]; 25 | startX: number; 26 | startY: number; 27 | 28 | constructor(plugin: ReadingViewEnhancer) { 29 | this.plugin = plugin; 30 | this.selectedBlock = null; 31 | this.nextBlockKeys = plugin.settings.nextBlockKeys.split(" "); 32 | this.prevBlockKeys = plugin.settings.prevBlockKeys.split(" "); 33 | this.toggleCollapseKeys = plugin.settings.toggleCollapseKeys.split(" "); 34 | this.deselectKeys = plugin.settings.deselectKeys.split(" "); 35 | } 36 | 37 | isSelected(block: HTMLElement) { 38 | return block === this.selectedBlock; 39 | } 40 | 41 | /** 42 | * Select block element 43 | * 44 | * @param block {HTMLElement} Block element 45 | */ 46 | select(block: HTMLElement) { 47 | block.focus(); 48 | block.addClass(SELECTED_BLOCK); 49 | this.selectedBlock = block; 50 | } 51 | 52 | /** 53 | * Deselect block element. 54 | * If there is no selected block, do nothing. 55 | */ 56 | deselect() { 57 | if (this.selectedBlock) { 58 | this.selectedBlock.removeClass(SELECTED_BLOCK); 59 | this.selectedBlock.blur(); 60 | } 61 | } 62 | 63 | /** 64 | * Record the starting point of mouse or touch event. 65 | * 66 | * @param e Mouse or touch event 67 | */ 68 | handleMouseTouchStart(e: MouseEvent | TouchEvent) { 69 | const { x, y } = this.clientXY(e); 70 | this.startX = x; 71 | this.startY = y; 72 | } 73 | 74 | /** 75 | * Calculate the distance between the starting point and the end point. 76 | * If the distance is less than 10, it is considered as a tap and 77 | * fires the `handleTap` method. 78 | * 79 | * @param e Mouse or touch event 80 | */ 81 | handleMouseTouchEnd(e: MouseEvent | TouchEvent) { 82 | const { x: endX, y: endY } = this.clientXY(e); 83 | 84 | const distance = Math.sqrt( 85 | Math.pow(endX - this.startX, 2) + Math.pow(endY - this.startY, 2) 86 | ); 87 | 88 | if (distance < 10) { 89 | this.handleTap(e); 90 | } 91 | } 92 | 93 | /** 94 | * Select or toggle collapse on tap. 95 | * 96 | * @param e Mouse or touch event 97 | */ 98 | private handleTap(e: MouseEvent | TouchEvent) { 99 | const target = e.target as HTMLElement; 100 | const block = target.closest(`[${BLOCK_ATTR}=true]`); 101 | if (block instanceof HTMLElement) { 102 | // if already selected, toggle collapse 103 | if (this.isSelected(block)) { 104 | this.toggleCollapse(block); 105 | } 106 | // if not selected, select 107 | else { 108 | this.select(block); 109 | } 110 | } 111 | } 112 | 113 | private clientXY(e: MouseEvent | TouchEvent) { 114 | if (e instanceof MouseEvent) { 115 | return { x: e.clientX, y: e.clientY }; 116 | } else { 117 | return { x: e.touches[0].clientX, y: e.touches[0].clientY }; 118 | } 119 | } 120 | 121 | /** 122 | * On keydown, navigate between blocks or toggle collapse. 123 | * 124 | * If selected block is too long, 125 | * `ArrowDown` and `ArrowUp` scrolls to see the element's bottom or top. 126 | * This is for loading adjacent blocks which are not in the DOM tree. 127 | * 128 | * @param e {KeyboardEvent} Keyboard event 129 | */ 130 | onKeyDown(e: KeyboardEvent) { 131 | if (!isReadingView(getActiveView(this.plugin))) return; 132 | 133 | const block = e.target as HTMLElement; 134 | 135 | if (this.pressed(this.nextBlockKeys, e)) { 136 | e.preventDefault(); 137 | this.selectNextBlockOrScroll(block); 138 | } else if (this.pressed(this.prevBlockKeys, e)) { 139 | e.preventDefault(); 140 | this.selectPreviousBlockOrScroll(block); 141 | } else if (this.pressed(this.toggleCollapseKeys, e)) { 142 | e.preventDefault(); 143 | this.toggleCollapse(block); 144 | } else if (this.pressed(this.deselectKeys, e)) { 145 | this.deselect(); 146 | } 147 | } 148 | 149 | private pressed(keysOrCodes: string[], e: KeyboardEvent) { 150 | return keysOrCodes.includes(e.code) || keysOrCodes.includes(e.key); 151 | } 152 | 153 | /** 154 | * Select next block or scroll to see the block's bottom. 155 | * 156 | * @param block {HTMLElement} Block element 157 | */ 158 | private selectNextBlockOrScroll(block: HTMLElement) { 159 | if (!isBottomInView(block)) { 160 | scrollBottomIntoView(block); 161 | } else { 162 | const next = findNextBlock(block); 163 | if (next) this.select(next as HTMLElement); 164 | } 165 | } 166 | 167 | /** 168 | * Select previous block or scroll to see the block's top. 169 | * 170 | * @param block {HTMLElement} Block element 171 | */ 172 | private selectPreviousBlockOrScroll(block: HTMLElement) { 173 | if (!isTopInView(block)) { 174 | scrollTopIntoView(block); 175 | } else { 176 | const prev = findPreviousBlock(block); 177 | if (prev) this.select(prev as HTMLElement); 178 | } 179 | } 180 | 181 | /** 182 | * Select top block in the view 183 | * 184 | * @param viewContainer {HTMLElement} View container element 185 | */ 186 | selectTopBlockInTheView(viewContainer: HTMLElement) { 187 | const blocks = viewContainer.querySelectorAll(`[${BLOCK_ATTR}=true]`); 188 | 189 | // If there is no block, do nothing 190 | if (blocks.length === 0) return; 191 | 192 | // Get the index of the topmost block in the view 193 | let topIndex = -1; 194 | for (let i = 0; i < blocks.length; i++) { 195 | topIndex = i; 196 | 197 | const rect = blocks[i].getBoundingClientRect(); 198 | if (rect.bottom > 120) { 199 | break; 200 | } 201 | } 202 | 203 | const topBlock = blocks[topIndex]; 204 | this.select(topBlock as HTMLElement); 205 | } 206 | 207 | /** 208 | * Toggle collapse. 209 | * 210 | * @param block Block element 211 | */ 212 | private toggleCollapse(block: HTMLElement) { 213 | const collapseIndicator = block.querySelector( 214 | COLLAPSE_INDICATORS.join(",") 215 | ) as HTMLElement; 216 | if (collapseIndicator) { 217 | collapseIndicator.click(); 218 | } 219 | } 220 | 221 | /** 222 | * Toggle block highlight. 223 | */ 224 | async selectedBlockHighlight() { 225 | if (this.selectedBlock == null) return; 226 | await this.toggleBlockHighlight(this.selectedBlock); 227 | } 228 | 229 | /** 230 | * Toggles highlight of the given block element. 231 | * 232 | * @param block Block element to toggle highlight 233 | */ 234 | private async toggleBlockHighlight(block: HTMLElement) { 235 | const view = getActiveView(this.plugin); 236 | if (view == null || view.file == null) return; 237 | const editor = view.editor; 238 | 239 | const lineStartStr = block.getAttribute("data-rve-line-start"); 240 | const lineEndStr = block.getAttribute("data-rve-line-end"); 241 | if (!lineStartStr || !lineEndStr) return; 242 | 243 | const lineStart = parseInt(lineStartStr, 10); 244 | const lineEnd = parseInt(lineEndStr, 10); 245 | const startPos = { line: lineStart, ch: 0 }; 246 | const endPos = { line: lineEnd, ch: view.editor.getLine(lineEnd).length }; 247 | const selectedText = editor.getRange(startPos, endPos); 248 | 249 | // editor instance might not be initialized 250 | this.changeMode("source"); 251 | view.containerEl.style.visibility = "hidden"; 252 | 253 | if (selectedText.startsWith("==") && selectedText.endsWith("==")) { 254 | editor.replaceRange(selectedText.slice(2, -2), startPos, endPos); 255 | } else { 256 | editor.replaceRange("==" + selectedText + "==", startPos, endPos); 257 | } 258 | 259 | // restore the reading view 260 | this.changeMode("preview"); 261 | view.containerEl.style.visibility = "visible"; 262 | } 263 | 264 | private changeMode(mode: MarkdownViewModeType) { 265 | const view = getActiveView(this.plugin); 266 | if (view == null) return; 267 | 268 | const state = view.getState(); 269 | 270 | view.setState({ ...state, mode }, { history: true }); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/block-selector/selection-util.ts: -------------------------------------------------------------------------------- 1 | import { BLOCK_ATTR, IS_COLLAPSED, MARKDOWN_PREVIEW_VIEW } from "../constants"; 2 | 3 | export const isBottomInView = (block: HTMLElement) => { 4 | const rect = block.getBoundingClientRect(); 5 | return rect.bottom <= window.innerHeight; 6 | }; 7 | 8 | export const scrollBottomIntoView = (block: HTMLElement) => { 9 | const rect = block.getBoundingClientRect(); 10 | const scrollable = getScrollParent(block); 11 | scrollable?.scrollBy({ 12 | behavior: "auto", 13 | top: rect.bottom - scrollable.clientHeight + 200, 14 | }); 15 | }; 16 | 17 | export const isTopInView = (block: HTMLElement) => { 18 | const rect = block.getBoundingClientRect(); 19 | return rect.top >= 0; 20 | }; 21 | 22 | export const scrollTopIntoView = (block: HTMLElement) => { 23 | const rect = block.getBoundingClientRect(); 24 | const scrollable = getScrollParent(block); 25 | scrollable?.scrollBy({ 26 | behavior: "auto", 27 | top: rect.top - 200, 28 | }); 29 | }; 30 | 31 | /** 32 | * Find next block element to select. 33 | * Return null if there is no next block element. 34 | * 35 | * @param currentElement {HTMLElement} Current element to start searching 36 | * @returns {HTMLElement | null} Next block element 37 | */ 38 | export const findNextBlock = ( 39 | currentElement: HTMLElement 40 | ): HTMLElement | null => { 41 | let nextBlock = null; 42 | 43 | // Start by checking if there's a block element inside the current element 44 | if (!isCollapsed(currentElement)) { 45 | const children = currentElement.children; 46 | for (let i = 0; i < children.length; i++) { 47 | nextBlock = findBlock(children[i] as HTMLElement); 48 | if (nextBlock) return nextBlock; 49 | } 50 | } 51 | 52 | // Check next siblings of current element 53 | let nextSibling = currentElement.nextElementSibling; 54 | while (nextSibling) { 55 | nextBlock = findBlock(nextSibling as HTMLElement); 56 | if (nextBlock) return nextBlock; 57 | 58 | nextSibling = nextSibling.nextElementSibling; 59 | } 60 | 61 | // Check next siblings of parent block element 62 | let parent = currentElement.parentElement; 63 | while (parent && !parent.hasClass(MARKDOWN_PREVIEW_VIEW)) { 64 | let parentSibling = parent.nextElementSibling; 65 | while (parentSibling) { 66 | nextBlock = findBlock(parentSibling as HTMLElement); 67 | if (nextBlock) return nextBlock; 68 | 69 | parentSibling = parentSibling.nextElementSibling; 70 | } 71 | parent = parent.parentElement; 72 | } 73 | 74 | // If no block element found, return null 75 | return null; 76 | }; 77 | 78 | /** 79 | * Find previous block element to select. 80 | * Return null if there is no previous block element. 81 | * 82 | * @param currentElement {HTMLElement} Current element to start searching 83 | * @returns {HTMLElement | null} Previous block element 84 | */ 85 | export const findPreviousBlock = ( 86 | currentElement: HTMLElement 87 | ): HTMLElement | null => { 88 | // Check previous siblings of current element 89 | let prevSibling = currentElement.previousElementSibling; 90 | while (prevSibling) { 91 | const prevBlock = findLastBlock(prevSibling as HTMLElement); 92 | if (prevBlock) return prevBlock; 93 | 94 | prevSibling = prevSibling.previousElementSibling; 95 | } 96 | 97 | // Check previous siblings of parent block element 98 | let parent = currentElement.parentElement; 99 | while (parent && !parent.classList.contains(MARKDOWN_PREVIEW_VIEW)) { 100 | // Check ancestors of current element first 101 | if (isBlock(parent)) return parent; 102 | 103 | let parentSibling = parent.previousElementSibling; 104 | while (parentSibling) { 105 | const prevBlock = findLastBlock(parentSibling as HTMLElement); 106 | if (prevBlock) return prevBlock; 107 | 108 | parentSibling = parentSibling.previousElementSibling; 109 | } 110 | parent = parent.parentElement; 111 | } 112 | 113 | // If no block element found, return null 114 | return null; 115 | }; 116 | 117 | /** 118 | * Check if the given element is collapsed 119 | * 120 | * @param element {HTMLElement} Element to check 121 | * @returns {boolean} True if the element is collapsed 122 | */ 123 | const isCollapsed = (element: HTMLElement) => { 124 | return element.hasClass(IS_COLLAPSED); 125 | }; 126 | 127 | /** 128 | * Find first block element inside the given element 129 | * 130 | * @param element {HTMLElement} Element to search 131 | * @returns {HTMLElement | null} First block element 132 | */ 133 | const findBlock = (element: HTMLElement): HTMLElement | null => { 134 | if (isBlock(element)) { 135 | return element; 136 | } 137 | 138 | let block = null; 139 | const childElements = element.children; 140 | for (let i = 0; i < childElements.length; i++) { 141 | block = findBlock(childElements[i] as HTMLElement); 142 | if (block) return block; 143 | } 144 | 145 | return null; 146 | }; 147 | 148 | /** 149 | * Find last block element inside the given element 150 | * 151 | * @param element {HTMLElement} Element to search 152 | * @returns {HTMLElement | null} Last block element 153 | */ 154 | const findLastBlock = (element: HTMLElement): HTMLElement | null => { 155 | if (isCollapsed(element) && isBlock(element)) return element; 156 | 157 | let block = null; 158 | const childElements = element.children; 159 | for (let i = childElements.length - 1; i >= 0; i--) { 160 | block = findLastBlock(childElements[i] as HTMLElement); 161 | if (block) return block; 162 | } 163 | 164 | if (isBlock(element)) return element; 165 | else return null; 166 | }; 167 | 168 | /** 169 | * Check if the given element is a block element 170 | * 171 | * @param element {HTMLElement} Element to check 172 | * @returns {boolean} True if the element is a block element 173 | */ 174 | const isBlock = (element: HTMLElement) => { 175 | return element.getAttribute(BLOCK_ATTR) === "true"; 176 | }; 177 | 178 | /** 179 | * Return the scrollable parent element of the given element 180 | * 181 | * @param node {HTMLElement} Element to start searching 182 | * @returns {HTMLElement | null} Scrollable parent element 183 | */ 184 | const getScrollParent = (node: HTMLElement): HTMLElement | null => { 185 | if (node == null) return null; 186 | 187 | if (node.scrollHeight > node.clientHeight) { 188 | return node; 189 | } else { 190 | return getScrollParent(node.parentNode as HTMLElement); 191 | } 192 | }; 193 | -------------------------------------------------------------------------------- /src/commands/commands.ts: -------------------------------------------------------------------------------- 1 | import ReadingViewEnhancer from "src/main"; 2 | import { 3 | getActiveView, 4 | getReadingViewContainer, 5 | isReadingView, 6 | } from "src/utils"; 7 | import { Platform } from "obsidian"; 8 | import type { RveCommand } from "."; 9 | 10 | /** 11 | * Rerender all reading views 12 | * 13 | * @param plugin Plugin instance 14 | * @returns Rerender all reading views command 15 | */ 16 | export const rerenderAllReadingViews: RveCommand = ( 17 | plugin: ReadingViewEnhancer 18 | ) => ({ 19 | id: "rerender-all-reading-views", 20 | name: "Rerender all reading views", 21 | callback: () => { 22 | const { workspace } = plugin.app; 23 | workspace.getLeavesOfType("markdown").forEach((leaf) => { 24 | if (leaf.view.getState().mode === "preview") { 25 | // @ts-ignore 26 | leaf.view.previewMode?.rerender(true); 27 | } 28 | }); 29 | }, 30 | }); 31 | 32 | /** 33 | * Select top block in the view 34 | * 35 | * @param plugin Plugin instance 36 | * @returns Select top block in the view command 37 | */ 38 | export const selectTopBlockInTheView: RveCommand = ( 39 | plugin: ReadingViewEnhancer 40 | ) => ({ 41 | id: "select-top-block-in-the-view", 42 | name: "Select Top Block in the View", 43 | checkCallback: (checking: boolean): boolean => { 44 | const activeView = getActiveView(plugin); 45 | // If checking is set to true, perform a preliminary check. 46 | if (checking) { 47 | if (!isReadingView(activeView)) return false; 48 | else if (isNotEnabled(plugin)) return false; 49 | else if (isMobileAndDisabled(plugin)) return false; 50 | else return true; 51 | } 52 | // If checking is set to false, perform an action. 53 | else { 54 | const container = getReadingViewContainer(activeView); 55 | if (container) { 56 | plugin.blockSelector.selectTopBlockInTheView(container); 57 | } 58 | 59 | return true; 60 | } 61 | }, 62 | }); 63 | 64 | export const toggleBlockHighlight: RveCommand = ( 65 | plugin: ReadingViewEnhancer 66 | ) => ({ 67 | id: "toggle-block-highlight", 68 | name: "Toggle Block Highlight", 69 | checkCallback: (checking: boolean): boolean => { 70 | const activeView = getActiveView(plugin); 71 | // If checking is set to true, perform a preliminary check. 72 | if (checking) { 73 | if (!isReadingView(activeView)) return false; 74 | else if (isNotEnabled(plugin)) return false; 75 | else if (isMobileAndDisabled(plugin)) return false; 76 | else return true; 77 | } 78 | // If checking is set to false, perform an action. 79 | else { 80 | const container = getReadingViewContainer(activeView); 81 | if (container) { 82 | plugin.blockSelector.toggleBlockHighlight(); 83 | } 84 | 85 | return true; 86 | } 87 | }, 88 | }); 89 | 90 | const isNotEnabled = (plugin: ReadingViewEnhancer) => { 91 | return !plugin.settings.enableBlockSelector; 92 | }; 93 | 94 | const isMobileAndDisabled = (plugin: ReadingViewEnhancer) => { 95 | return ( 96 | (Platform.isMobile || Platform.isMobileApp) && 97 | plugin.settings.disableBlockSelectorOnMobile 98 | ); 99 | }; 100 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | import * as commands from "./commands"; 4 | 5 | export interface RveCommand { 6 | (plugin: ReadingViewEnhancer): Command; 7 | } 8 | 9 | /** 10 | * Set commands for plugin. 11 | * Loads all commands from `commands` directory and add them to plugin. 12 | */ 13 | export default class Commands { 14 | plugin: ReadingViewEnhancer; 15 | 16 | constructor(plugin: ReadingViewEnhancer) { 17 | this.plugin = plugin; 18 | } 19 | 20 | register() { 21 | Object.values(commands) 22 | .map((revCommand: RveCommand) => revCommand(this.plugin)) 23 | .forEach((command) => this.plugin.addCommand(command)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Class name for the preview view 3 | */ 4 | export const MARKDOWN_PREVIEW_VIEW = "markdown-preview-view"; 5 | 6 | /** 7 | * Class name for the frontmatter element 8 | */ 9 | export const FRONTMATTER = "frontmatter"; 10 | 11 | /** 12 | * List of selectors for block elements 13 | */ 14 | export const BLOCKS = [ 15 | "p", 16 | "li", 17 | "table", 18 | "h1", 19 | "h2", 20 | "h3", 21 | "h4", 22 | "h5", 23 | "h6", 24 | "pre", 25 | "blockquote", 26 | "img", 27 | "video", 28 | "audio", 29 | "iframe", 30 | "[data-callout]", 31 | ]; 32 | 33 | /** 34 | * Class name for block selector 35 | */ 36 | export const BLOCK_SELECTOR = "rve-block-selector"; 37 | 38 | /** 39 | * Attribute name for block elements 40 | */ 41 | export const BLOCK_ATTR = "data-rve-block"; 42 | 43 | /** 44 | * Class name for selected block 45 | */ 46 | export const SELECTED_BLOCK = "rve-selected-block"; 47 | 48 | /** 49 | * Selector for collapse indicators 50 | */ 51 | export const COLLAPSE_INDICATORS = [".collapse-indicator", ".callout-fold"]; 52 | 53 | /** 54 | * Class name for collapsed block 55 | */ 56 | export const IS_COLLAPSED = "is-collapsed"; 57 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "obsidian"; 2 | import RveStyles, { 3 | BlockColorRule, 4 | CollapseIndicatorOnTheRightSideRule, 5 | } from "./styles"; 6 | import { RveSettingTab, RveSettings, DEFAULT_SETTINGS } from "./settings"; 7 | import Commands from "./commands"; 8 | import BlockSelector from "./block-selector"; 9 | 10 | export default class ReadingViewEnhancer extends Plugin { 11 | settings: RveSettings; 12 | styles: RveStyles; 13 | blockSelector: BlockSelector; 14 | 15 | /** 16 | * On load, 17 | * 18 | * - Load settings & styles 19 | * - Activate block selector 20 | * - It actually do its work if settings.enableBlockSelector is true 21 | * - Register all commands 22 | * - Add settings tab 23 | */ 24 | async onload() { 25 | // Settings & Styles 26 | await this.loadSettings(); 27 | this.styles = new RveStyles(); 28 | this.app.workspace.onLayoutReady(() => this.applySettingsToStyles()); 29 | 30 | // Activate block selector. 31 | this.blockSelector = new BlockSelector(this); 32 | this.blockSelector.activate(); 33 | 34 | // Register commands 35 | new Commands(this).register(); 36 | 37 | // Add settings tab at last 38 | this.addSettingTab(new RveSettingTab(this)); 39 | 40 | // Leave a message in the console 41 | console.log("Loaded 'Reading View Enhancer'"); 42 | } 43 | 44 | /** 45 | * On unload, 46 | * 47 | * - Remove all styles 48 | */ 49 | async onunload() { 50 | this.styles.cleanup(); 51 | 52 | // Leave a message in the console 53 | console.log("Unloaded 'Reading View Enhancer'"); 54 | } 55 | 56 | // =================================================================== 57 | 58 | /** 59 | * Load settings 60 | */ 61 | async loadSettings() { 62 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 63 | } 64 | 65 | /** 66 | * Save settings 67 | */ 68 | async saveSettings() { 69 | await this.saveData(this.settings); 70 | } 71 | 72 | /** 73 | * Apply settings to styles 74 | * 75 | * - Apply block color 76 | * - Apply always on collapse indicator 77 | * - Apply scrollable code 78 | */ 79 | private applySettingsToStyles() { 80 | this.applyBlockColor(); 81 | this.applyAlwaysOnCollapse(); 82 | this.applyScrollableCode(); 83 | this.applyCollapseIndicatorOnTheRightSide(); 84 | this.applyAlignCheckboxToIndentationGuide(); 85 | this.styles.apply(); 86 | } 87 | 88 | /** 89 | * Apply block color 90 | * 91 | * @param isImmediate Whether to apply styles immediately 92 | */ 93 | applyBlockColor(isImmediate = false) { 94 | const blockColor = this.styles.of("block-color") as BlockColorRule; 95 | blockColor.set(this.settings.blockColor); 96 | if (isImmediate) this.styles.apply(); 97 | } 98 | 99 | /** 100 | * Apply always on collapse indicator 101 | * 102 | * @param isImmediate Whether to apply styles immediately 103 | */ 104 | applyAlwaysOnCollapse(isImmediate = false) { 105 | this.styles.of("collapse-indicator-always-on").isActive = 106 | this.settings.alwaysOnCollapseIndicator; 107 | if (isImmediate) this.styles.apply(); 108 | } 109 | 110 | /** 111 | * Apply collapse indicator on the right side 112 | * 113 | * @param isImmediate Whether to apply styles immediately 114 | */ 115 | applyCollapseIndicatorOnTheRightSide(isImmediate = false) { 116 | const rightIndicator = this.styles.of( 117 | "collapse-indicator-on-the-right-side", 118 | ) as CollapseIndicatorOnTheRightSideRule; 119 | 120 | rightIndicator.isCheckboxAligned = 121 | this.settings.alignCheckboxToIndentationGuide; 122 | rightIndicator.isActive = this.settings.collapseIndicatorOnTheRightSide; 123 | 124 | if (isImmediate) this.styles.apply(); 125 | } 126 | 127 | /** 128 | * Apply checkbox align with indentation guide 129 | * 130 | * @param isImmediate Whether to apply styles immediately 131 | */ 132 | applyAlignCheckboxToIndentationGuide(isImmediate = false) { 133 | this.styles.of("align-checkbox-to-indentation-guide").isActive = 134 | this.settings.alignCheckboxToIndentationGuide; 135 | 136 | const rightIndicator = this.styles.of( 137 | "collapse-indicator-on-the-right-side", 138 | ) as CollapseIndicatorOnTheRightSideRule; 139 | rightIndicator.isCheckboxAligned = 140 | this.settings.alignCheckboxToIndentationGuide; 141 | 142 | if (isImmediate) this.styles.apply(); 143 | } 144 | 145 | /** 146 | * Apply scrollable code 147 | * 148 | * @param isImmediate Whether to apply styles immediately 149 | */ 150 | applyScrollableCode(isImmediate = false) { 151 | this.styles.of("scrollable-code").isActive = this.settings.scrollableCode; 152 | if (isImmediate) this.styles.apply(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/settings/block/block-color.ts: -------------------------------------------------------------------------------- 1 | import { toHex, toRgba } from "color2k"; 2 | import { ButtonComponent, ColorComponent, Setting } from "obsidian"; 3 | import ReadingViewEnhancer from "src/main"; 4 | 5 | /** 6 | * Block color setting component 7 | */ 8 | export default class BlockColorSetting extends Setting { 9 | plugin: ReadingViewEnhancer; 10 | workspaceEl: HTMLElement; 11 | 12 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 13 | super(settingsTabEl); 14 | this.plugin = plugin; 15 | 16 | this.setName("Block color") 17 | .setDesc( 18 | "Set background color of the block in reading view. Transparency will be set automatically", 19 | ) 20 | .addColorPicker((color) => this.colorPicker(color)); 21 | } 22 | 23 | /** 24 | * Creates color picker component. 25 | * Also, creates a button to set color to the current accent color. 26 | * 27 | * @param color Color component 28 | */ 29 | colorPicker(color: ColorComponent) { 30 | const { settings } = this.plugin; 31 | color.setValue(toHex(settings.blockColor.color)).onChange((changed) => { 32 | // save on change 33 | settings.blockColor.color = toRgba(changed); 34 | this.plugin.saveSettings(); 35 | this.plugin.applyBlockColor(true); 36 | }); 37 | this.addButton((button) => this.accentColorButton(button, color)); 38 | } 39 | 40 | /** 41 | * Creates a button to use current accent color. 42 | * Used in {@link colorPicker}. 43 | * 44 | * @param button Button component 45 | * @param color Color component 46 | */ 47 | accentColorButton(button: ButtonComponent, color: ColorComponent) { 48 | button.setButtonText("Use current accent color").onClick(() => { 49 | const accentColor = this.getAccentColor(); 50 | color.setValue(toHex(accentColor)); 51 | 52 | this.plugin.settings.blockColor.color = accentColor; 53 | this.plugin.saveSettings(); 54 | this.plugin.applyBlockColor(true); 55 | }); 56 | } 57 | 58 | /** 59 | * Gets current accent color from Obsidian. 60 | * 61 | * @returns Current accent color in hex format 62 | */ 63 | private getAccentColor(): string { 64 | const workspaceEl = this.plugin.app.workspace.containerEl; 65 | const accentColor = toRgba( 66 | getComputedStyle(workspaceEl).getPropertyValue("--color-accent").trim(), 67 | ); 68 | return accentColor; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/settings/block/block-selector-mobile.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Disable block selector on mobile setting component 6 | */ 7 | export default class DisableBlockSelectorOnMobileSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Disable block selector on mobile") 15 | .setDesc("Restart is needed to apply changes.") 16 | .addToggle((toggle) => this.setDisableSelectorOnMobile(toggle)); 17 | } 18 | 19 | /** 20 | * Creates toggle component that enables/disables block selector on mobile. 21 | * 22 | * @param toggle {ToggleComponent} Toggle component 23 | */ 24 | setDisableSelectorOnMobile(toggle: ToggleComponent) { 25 | toggle.setValue(this.plugin.settings.disableBlockSelectorOnMobile); 26 | 27 | // save on change 28 | toggle.onChange((changed) => { 29 | this.plugin.settings.disableBlockSelectorOnMobile = changed; 30 | this.plugin.saveSettings(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/settings/block/block-selector.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Enable block selector setting component 6 | */ 7 | export default class EnableBlockSelectorSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Enable block selector") 15 | .setDesc("Restart is needed to apply changes.") 16 | .addToggle((toggle) => this.setEnableSelector(toggle)); 17 | } 18 | 19 | /** 20 | * Creates toggle component that enables/disables block selector. 21 | * 22 | * @param toggle {ToggleComponent} Toggle component 23 | */ 24 | setEnableSelector(toggle: ToggleComponent) { 25 | toggle.setValue(this.plugin.settings.enableBlockSelector); 26 | 27 | // save on change 28 | toggle.onChange((changed) => { 29 | this.plugin.settings.enableBlockSelector = changed; 30 | this.plugin.saveSettings(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/settings/block/block-transparency.ts: -------------------------------------------------------------------------------- 1 | import { Setting, SliderComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Block color transparency setting component 6 | */ 7 | export default class BlockTransparencySetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | workspaceEl: HTMLElement; 10 | 11 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 12 | super(settingsTabEl); 13 | this.plugin = plugin; 14 | 15 | this.setName("Block color transparency") 16 | .setDesc("Set transparency of the above color.") 17 | .addSlider((color) => this.transparencySlider(color)); 18 | } 19 | 20 | /** 21 | * Creates transparency slider component. 22 | * 23 | * @param slider Slider component 24 | */ 25 | transparencySlider(slider: SliderComponent) { 26 | const { settings } = this.plugin; 27 | slider 28 | .setLimits(0, 100, 1) 29 | .setValue(settings.blockColor.transparency) 30 | .onChange((changed) => { 31 | // save on change 32 | settings.blockColor.transparency = changed; 33 | this.plugin.saveSettings(); 34 | this.plugin.applyBlockColor(true); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/settings/block/index.ts: -------------------------------------------------------------------------------- 1 | import ReadingViewEnhancer from "src/main"; 2 | import BlockColorSetting from "./block-color"; 3 | import BlockTransparencySetting from "./block-transparency"; 4 | import EnableBlockSelectorSetting from "./block-selector"; 5 | import DisableBlockSelectorOnMobileSetting from "./block-selector-mobile"; 6 | 7 | /** 8 | * Registers settings components related to block selector. 9 | */ 10 | export default class BlockSelectorSettings { 11 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 12 | containerEl.createEl("h1", { text: "Block selector" }); 13 | new BlockColorSetting(containerEl, plugin); 14 | new BlockTransparencySetting(containerEl, plugin); 15 | new EnableBlockSelectorSetting(containerEl, plugin); 16 | new DisableBlockSelectorOnMobileSetting(containerEl, plugin); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/settings/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginSettingTab } from "obsidian"; 2 | import ReadingViewEnhancer from "../main"; 3 | import BlockSelectorSettings from "./block"; 4 | import KeysSettings from "./keys"; 5 | import MiscellaneousSettings from "./miscellaneous"; 6 | 7 | export interface RveSettings { 8 | blockColor: { 9 | color: string; 10 | transparency: number; 11 | }; 12 | enableBlockSelector: boolean; 13 | disableBlockSelectorOnMobile: boolean; 14 | alwaysOnCollapseIndicator: boolean; 15 | scrollableCode: boolean; 16 | autoSelectTopBlock: boolean; 17 | collapseIndicatorOnTheRightSide: boolean; 18 | alignCheckboxToIndentationGuide: boolean; 19 | nextBlockKeys: string; 20 | prevBlockKeys: string; 21 | toggleCollapseKeys: string; 22 | deselectKeys: string; 23 | } 24 | 25 | export const DEFAULT_SETTINGS: RveSettings = { 26 | blockColor: { 27 | color: "rgba(139, 108, 239, 1)", // Obsidian default color 28 | transparency: 20, 29 | }, 30 | enableBlockSelector: true, 31 | disableBlockSelectorOnMobile: false, 32 | alwaysOnCollapseIndicator: false, 33 | scrollableCode: false, 34 | autoSelectTopBlock: false, 35 | collapseIndicatorOnTheRightSide: false, 36 | alignCheckboxToIndentationGuide: false, 37 | nextBlockKeys: "ArrowDown KeyJ", 38 | prevBlockKeys: "ArrowUp KeyK", 39 | toggleCollapseKeys: "ArrowLeft ArrowRight KeyH KeyL", 40 | deselectKeys: "Escape", 41 | }; 42 | 43 | // =================================================================== 44 | 45 | /** 46 | * Settings tab. 47 | * In this tab, you can change settings. 48 | * 49 | * - Block color 50 | * - Enable/Disable Block Selector 51 | */ 52 | export class RveSettingTab extends PluginSettingTab { 53 | plugin: ReadingViewEnhancer; 54 | 55 | constructor(plugin: ReadingViewEnhancer) { 56 | super(plugin.app, plugin); 57 | this.plugin = plugin; 58 | } 59 | 60 | /** 61 | * Displays settings tab. 62 | */ 63 | display() { 64 | const { containerEl } = this; 65 | 66 | // Clear all first 67 | containerEl.empty(); 68 | 69 | // Add block selector settings 70 | new BlockSelectorSettings(containerEl, this.plugin); 71 | 72 | // Add miscellaneous settings 73 | new MiscellaneousSettings(containerEl, this.plugin); 74 | 75 | // Add keys settings 76 | new KeysSettings(containerEl, this.plugin); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/settings/keys/deselect-block.ts: -------------------------------------------------------------------------------- 1 | import { Setting, TextAreaComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Deselect block keys setting component 6 | */ 7 | export default class DeselectBlockKeysSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Deselect block") 15 | .setDesc("Keys to deselect current block.") 16 | .addTextArea((textArea) => this.setKeys(textArea)); 17 | } 18 | 19 | setKeys(textArea: TextAreaComponent) { 20 | textArea.setValue(this.plugin.settings.deselectKeys); 21 | 22 | // save on change 23 | textArea.onChange((changed) => { 24 | this.plugin.settings.deselectKeys = changed; 25 | this.plugin.saveSettings(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/keys/index.ts: -------------------------------------------------------------------------------- 1 | import ReadingViewEnhancer from "src/main"; 2 | import NextBlockKeysSetting from "./next-block"; 3 | import PrevBlockKeysSetting from "./prev-block"; 4 | import ToggleCollapseKeysSetting from "./toggle-collapse"; 5 | import DeselectBlockKeysSetting from "./deselect-block"; 6 | 7 | const descriptions = [ 8 | "Set keys to trigger actions.", 9 | "You can set multiple keys(not sequence on keys) to trigger an internal action.", 10 | "To set multiple keys, separate each key with a space.", 11 | "[Note] You should use KeyboardEvent.key property value.", 12 | "[Note] Restart is needed to apply changes.", 13 | ]; 14 | 15 | /** 16 | * Registers settings components related to keys. 17 | */ 18 | export default class KeysSettings { 19 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 20 | containerEl.createEl("h1", { text: "Keys" }); 21 | containerEl.createDiv({ cls: "setting-item-description" }, (desc) => { 22 | descriptions.forEach((text, idx) => { 23 | desc.createSpan({ text }); 24 | if (idx + 1 < descriptions.length) desc.createEl("br"); 25 | }); 26 | }); 27 | containerEl.createEl("br"); 28 | 29 | new NextBlockKeysSetting(containerEl, plugin); 30 | new PrevBlockKeysSetting(containerEl, plugin); 31 | new ToggleCollapseKeysSetting(containerEl, plugin); 32 | new DeselectBlockKeysSetting(containerEl, plugin); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/settings/keys/next-block.ts: -------------------------------------------------------------------------------- 1 | import { Setting, TextAreaComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Next block keys setting component 6 | */ 7 | export default class NextBlockKeysSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Next block") 15 | .setDesc("Keys to select next block.") 16 | .addTextArea((textArea) => this.setKeys(textArea)); 17 | } 18 | 19 | setKeys(textArea: TextAreaComponent) { 20 | textArea.setValue(this.plugin.settings.nextBlockKeys); 21 | 22 | // save on change 23 | textArea.onChange((changed) => { 24 | this.plugin.settings.nextBlockKeys = changed; 25 | this.plugin.saveSettings(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/keys/prev-block.ts: -------------------------------------------------------------------------------- 1 | import { Setting, TextAreaComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Previous block keys setting component 6 | */ 7 | export default class PrevBlockKeysSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Previous block") 15 | .setDesc("Keys to select previous block.") 16 | .addTextArea((textArea) => this.setKeys(textArea)); 17 | } 18 | 19 | setKeys(textArea: TextAreaComponent) { 20 | textArea.setValue(this.plugin.settings.prevBlockKeys); 21 | 22 | // save on change 23 | textArea.onChange((changed) => { 24 | this.plugin.settings.prevBlockKeys = changed; 25 | this.plugin.saveSettings(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/keys/toggle-collapse.ts: -------------------------------------------------------------------------------- 1 | import { Setting, TextAreaComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Toggle collapse keys setting component 6 | */ 7 | export default class ToggleCollapseKeysSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Toggle collapse") 15 | .setDesc("Keys to toggle collapse.") 16 | .addTextArea((textArea) => this.setKeys(textArea)); 17 | } 18 | 19 | setKeys(textArea: TextAreaComponent) { 20 | textArea.setValue(this.plugin.settings.toggleCollapseKeys); 21 | 22 | // save on change 23 | textArea.onChange((changed) => { 24 | this.plugin.settings.toggleCollapseKeys = changed; 25 | this.plugin.saveSettings(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/always-on-collapse-indicator.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Always on collapse indicator setting component 6 | */ 7 | export default class AlwaysOnCollapseIndicatorSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | workspaceEl: HTMLElement; 10 | 11 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 12 | super(settingsTabEl); 13 | this.plugin = plugin; 14 | 15 | this.setName("Always on collapse indicator") 16 | .setDesc("Set collapse indicators always visible in reading view.") 17 | .addToggle((toggle) => this.alwaysOnCollapseIndicator(toggle)); 18 | } 19 | 20 | /** 21 | * Creates toggle component 22 | * 23 | * @param toggle Toggle component 24 | */ 25 | alwaysOnCollapseIndicator(toggle: ToggleComponent) { 26 | const { settings } = this.plugin; 27 | 28 | toggle.setValue(settings.alwaysOnCollapseIndicator).onChange((changed) => { 29 | settings.alwaysOnCollapseIndicator = changed; 30 | this.plugin.saveSettings(); 31 | this.plugin.applyAlwaysOnCollapse(true); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/auto-select-top-block.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Auto select top block setting component 6 | */ 7 | export default class AutoSelectTopBlockSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | workspaceEl: HTMLElement; 10 | 11 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 12 | super(settingsTabEl); 13 | this.plugin = plugin; 14 | 15 | this.setName("Auto-select top block") 16 | .setDesc( 17 | "Auto-select the top block in the view when switching into the reading view.", 18 | ) 19 | .addToggle((toggle) => this.autoSelectTopBlock(toggle)); 20 | } 21 | 22 | /** 23 | * Creates toggle component 24 | * 25 | * @param toggle Toggle component 26 | */ 27 | autoSelectTopBlock(toggle: ToggleComponent) { 28 | const { settings } = this.plugin; 29 | 30 | toggle.setValue(settings.autoSelectTopBlock).onChange((changed) => { 31 | settings.autoSelectTopBlock = changed; 32 | this.plugin.saveSettings(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/checkbox-align-with-indentation-guide.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | const description = [ 5 | "Align checkboxes(task list item) to indentation guide line.", 6 | ]; 7 | 8 | /** 9 | * Align checkbox to indentation guide setting component 10 | */ 11 | export default class AlignCheckboxToIndentationGuide extends Setting { 12 | plugin: ReadingViewEnhancer; 13 | workspaceEl: HTMLElement; 14 | 15 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 16 | super(settingsTabEl); 17 | this.plugin = plugin; 18 | 19 | this.setName("Align checkbox to indentation guide") 20 | .setDesc(description.join(" ")) 21 | .addToggle((toggle) => this.alignCheckboxToIndentationGuide(toggle)); 22 | } 23 | 24 | /** 25 | * Creates toggle component 26 | * 27 | * @param toggle Toggle component 28 | */ 29 | alignCheckboxToIndentationGuide(toggle: ToggleComponent) { 30 | const { settings } = this.plugin; 31 | 32 | toggle 33 | .setValue(settings.alignCheckboxToIndentationGuide) 34 | .onChange((changed) => { 35 | settings.alignCheckboxToIndentationGuide = changed; 36 | this.plugin.saveSettings(); 37 | this.plugin.applyAlignCheckboxToIndentationGuide(true); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/collapse-indicators-on-the-right-side.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | const description = [ 5 | "Set collapse indicators to be shown on the right side.", 6 | "Since this makes some elements relative that were previously not, may lead some problems.", 7 | ]; 8 | 9 | /** 10 | * Collapse indicators on the right side setting component 11 | */ 12 | export default class CollapseIndicatorsOnTheRightSideSetting extends Setting { 13 | plugin: ReadingViewEnhancer; 14 | workspaceEl: HTMLElement; 15 | 16 | constructor(settingsTabEl: HTMLElement, plugin: ReadingViewEnhancer) { 17 | super(settingsTabEl); 18 | this.plugin = plugin; 19 | 20 | this.setName("[Experimental] Collapse indicator on the right side") 21 | .setDesc(description.join(" ")) 22 | .addToggle((toggle) => this.collapseIndicatorOnTheRightSide(toggle)); 23 | } 24 | 25 | /** 26 | * Creates toggle component 27 | * 28 | * @param toggle Toggle component 29 | */ 30 | collapseIndicatorOnTheRightSide(toggle: ToggleComponent) { 31 | const { settings } = this.plugin; 32 | 33 | toggle 34 | .setValue(settings.collapseIndicatorOnTheRightSide) 35 | .onChange((changed) => { 36 | settings.collapseIndicatorOnTheRightSide = changed; 37 | this.plugin.saveSettings(); 38 | this.plugin.applyCollapseIndicatorOnTheRightSide(true); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/index.ts: -------------------------------------------------------------------------------- 1 | import ReadingViewEnhancer from "src/main"; 2 | import AlwaysOnCollapseIndicatorSetting from "./always-on-collapse-indicator"; 3 | import AutoSelectTopBlockSetting from "./auto-select-top-block"; 4 | import CollapseIndicatorsOnTheRightSideSetting from "./collapse-indicators-on-the-right-side"; 5 | import CheckboxAlignWithIndentationGuideSetting from "./checkbox-align-with-indentation-guide"; 6 | import ScrollableCodeSetting from "./scrollable-code"; 7 | 8 | /** 9 | * Registers settings components not related to block. 10 | */ 11 | export default class MiscellaneousSettings { 12 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 13 | containerEl.createEl("h1", { text: "Miscellaneous" }); 14 | new AlwaysOnCollapseIndicatorSetting(containerEl, plugin); 15 | new ScrollableCodeSetting(containerEl, plugin); 16 | new AutoSelectTopBlockSetting(containerEl, plugin); 17 | new CollapseIndicatorsOnTheRightSideSetting(containerEl, plugin); 18 | new CheckboxAlignWithIndentationGuideSetting(containerEl, plugin); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/settings/miscellaneous/scrollable-code.ts: -------------------------------------------------------------------------------- 1 | import { Setting, ToggleComponent } from "obsidian"; 2 | import ReadingViewEnhancer from "src/main"; 3 | 4 | /** 5 | * Scrollable code setting component 6 | */ 7 | export default class ScrollableCodeSetting extends Setting { 8 | plugin: ReadingViewEnhancer; 9 | 10 | constructor(containerEl: HTMLElement, plugin: ReadingViewEnhancer) { 11 | super(containerEl); 12 | this.plugin = plugin; 13 | 14 | this.setName("Scrollable code") 15 | .setDesc("Make code blocks scrollable instead of line break.") 16 | .addToggle((toggle) => this.setCodeScrollable(toggle)); 17 | } 18 | 19 | setCodeScrollable(toggle: ToggleComponent) { 20 | toggle.setValue(this.plugin.settings.scrollableCode); 21 | 22 | // save on change 23 | toggle.onChange((changed) => { 24 | this.plugin.settings.scrollableCode = changed; 25 | this.plugin.saveSettings(); 26 | this.plugin.applyScrollableCode(true); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | import { SELECTED_BLOCK } from "./constants"; 2 | 3 | /** 4 | * Style rule that holds the template 5 | * and the function to inject variables 6 | */ 7 | class StyleRule { 8 | private template: string; 9 | private injectVariables: (template: string) => string; 10 | isActive: boolean; 11 | 12 | constructor(template: string, injectVariables: (template: string) => string) { 13 | this.template = template; 14 | this.isActive = false; 15 | this.injectVariables = injectVariables; 16 | } 17 | 18 | /** 19 | * Get the rule after injecting variables 20 | * 21 | * @returns The rule 22 | */ 23 | getRule() { 24 | return this.injectVariables(this.template); 25 | } 26 | } 27 | 28 | /** 29 | * Block color rule. 30 | * 31 | * Accepts a block color and injects it into the template. 32 | */ 33 | export class BlockColorRule extends StyleRule { 34 | private color: string; 35 | private transparency: number; 36 | 37 | constructor() { 38 | const template = ` 39 | .${SELECTED_BLOCK} { 40 | position: relative; 41 | z-index: 0; 42 | } 43 | 44 | .${SELECTED_BLOCK}::before { 45 | content: ""; 46 | position: absolute; 47 | z-index: -1; 48 | top: 0; 49 | left: 0; 50 | width: 100%; 51 | height: 100%; 52 | pointer-events: none; 53 | background-color: {{BLOCK_COLOR}}; 54 | } 55 | `; 56 | super(template, (template: string) => { 57 | const percentage = this.transparency / 100; 58 | const transparencyApplied = this.color.replace( 59 | /\d+\s*\)$/, 60 | percentage + ")", 61 | ); 62 | return template.replace("{{BLOCK_COLOR}}", transparencyApplied); 63 | }); 64 | 65 | this.isActive = true; 66 | } 67 | 68 | /** 69 | * Set the block color 70 | * 71 | * @param blockColor {string} The block color 72 | */ 73 | set(blockColor: { color: string; transparency: number }) { 74 | this.color = blockColor.color; 75 | this.transparency = blockColor.transparency; 76 | } 77 | } 78 | 79 | /** 80 | * Collapse indicator rule. 81 | * 82 | * No variables to inject. 83 | */ 84 | export class CollapseIndicatorAlwaysOnRule extends StyleRule { 85 | constructor() { 86 | const template = ` 87 | .markdown-reading-view .markdown-preview-section .collapse-indicator { 88 | opacity: 1; 89 | } 90 | `; 91 | super(template, (template: string) => template); 92 | } 93 | } 94 | 95 | /** 96 | * Collapse indicator on the right side rule. 97 | * 98 | * No variables to inject. 99 | */ 100 | export class CollapseIndicatorOnTheRightSideRule extends StyleRule { 101 | isCheckboxAligned: boolean; 102 | 103 | constructor() { 104 | const template = ` 105 | .markdown-reading-view .markdown-preview-section>div:not([class="markdown-preview-pusher"]), 106 | .markdown-reading-view .markdown-preview-section>div:not([class="mod-header"]) { 107 | position: relative; 108 | } 109 | 110 | .markdown-reading-view .markdown-preview-section .collapse-indicator { 111 | right: -1rem; 112 | padding-inline-end: 0; 113 | } 114 | 115 | .markdown-reading-view .markdown-preview-section > div > .has-list-bullet > li { 116 | margin-inline-start: calc(var(--list-indent) * {{LIST_INDENT}}); 117 | } 118 | `; 119 | super(template, (template: string) => 120 | template.replace( 121 | "{{LIST_INDENT}}", 122 | this.isCheckboxAligned ? "0.7" : "0.8", 123 | ), 124 | ); 125 | } 126 | } 127 | 128 | export class AlignCheckboxToIndentationGuide extends StyleRule { 129 | constructor() { 130 | const template = ` 131 | .markdown-reading-view ul > li.task-list-item .task-list-item-checkbox { 132 | margin-inline-start: calc(var(--checkbox-size) * -1.35); 133 | } 134 | `; 135 | super(template, (template: string) => template); 136 | } 137 | } 138 | 139 | /** 140 | * Scrollable code rule. 141 | * 142 | * No variables to inject. 143 | */ 144 | export class ScrollableCodeRule extends StyleRule { 145 | constructor() { 146 | const template = ` 147 | .markdown-reading-view .markdown-preview-section div > pre { 148 | overflow: hidden; 149 | white-space: pre-wrap; 150 | } 151 | 152 | .markdown-reading-view .markdown-preview-section div > pre > code { 153 | display: block; 154 | overflow: auto; 155 | white-space: pre; 156 | } 157 | `; 158 | super(template, (template: string) => template); 159 | } 160 | } 161 | 162 | type RuleKey = 163 | | "block-color" 164 | | "collapse-indicator-always-on" 165 | | "collapse-indicator-on-the-right-side" 166 | | "align-checkbox-to-indentation-guide" 167 | | "scrollable-code"; 168 | 169 | /** 170 | * The class that manages all style rules. 171 | */ 172 | export default class RveStyles { 173 | styleTag: HTMLStyleElement; 174 | rules: Record; 175 | 176 | constructor() { 177 | this.styleTag = document.createElement("style"); 178 | this.styleTag.id = "rve-styles"; 179 | document.getElementsByTagName("head")[0].appendChild(this.styleTag); 180 | 181 | this.rules = { 182 | "block-color": new BlockColorRule(), 183 | "collapse-indicator-always-on": new CollapseIndicatorAlwaysOnRule(), 184 | "collapse-indicator-on-the-right-side": 185 | new CollapseIndicatorOnTheRightSideRule(), 186 | "align-checkbox-to-indentation-guide": 187 | new AlignCheckboxToIndentationGuide(), 188 | "scrollable-code": new ScrollableCodeRule(), 189 | }; 190 | } 191 | 192 | /** 193 | * Clean up the style tag 194 | */ 195 | cleanup() { 196 | this.styleTag.remove(); 197 | } 198 | 199 | /** 200 | * Get a rule by key 201 | * 202 | * @param rule rule's key 203 | * @returns One of the rules 204 | */ 205 | of(rule: RuleKey) { 206 | return this.rules[rule]; 207 | } 208 | 209 | /** 210 | * Apply all active rules 211 | */ 212 | apply() { 213 | const style = Object.values(this.rules) 214 | .filter((rule) => rule.isActive) 215 | .map((rule) => rule.getRule()) 216 | .join("\n"); 217 | 218 | this.styleTag.innerHTML = style; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownView } from "obsidian"; 2 | import ReadingViewEnhancer from "./main"; 3 | 4 | export function getReadingViewContainer(activeView: MarkdownView | null) { 5 | if (activeView == null) return null; 6 | 7 | return activeView.previewMode.containerEl; 8 | } 9 | 10 | export function isReadingView(activeView: MarkdownView | null) { 11 | if (activeView == null) return false; 12 | else return activeView.currentMode === activeView.previewMode; 13 | } 14 | 15 | export function getActiveView(plugin: ReadingViewEnhancer) { 16 | const { workspace } = plugin.app; 17 | return workspace.getActiveViewOfType(MarkdownView); 18 | } 19 | -------------------------------------------------------------------------------- /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": ["DOM", "ES5", "ES6", "ES7"] 15 | }, 16 | "include": ["**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.1": "1.0.0", 3 | "0.1.1": "1.0.0", 4 | "0.1.2": "1.0.0", 5 | "0.1.3": "1.0.0", 6 | "0.1.4": "1.0.0", 7 | "0.1.5": "1.0.0", 8 | "0.1.6": "1.0.0", 9 | "0.1.7": "1.0.0", 10 | "0.1.8": "1.0.0", 11 | "0.2.0": "1.0.0", 12 | "0.2.1": "1.8.0" 13 | } --------------------------------------------------------------------------------