├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── docs ├── Glossary.md └── Roadmap.md ├── esbuild.config.mjs ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── frontmatterQueue.ts ├── logger.ts ├── main.ts ├── settings.ts ├── suggester.ts └── types.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 = 4 10 | tab_width = 4 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 | } -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "20.x" 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - name: Create release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | run: | 29 | tag="${GITHUB_REF#refs/tags/}" 30 | 31 | gh release create "$tag" \ 32 | --title="$tag" \ 33 | --draft \ 34 | main.js manifest.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # build 12 | main.js 13 | *.map 14 | 15 | # obsidian 16 | data.json 17 | .hotreload 18 | 19 | # Exclude macOS Finder (System Explorer) View States 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zach Mueller 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 | # Spaced Everything (Obsidian plugin) 2 | 3 | _[Obsidian](https://obsidian.md/) plugin to apply spaced repetition algorithms to everything in your vault._ 4 | 5 | Borrowing from Andy Matuschak's [notes](https://notes.andymatuschak.org/About_these_notes?stackedNotes=zVFGpprS64TzmKGNzGxq9FiCDnAnCPwRU5T&stackedNotes=z5aJUJcSbxuQxzHr2YvaY4cX5TuvLQT7r27Dz&stackedNotes=z8aZybuJJopS5fL7TnPou2JcmCsBUJeqirbBh&stackedNotes=zJ5Yzvba2729XKXivBBZ91J&stackedNotes=zB92WZZ5baBHKZPPbWMbYEv&stackedNotes=zHwr5v9VJGX3MzHyzz4V8wt&stackedNotes=zDXBGEWk7msyonQ2Ngnrf8h&stackedNotes=zSK4LyrCbG9zDrdCWmcovUW&stackedNotes=z4KxfCZPkVEf2R8nayLJZBG) outlining such a practice, this plugin applies the main concepts of spaced repetition to writing. That is, using the SuperMemo-2.0 algorithm to decide what note to review next when engaging in a Spaced Writing Practice (SWP). 6 | 7 | (still under development): The plugin is extensible. It allows each note to be tagged to different contexts (e.g., work vs personal) to only pull up notes approriate for the moment. Each context may optionally apply different spacing algorithms. This includes fully customized JavaScript implementations to derive your own spacing algorithms, in addition to built-in implementations. 8 | 9 | ## Features 10 | 11 | ### Review a note 12 | 13 | Taking from Andy's proposed idea, when reviewing a note you can select among three options for how the review went: Fruitful, Unfruitful, or Ignore. Fruitful means you made good progress on the note; this guides the spacing algorithm to include the note again soon in your queue. Unfruitful means you tried to engage with the note but progress was limited; this pushes that note out further in your queue. Ignore is an in-between option. 14 | 15 | You may customize these options for this review process. You may add or delete options and change their numeric value. Currently, the only spacing algorithm implemented is the SuperMemo-2.0 algorithm. The numeric value provided in the settings alongside each review option maps to the "review quality score" from that algorithm. Over time, I may experiment with adding alternate spacing algorithms and further customizations. 16 | 17 | ### Pull up next note in review queue 18 | 19 | At any time, you may run the "Open next review item" command to pull up the next note in your review queue. This is calculated based on looking across the SWP frontmatter properties of notes in your vault. From there, it adds the interval value (in days) to the last review timestamp to derive the due date for each note. Then any notes with a due date in the future are filtered out. Among remaining notes, it sorts it by the earliest/oldest due date timestamp first and pulls notes in that order. 20 | 21 | ### Capture thoughts 22 | 23 | Inspired by Andy Matuschak's [writing inbox concept](https://notes.andymatuschak.org/zUP4GuzPF33dWkZPiu9N6V5), the "Capture thought" command allows for a rapid way to write down any sudden thought and have it immediately onboarded to the spaced writing queue. 24 | 25 | ### Toggle note contexts 26 | 27 | By default, all notes are treated as one large queue of notes to review. However, you may optionally add any number of separate contexts to filter down which subset of notes to include when pulling up the next note in the review queue. When onboarding notes, it will prompt you to select which SWP context to add the note into. You may also run the "Toggle note contexts" command on any note to add/remove the note from any context. 28 | 29 | You can create and remove SWP contexts from within the settings. You may also toggle which contexts are "active" (i.e., only notes matching an active context are considered when going through the review queue). 30 | 31 | ### Onboard individual notes 32 | 33 | Only notes you actively choose to review to onboard are considered for the spaced writing practice review queue. When running the "Log review outcome" command on a note not yet onboarded, it will onboard the note. When conducting the review (using that same command), you may remove any individual note at any time. 34 | 35 | ### Logging 36 | 37 | The actions you take in this spaced writing practice can optionally be logged to a local file. This data is stored in JSONL format. Some customization is possible in the settings, including providing a list of frontmatter properties that you are interested in capturing for whichever note the action was taken on. -------------------------------------------------------------------------------- /docs/Glossary.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | 3 | TODO::migrate over content from [my notes](https://notes.zach.nz/Spaced-Everything---Glossary) as I clean it up and can arrive at a published form:: -------------------------------------------------------------------------------- /docs/Roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | I develop my ideas for how to evolve this plugin first within [my notes](https://notes.zach.nz/Spaced-Everything-plugin) then later migrate them into [GitHub Issues](https://github.com/zachmueller/spaced-everything/issues). Below I call out a few specific enhancements I have in mind, with links to their respective Issues. Beyond the list below, many bugs and small changes are also articulated in Issues. 3 | 4 | Users of the plugin are encouraged to provide input in a variety of ways. Submitting new Issues to highlight bugs or gaps in documentation helps me catch problems I missed myself. Issues are also the best mechanism for requesting new features or enhancements for the plugin. Adding a 👍 reaction to any Issues you would find most valuable can also help signal which features are most of interest by users. 5 | 6 | As of today, I do not have a specific structure for deciding which aspects of the plugin to work on next. There also is no specific timeline for when I'll tackle any given Issue. This is a plugin I use in my daily workflow in both my professional and personal life, so new ideas spring up frequently and I implement changes as inspiration strikes. 7 | ## Highlights 8 | - [Cloze deletions](https://github.com/zachmueller/spaced-everything/issues/18) 9 | - [Custom spacing algorithms](https://github.com/zachmueller/spaced-everything/issues/24) 10 | - [Spaced writing session wrapper](https://github.com/zachmueller/spaced-everything/issues/26) 11 | - [Sample vault](https://github.com/zachmueller/spaced-everything/issues/22) -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === "production"); 13 | 14 | const context = await esbuild.context({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["src/main.ts"], 19 | bundle: true, 20 | external: [ 21 | "obsidian", 22 | "electron", 23 | "@codemirror/autocomplete", 24 | "@codemirror/collab", 25 | "@codemirror/commands", 26 | "@codemirror/language", 27 | "@codemirror/lint", 28 | "@codemirror/search", 29 | "@codemirror/state", 30 | "@codemirror/view", 31 | "@lezer/common", 32 | "@lezer/highlight", 33 | "@lezer/lr", 34 | ...builtins], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | minify: prod, 42 | }); 43 | 44 | if (prod) { 45 | await context.rebuild(); 46 | process.exit(0); 47 | } else { 48 | await context.watch(); 49 | } 50 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "spaced-everything", 3 | "name": "Spaced everything", 4 | "version": "1.3.2", 5 | "minAppVersion": "1.6.7", 6 | "description": "Apply spaced repetition algorithms to everything in your vault.", 7 | "author": "Zach Mueller", 8 | "authorUrl": "https://notes.zach.nz", 9 | "fundingUrl": "https://buymeacoffee.com/zachmueller", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "obsidian-sample-plugin", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "^16.11.6", 13 | "@typescript-eslint/eslint-plugin": "5.29.0", 14 | "@typescript-eslint/parser": "5.29.0", 15 | "builtin-modules": "3.3.0", 16 | "esbuild": "0.17.3", 17 | "obsidian": "latest", 18 | "tslib": "2.4.0", 19 | "typescript": "4.7.4" 20 | } 21 | }, 22 | "node_modules/@codemirror/state": { 23 | "version": "6.4.1", 24 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", 25 | "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", 26 | "dev": true, 27 | "peer": true 28 | }, 29 | "node_modules/@codemirror/view": { 30 | "version": "6.33.0", 31 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", 32 | "integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", 33 | "dev": true, 34 | "peer": true, 35 | "dependencies": { 36 | "@codemirror/state": "^6.4.0", 37 | "style-mod": "^4.1.0", 38 | "w3c-keyname": "^2.2.4" 39 | } 40 | }, 41 | "node_modules/@esbuild/android-arm": { 42 | "version": "0.17.3", 43 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", 44 | "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", 45 | "cpu": [ 46 | "arm" 47 | ], 48 | "dev": true, 49 | "optional": true, 50 | "os": [ 51 | "android" 52 | ], 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@esbuild/android-arm64": { 58 | "version": "0.17.3", 59 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", 60 | "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", 61 | "cpu": [ 62 | "arm64" 63 | ], 64 | "dev": true, 65 | "optional": true, 66 | "os": [ 67 | "android" 68 | ], 69 | "engines": { 70 | "node": ">=12" 71 | } 72 | }, 73 | "node_modules/@esbuild/android-x64": { 74 | "version": "0.17.3", 75 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", 76 | "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", 77 | "cpu": [ 78 | "x64" 79 | ], 80 | "dev": true, 81 | "optional": true, 82 | "os": [ 83 | "android" 84 | ], 85 | "engines": { 86 | "node": ">=12" 87 | } 88 | }, 89 | "node_modules/@esbuild/darwin-arm64": { 90 | "version": "0.17.3", 91 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", 92 | "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", 93 | "cpu": [ 94 | "arm64" 95 | ], 96 | "dev": true, 97 | "optional": true, 98 | "os": [ 99 | "darwin" 100 | ], 101 | "engines": { 102 | "node": ">=12" 103 | } 104 | }, 105 | "node_modules/@esbuild/darwin-x64": { 106 | "version": "0.17.3", 107 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", 108 | "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", 109 | "cpu": [ 110 | "x64" 111 | ], 112 | "dev": true, 113 | "optional": true, 114 | "os": [ 115 | "darwin" 116 | ], 117 | "engines": { 118 | "node": ">=12" 119 | } 120 | }, 121 | "node_modules/@esbuild/freebsd-arm64": { 122 | "version": "0.17.3", 123 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", 124 | "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", 125 | "cpu": [ 126 | "arm64" 127 | ], 128 | "dev": true, 129 | "optional": true, 130 | "os": [ 131 | "freebsd" 132 | ], 133 | "engines": { 134 | "node": ">=12" 135 | } 136 | }, 137 | "node_modules/@esbuild/freebsd-x64": { 138 | "version": "0.17.3", 139 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", 140 | "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", 141 | "cpu": [ 142 | "x64" 143 | ], 144 | "dev": true, 145 | "optional": true, 146 | "os": [ 147 | "freebsd" 148 | ], 149 | "engines": { 150 | "node": ">=12" 151 | } 152 | }, 153 | "node_modules/@esbuild/linux-arm": { 154 | "version": "0.17.3", 155 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", 156 | "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", 157 | "cpu": [ 158 | "arm" 159 | ], 160 | "dev": true, 161 | "optional": true, 162 | "os": [ 163 | "linux" 164 | ], 165 | "engines": { 166 | "node": ">=12" 167 | } 168 | }, 169 | "node_modules/@esbuild/linux-arm64": { 170 | "version": "0.17.3", 171 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", 172 | "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", 173 | "cpu": [ 174 | "arm64" 175 | ], 176 | "dev": true, 177 | "optional": true, 178 | "os": [ 179 | "linux" 180 | ], 181 | "engines": { 182 | "node": ">=12" 183 | } 184 | }, 185 | "node_modules/@esbuild/linux-ia32": { 186 | "version": "0.17.3", 187 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", 188 | "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", 189 | "cpu": [ 190 | "ia32" 191 | ], 192 | "dev": true, 193 | "optional": true, 194 | "os": [ 195 | "linux" 196 | ], 197 | "engines": { 198 | "node": ">=12" 199 | } 200 | }, 201 | "node_modules/@esbuild/linux-loong64": { 202 | "version": "0.17.3", 203 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", 204 | "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", 205 | "cpu": [ 206 | "loong64" 207 | ], 208 | "dev": true, 209 | "optional": true, 210 | "os": [ 211 | "linux" 212 | ], 213 | "engines": { 214 | "node": ">=12" 215 | } 216 | }, 217 | "node_modules/@esbuild/linux-mips64el": { 218 | "version": "0.17.3", 219 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", 220 | "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", 221 | "cpu": [ 222 | "mips64el" 223 | ], 224 | "dev": true, 225 | "optional": true, 226 | "os": [ 227 | "linux" 228 | ], 229 | "engines": { 230 | "node": ">=12" 231 | } 232 | }, 233 | "node_modules/@esbuild/linux-ppc64": { 234 | "version": "0.17.3", 235 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", 236 | "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", 237 | "cpu": [ 238 | "ppc64" 239 | ], 240 | "dev": true, 241 | "optional": true, 242 | "os": [ 243 | "linux" 244 | ], 245 | "engines": { 246 | "node": ">=12" 247 | } 248 | }, 249 | "node_modules/@esbuild/linux-riscv64": { 250 | "version": "0.17.3", 251 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", 252 | "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", 253 | "cpu": [ 254 | "riscv64" 255 | ], 256 | "dev": true, 257 | "optional": true, 258 | "os": [ 259 | "linux" 260 | ], 261 | "engines": { 262 | "node": ">=12" 263 | } 264 | }, 265 | "node_modules/@esbuild/linux-s390x": { 266 | "version": "0.17.3", 267 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", 268 | "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", 269 | "cpu": [ 270 | "s390x" 271 | ], 272 | "dev": true, 273 | "optional": true, 274 | "os": [ 275 | "linux" 276 | ], 277 | "engines": { 278 | "node": ">=12" 279 | } 280 | }, 281 | "node_modules/@esbuild/linux-x64": { 282 | "version": "0.17.3", 283 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", 284 | "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", 285 | "cpu": [ 286 | "x64" 287 | ], 288 | "dev": true, 289 | "optional": true, 290 | "os": [ 291 | "linux" 292 | ], 293 | "engines": { 294 | "node": ">=12" 295 | } 296 | }, 297 | "node_modules/@esbuild/netbsd-x64": { 298 | "version": "0.17.3", 299 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", 300 | "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", 301 | "cpu": [ 302 | "x64" 303 | ], 304 | "dev": true, 305 | "optional": true, 306 | "os": [ 307 | "netbsd" 308 | ], 309 | "engines": { 310 | "node": ">=12" 311 | } 312 | }, 313 | "node_modules/@esbuild/openbsd-x64": { 314 | "version": "0.17.3", 315 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", 316 | "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", 317 | "cpu": [ 318 | "x64" 319 | ], 320 | "dev": true, 321 | "optional": true, 322 | "os": [ 323 | "openbsd" 324 | ], 325 | "engines": { 326 | "node": ">=12" 327 | } 328 | }, 329 | "node_modules/@esbuild/sunos-x64": { 330 | "version": "0.17.3", 331 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", 332 | "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", 333 | "cpu": [ 334 | "x64" 335 | ], 336 | "dev": true, 337 | "optional": true, 338 | "os": [ 339 | "sunos" 340 | ], 341 | "engines": { 342 | "node": ">=12" 343 | } 344 | }, 345 | "node_modules/@esbuild/win32-arm64": { 346 | "version": "0.17.3", 347 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", 348 | "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", 349 | "cpu": [ 350 | "arm64" 351 | ], 352 | "dev": true, 353 | "optional": true, 354 | "os": [ 355 | "win32" 356 | ], 357 | "engines": { 358 | "node": ">=12" 359 | } 360 | }, 361 | "node_modules/@esbuild/win32-ia32": { 362 | "version": "0.17.3", 363 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", 364 | "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", 365 | "cpu": [ 366 | "ia32" 367 | ], 368 | "dev": true, 369 | "optional": true, 370 | "os": [ 371 | "win32" 372 | ], 373 | "engines": { 374 | "node": ">=12" 375 | } 376 | }, 377 | "node_modules/@esbuild/win32-x64": { 378 | "version": "0.17.3", 379 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", 380 | "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", 381 | "cpu": [ 382 | "x64" 383 | ], 384 | "dev": true, 385 | "optional": true, 386 | "os": [ 387 | "win32" 388 | ], 389 | "engines": { 390 | "node": ">=12" 391 | } 392 | }, 393 | "node_modules/@eslint-community/eslint-utils": { 394 | "version": "4.4.0", 395 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", 396 | "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", 397 | "dev": true, 398 | "peer": true, 399 | "dependencies": { 400 | "eslint-visitor-keys": "^3.3.0" 401 | }, 402 | "engines": { 403 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 404 | }, 405 | "peerDependencies": { 406 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 407 | } 408 | }, 409 | "node_modules/@eslint-community/regexpp": { 410 | "version": "4.11.0", 411 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", 412 | "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", 413 | "dev": true, 414 | "peer": true, 415 | "engines": { 416 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 417 | } 418 | }, 419 | "node_modules/@eslint/eslintrc": { 420 | "version": "2.1.4", 421 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", 422 | "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", 423 | "dev": true, 424 | "peer": true, 425 | "dependencies": { 426 | "ajv": "^6.12.4", 427 | "debug": "^4.3.2", 428 | "espree": "^9.6.0", 429 | "globals": "^13.19.0", 430 | "ignore": "^5.2.0", 431 | "import-fresh": "^3.2.1", 432 | "js-yaml": "^4.1.0", 433 | "minimatch": "^3.1.2", 434 | "strip-json-comments": "^3.1.1" 435 | }, 436 | "engines": { 437 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 438 | }, 439 | "funding": { 440 | "url": "https://opencollective.com/eslint" 441 | } 442 | }, 443 | "node_modules/@eslint/js": { 444 | "version": "8.57.0", 445 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", 446 | "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", 447 | "dev": true, 448 | "peer": true, 449 | "engines": { 450 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 451 | } 452 | }, 453 | "node_modules/@humanwhocodes/config-array": { 454 | "version": "0.11.14", 455 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", 456 | "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", 457 | "deprecated": "Use @eslint/config-array instead", 458 | "dev": true, 459 | "peer": true, 460 | "dependencies": { 461 | "@humanwhocodes/object-schema": "^2.0.2", 462 | "debug": "^4.3.1", 463 | "minimatch": "^3.0.5" 464 | }, 465 | "engines": { 466 | "node": ">=10.10.0" 467 | } 468 | }, 469 | "node_modules/@humanwhocodes/module-importer": { 470 | "version": "1.0.1", 471 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 472 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 473 | "dev": true, 474 | "peer": true, 475 | "engines": { 476 | "node": ">=12.22" 477 | }, 478 | "funding": { 479 | "type": "github", 480 | "url": "https://github.com/sponsors/nzakas" 481 | } 482 | }, 483 | "node_modules/@humanwhocodes/object-schema": { 484 | "version": "2.0.3", 485 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", 486 | "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", 487 | "deprecated": "Use @eslint/object-schema instead", 488 | "dev": true, 489 | "peer": true 490 | }, 491 | "node_modules/@nodelib/fs.scandir": { 492 | "version": "2.1.5", 493 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 494 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 495 | "dev": true, 496 | "dependencies": { 497 | "@nodelib/fs.stat": "2.0.5", 498 | "run-parallel": "^1.1.9" 499 | }, 500 | "engines": { 501 | "node": ">= 8" 502 | } 503 | }, 504 | "node_modules/@nodelib/fs.stat": { 505 | "version": "2.0.5", 506 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 507 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 508 | "dev": true, 509 | "engines": { 510 | "node": ">= 8" 511 | } 512 | }, 513 | "node_modules/@nodelib/fs.walk": { 514 | "version": "1.2.8", 515 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 516 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 517 | "dev": true, 518 | "dependencies": { 519 | "@nodelib/fs.scandir": "2.1.5", 520 | "fastq": "^1.6.0" 521 | }, 522 | "engines": { 523 | "node": ">= 8" 524 | } 525 | }, 526 | "node_modules/@types/codemirror": { 527 | "version": "5.60.8", 528 | "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", 529 | "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", 530 | "dev": true, 531 | "dependencies": { 532 | "@types/tern": "*" 533 | } 534 | }, 535 | "node_modules/@types/estree": { 536 | "version": "1.0.5", 537 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 538 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 539 | "dev": true 540 | }, 541 | "node_modules/@types/json-schema": { 542 | "version": "7.0.15", 543 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 544 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 545 | "dev": true 546 | }, 547 | "node_modules/@types/node": { 548 | "version": "16.18.107", 549 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.107.tgz", 550 | "integrity": "sha512-VSha8UIBpCpETub8FZ1nXkODXm+k+YRwpuVQsF3zOuD6QyPQeuIdPRm6IBVa2E5en58CUFJfaw6GmYHP2q/vkQ==", 551 | "dev": true 552 | }, 553 | "node_modules/@types/tern": { 554 | "version": "0.23.9", 555 | "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", 556 | "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", 557 | "dev": true, 558 | "dependencies": { 559 | "@types/estree": "*" 560 | } 561 | }, 562 | "node_modules/@typescript-eslint/eslint-plugin": { 563 | "version": "5.29.0", 564 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", 565 | "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", 566 | "dev": true, 567 | "dependencies": { 568 | "@typescript-eslint/scope-manager": "5.29.0", 569 | "@typescript-eslint/type-utils": "5.29.0", 570 | "@typescript-eslint/utils": "5.29.0", 571 | "debug": "^4.3.4", 572 | "functional-red-black-tree": "^1.0.1", 573 | "ignore": "^5.2.0", 574 | "regexpp": "^3.2.0", 575 | "semver": "^7.3.7", 576 | "tsutils": "^3.21.0" 577 | }, 578 | "engines": { 579 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 580 | }, 581 | "funding": { 582 | "type": "opencollective", 583 | "url": "https://opencollective.com/typescript-eslint" 584 | }, 585 | "peerDependencies": { 586 | "@typescript-eslint/parser": "^5.0.0", 587 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 588 | }, 589 | "peerDependenciesMeta": { 590 | "typescript": { 591 | "optional": true 592 | } 593 | } 594 | }, 595 | "node_modules/@typescript-eslint/parser": { 596 | "version": "5.29.0", 597 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", 598 | "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", 599 | "dev": true, 600 | "dependencies": { 601 | "@typescript-eslint/scope-manager": "5.29.0", 602 | "@typescript-eslint/types": "5.29.0", 603 | "@typescript-eslint/typescript-estree": "5.29.0", 604 | "debug": "^4.3.4" 605 | }, 606 | "engines": { 607 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 608 | }, 609 | "funding": { 610 | "type": "opencollective", 611 | "url": "https://opencollective.com/typescript-eslint" 612 | }, 613 | "peerDependencies": { 614 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 615 | }, 616 | "peerDependenciesMeta": { 617 | "typescript": { 618 | "optional": true 619 | } 620 | } 621 | }, 622 | "node_modules/@typescript-eslint/scope-manager": { 623 | "version": "5.29.0", 624 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", 625 | "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", 626 | "dev": true, 627 | "dependencies": { 628 | "@typescript-eslint/types": "5.29.0", 629 | "@typescript-eslint/visitor-keys": "5.29.0" 630 | }, 631 | "engines": { 632 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 633 | }, 634 | "funding": { 635 | "type": "opencollective", 636 | "url": "https://opencollective.com/typescript-eslint" 637 | } 638 | }, 639 | "node_modules/@typescript-eslint/type-utils": { 640 | "version": "5.29.0", 641 | "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", 642 | "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", 643 | "dev": true, 644 | "dependencies": { 645 | "@typescript-eslint/utils": "5.29.0", 646 | "debug": "^4.3.4", 647 | "tsutils": "^3.21.0" 648 | }, 649 | "engines": { 650 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 651 | }, 652 | "funding": { 653 | "type": "opencollective", 654 | "url": "https://opencollective.com/typescript-eslint" 655 | }, 656 | "peerDependencies": { 657 | "eslint": "*" 658 | }, 659 | "peerDependenciesMeta": { 660 | "typescript": { 661 | "optional": true 662 | } 663 | } 664 | }, 665 | "node_modules/@typescript-eslint/types": { 666 | "version": "5.29.0", 667 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", 668 | "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", 669 | "dev": true, 670 | "engines": { 671 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 672 | }, 673 | "funding": { 674 | "type": "opencollective", 675 | "url": "https://opencollective.com/typescript-eslint" 676 | } 677 | }, 678 | "node_modules/@typescript-eslint/typescript-estree": { 679 | "version": "5.29.0", 680 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", 681 | "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", 682 | "dev": true, 683 | "dependencies": { 684 | "@typescript-eslint/types": "5.29.0", 685 | "@typescript-eslint/visitor-keys": "5.29.0", 686 | "debug": "^4.3.4", 687 | "globby": "^11.1.0", 688 | "is-glob": "^4.0.3", 689 | "semver": "^7.3.7", 690 | "tsutils": "^3.21.0" 691 | }, 692 | "engines": { 693 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 694 | }, 695 | "funding": { 696 | "type": "opencollective", 697 | "url": "https://opencollective.com/typescript-eslint" 698 | }, 699 | "peerDependenciesMeta": { 700 | "typescript": { 701 | "optional": true 702 | } 703 | } 704 | }, 705 | "node_modules/@typescript-eslint/utils": { 706 | "version": "5.29.0", 707 | "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", 708 | "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", 709 | "dev": true, 710 | "dependencies": { 711 | "@types/json-schema": "^7.0.9", 712 | "@typescript-eslint/scope-manager": "5.29.0", 713 | "@typescript-eslint/types": "5.29.0", 714 | "@typescript-eslint/typescript-estree": "5.29.0", 715 | "eslint-scope": "^5.1.1", 716 | "eslint-utils": "^3.0.0" 717 | }, 718 | "engines": { 719 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 720 | }, 721 | "funding": { 722 | "type": "opencollective", 723 | "url": "https://opencollective.com/typescript-eslint" 724 | }, 725 | "peerDependencies": { 726 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" 727 | } 728 | }, 729 | "node_modules/@typescript-eslint/visitor-keys": { 730 | "version": "5.29.0", 731 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", 732 | "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", 733 | "dev": true, 734 | "dependencies": { 735 | "@typescript-eslint/types": "5.29.0", 736 | "eslint-visitor-keys": "^3.3.0" 737 | }, 738 | "engines": { 739 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 740 | }, 741 | "funding": { 742 | "type": "opencollective", 743 | "url": "https://opencollective.com/typescript-eslint" 744 | } 745 | }, 746 | "node_modules/@ungap/structured-clone": { 747 | "version": "1.2.0", 748 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", 749 | "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", 750 | "dev": true, 751 | "peer": true 752 | }, 753 | "node_modules/acorn": { 754 | "version": "8.12.1", 755 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", 756 | "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", 757 | "dev": true, 758 | "peer": true, 759 | "bin": { 760 | "acorn": "bin/acorn" 761 | }, 762 | "engines": { 763 | "node": ">=0.4.0" 764 | } 765 | }, 766 | "node_modules/acorn-jsx": { 767 | "version": "5.3.2", 768 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 769 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 770 | "dev": true, 771 | "peer": true, 772 | "peerDependencies": { 773 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 774 | } 775 | }, 776 | "node_modules/ajv": { 777 | "version": "6.12.6", 778 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 779 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 780 | "dev": true, 781 | "peer": true, 782 | "dependencies": { 783 | "fast-deep-equal": "^3.1.1", 784 | "fast-json-stable-stringify": "^2.0.0", 785 | "json-schema-traverse": "^0.4.1", 786 | "uri-js": "^4.2.2" 787 | }, 788 | "funding": { 789 | "type": "github", 790 | "url": "https://github.com/sponsors/epoberezkin" 791 | } 792 | }, 793 | "node_modules/ansi-regex": { 794 | "version": "5.0.1", 795 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 796 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 797 | "dev": true, 798 | "peer": true, 799 | "engines": { 800 | "node": ">=8" 801 | } 802 | }, 803 | "node_modules/ansi-styles": { 804 | "version": "4.3.0", 805 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 806 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 807 | "dev": true, 808 | "peer": true, 809 | "dependencies": { 810 | "color-convert": "^2.0.1" 811 | }, 812 | "engines": { 813 | "node": ">=8" 814 | }, 815 | "funding": { 816 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 817 | } 818 | }, 819 | "node_modules/argparse": { 820 | "version": "2.0.1", 821 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 822 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 823 | "dev": true, 824 | "peer": true 825 | }, 826 | "node_modules/array-union": { 827 | "version": "2.1.0", 828 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 829 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 830 | "dev": true, 831 | "engines": { 832 | "node": ">=8" 833 | } 834 | }, 835 | "node_modules/balanced-match": { 836 | "version": "1.0.2", 837 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 838 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 839 | "dev": true, 840 | "peer": true 841 | }, 842 | "node_modules/brace-expansion": { 843 | "version": "1.1.11", 844 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 845 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 846 | "dev": true, 847 | "peer": true, 848 | "dependencies": { 849 | "balanced-match": "^1.0.0", 850 | "concat-map": "0.0.1" 851 | } 852 | }, 853 | "node_modules/braces": { 854 | "version": "3.0.3", 855 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 856 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 857 | "dev": true, 858 | "dependencies": { 859 | "fill-range": "^7.1.1" 860 | }, 861 | "engines": { 862 | "node": ">=8" 863 | } 864 | }, 865 | "node_modules/builtin-modules": { 866 | "version": "3.3.0", 867 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", 868 | "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", 869 | "dev": true, 870 | "engines": { 871 | "node": ">=6" 872 | }, 873 | "funding": { 874 | "url": "https://github.com/sponsors/sindresorhus" 875 | } 876 | }, 877 | "node_modules/callsites": { 878 | "version": "3.1.0", 879 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 880 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 881 | "dev": true, 882 | "peer": true, 883 | "engines": { 884 | "node": ">=6" 885 | } 886 | }, 887 | "node_modules/chalk": { 888 | "version": "4.1.2", 889 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 890 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 891 | "dev": true, 892 | "peer": true, 893 | "dependencies": { 894 | "ansi-styles": "^4.1.0", 895 | "supports-color": "^7.1.0" 896 | }, 897 | "engines": { 898 | "node": ">=10" 899 | }, 900 | "funding": { 901 | "url": "https://github.com/chalk/chalk?sponsor=1" 902 | } 903 | }, 904 | "node_modules/color-convert": { 905 | "version": "2.0.1", 906 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 907 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 908 | "dev": true, 909 | "peer": true, 910 | "dependencies": { 911 | "color-name": "~1.1.4" 912 | }, 913 | "engines": { 914 | "node": ">=7.0.0" 915 | } 916 | }, 917 | "node_modules/color-name": { 918 | "version": "1.1.4", 919 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 920 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 921 | "dev": true, 922 | "peer": true 923 | }, 924 | "node_modules/concat-map": { 925 | "version": "0.0.1", 926 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 927 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 928 | "dev": true, 929 | "peer": true 930 | }, 931 | "node_modules/cross-spawn": { 932 | "version": "7.0.3", 933 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 934 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 935 | "dev": true, 936 | "peer": true, 937 | "dependencies": { 938 | "path-key": "^3.1.0", 939 | "shebang-command": "^2.0.0", 940 | "which": "^2.0.1" 941 | }, 942 | "engines": { 943 | "node": ">= 8" 944 | } 945 | }, 946 | "node_modules/debug": { 947 | "version": "4.3.6", 948 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 949 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 950 | "dev": true, 951 | "dependencies": { 952 | "ms": "2.1.2" 953 | }, 954 | "engines": { 955 | "node": ">=6.0" 956 | }, 957 | "peerDependenciesMeta": { 958 | "supports-color": { 959 | "optional": true 960 | } 961 | } 962 | }, 963 | "node_modules/deep-is": { 964 | "version": "0.1.4", 965 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 966 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 967 | "dev": true, 968 | "peer": true 969 | }, 970 | "node_modules/dir-glob": { 971 | "version": "3.0.1", 972 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 973 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 974 | "dev": true, 975 | "dependencies": { 976 | "path-type": "^4.0.0" 977 | }, 978 | "engines": { 979 | "node": ">=8" 980 | } 981 | }, 982 | "node_modules/doctrine": { 983 | "version": "3.0.0", 984 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 985 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 986 | "dev": true, 987 | "peer": true, 988 | "dependencies": { 989 | "esutils": "^2.0.2" 990 | }, 991 | "engines": { 992 | "node": ">=6.0.0" 993 | } 994 | }, 995 | "node_modules/esbuild": { 996 | "version": "0.17.3", 997 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", 998 | "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", 999 | "dev": true, 1000 | "hasInstallScript": true, 1001 | "bin": { 1002 | "esbuild": "bin/esbuild" 1003 | }, 1004 | "engines": { 1005 | "node": ">=12" 1006 | }, 1007 | "optionalDependencies": { 1008 | "@esbuild/android-arm": "0.17.3", 1009 | "@esbuild/android-arm64": "0.17.3", 1010 | "@esbuild/android-x64": "0.17.3", 1011 | "@esbuild/darwin-arm64": "0.17.3", 1012 | "@esbuild/darwin-x64": "0.17.3", 1013 | "@esbuild/freebsd-arm64": "0.17.3", 1014 | "@esbuild/freebsd-x64": "0.17.3", 1015 | "@esbuild/linux-arm": "0.17.3", 1016 | "@esbuild/linux-arm64": "0.17.3", 1017 | "@esbuild/linux-ia32": "0.17.3", 1018 | "@esbuild/linux-loong64": "0.17.3", 1019 | "@esbuild/linux-mips64el": "0.17.3", 1020 | "@esbuild/linux-ppc64": "0.17.3", 1021 | "@esbuild/linux-riscv64": "0.17.3", 1022 | "@esbuild/linux-s390x": "0.17.3", 1023 | "@esbuild/linux-x64": "0.17.3", 1024 | "@esbuild/netbsd-x64": "0.17.3", 1025 | "@esbuild/openbsd-x64": "0.17.3", 1026 | "@esbuild/sunos-x64": "0.17.3", 1027 | "@esbuild/win32-arm64": "0.17.3", 1028 | "@esbuild/win32-ia32": "0.17.3", 1029 | "@esbuild/win32-x64": "0.17.3" 1030 | } 1031 | }, 1032 | "node_modules/escape-string-regexp": { 1033 | "version": "4.0.0", 1034 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1035 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1036 | "dev": true, 1037 | "peer": true, 1038 | "engines": { 1039 | "node": ">=10" 1040 | }, 1041 | "funding": { 1042 | "url": "https://github.com/sponsors/sindresorhus" 1043 | } 1044 | }, 1045 | "node_modules/eslint": { 1046 | "version": "8.57.0", 1047 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", 1048 | "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", 1049 | "dev": true, 1050 | "peer": true, 1051 | "dependencies": { 1052 | "@eslint-community/eslint-utils": "^4.2.0", 1053 | "@eslint-community/regexpp": "^4.6.1", 1054 | "@eslint/eslintrc": "^2.1.4", 1055 | "@eslint/js": "8.57.0", 1056 | "@humanwhocodes/config-array": "^0.11.14", 1057 | "@humanwhocodes/module-importer": "^1.0.1", 1058 | "@nodelib/fs.walk": "^1.2.8", 1059 | "@ungap/structured-clone": "^1.2.0", 1060 | "ajv": "^6.12.4", 1061 | "chalk": "^4.0.0", 1062 | "cross-spawn": "^7.0.2", 1063 | "debug": "^4.3.2", 1064 | "doctrine": "^3.0.0", 1065 | "escape-string-regexp": "^4.0.0", 1066 | "eslint-scope": "^7.2.2", 1067 | "eslint-visitor-keys": "^3.4.3", 1068 | "espree": "^9.6.1", 1069 | "esquery": "^1.4.2", 1070 | "esutils": "^2.0.2", 1071 | "fast-deep-equal": "^3.1.3", 1072 | "file-entry-cache": "^6.0.1", 1073 | "find-up": "^5.0.0", 1074 | "glob-parent": "^6.0.2", 1075 | "globals": "^13.19.0", 1076 | "graphemer": "^1.4.0", 1077 | "ignore": "^5.2.0", 1078 | "imurmurhash": "^0.1.4", 1079 | "is-glob": "^4.0.0", 1080 | "is-path-inside": "^3.0.3", 1081 | "js-yaml": "^4.1.0", 1082 | "json-stable-stringify-without-jsonify": "^1.0.1", 1083 | "levn": "^0.4.1", 1084 | "lodash.merge": "^4.6.2", 1085 | "minimatch": "^3.1.2", 1086 | "natural-compare": "^1.4.0", 1087 | "optionator": "^0.9.3", 1088 | "strip-ansi": "^6.0.1", 1089 | "text-table": "^0.2.0" 1090 | }, 1091 | "bin": { 1092 | "eslint": "bin/eslint.js" 1093 | }, 1094 | "engines": { 1095 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1096 | }, 1097 | "funding": { 1098 | "url": "https://opencollective.com/eslint" 1099 | } 1100 | }, 1101 | "node_modules/eslint-scope": { 1102 | "version": "5.1.1", 1103 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 1104 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 1105 | "dev": true, 1106 | "dependencies": { 1107 | "esrecurse": "^4.3.0", 1108 | "estraverse": "^4.1.1" 1109 | }, 1110 | "engines": { 1111 | "node": ">=8.0.0" 1112 | } 1113 | }, 1114 | "node_modules/eslint-utils": { 1115 | "version": "3.0.0", 1116 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 1117 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 1118 | "dev": true, 1119 | "dependencies": { 1120 | "eslint-visitor-keys": "^2.0.0" 1121 | }, 1122 | "engines": { 1123 | "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" 1124 | }, 1125 | "funding": { 1126 | "url": "https://github.com/sponsors/mysticatea" 1127 | }, 1128 | "peerDependencies": { 1129 | "eslint": ">=5" 1130 | } 1131 | }, 1132 | "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { 1133 | "version": "2.1.0", 1134 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 1135 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 1136 | "dev": true, 1137 | "engines": { 1138 | "node": ">=10" 1139 | } 1140 | }, 1141 | "node_modules/eslint-visitor-keys": { 1142 | "version": "3.4.3", 1143 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 1144 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 1145 | "dev": true, 1146 | "engines": { 1147 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1148 | }, 1149 | "funding": { 1150 | "url": "https://opencollective.com/eslint" 1151 | } 1152 | }, 1153 | "node_modules/eslint/node_modules/eslint-scope": { 1154 | "version": "7.2.2", 1155 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", 1156 | "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", 1157 | "dev": true, 1158 | "peer": true, 1159 | "dependencies": { 1160 | "esrecurse": "^4.3.0", 1161 | "estraverse": "^5.2.0" 1162 | }, 1163 | "engines": { 1164 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1165 | }, 1166 | "funding": { 1167 | "url": "https://opencollective.com/eslint" 1168 | } 1169 | }, 1170 | "node_modules/eslint/node_modules/estraverse": { 1171 | "version": "5.3.0", 1172 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1173 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1174 | "dev": true, 1175 | "peer": true, 1176 | "engines": { 1177 | "node": ">=4.0" 1178 | } 1179 | }, 1180 | "node_modules/espree": { 1181 | "version": "9.6.1", 1182 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", 1183 | "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", 1184 | "dev": true, 1185 | "peer": true, 1186 | "dependencies": { 1187 | "acorn": "^8.9.0", 1188 | "acorn-jsx": "^5.3.2", 1189 | "eslint-visitor-keys": "^3.4.1" 1190 | }, 1191 | "engines": { 1192 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 1193 | }, 1194 | "funding": { 1195 | "url": "https://opencollective.com/eslint" 1196 | } 1197 | }, 1198 | "node_modules/esquery": { 1199 | "version": "1.6.0", 1200 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 1201 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 1202 | "dev": true, 1203 | "peer": true, 1204 | "dependencies": { 1205 | "estraverse": "^5.1.0" 1206 | }, 1207 | "engines": { 1208 | "node": ">=0.10" 1209 | } 1210 | }, 1211 | "node_modules/esquery/node_modules/estraverse": { 1212 | "version": "5.3.0", 1213 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1214 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1215 | "dev": true, 1216 | "peer": true, 1217 | "engines": { 1218 | "node": ">=4.0" 1219 | } 1220 | }, 1221 | "node_modules/esrecurse": { 1222 | "version": "4.3.0", 1223 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1224 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1225 | "dev": true, 1226 | "dependencies": { 1227 | "estraverse": "^5.2.0" 1228 | }, 1229 | "engines": { 1230 | "node": ">=4.0" 1231 | } 1232 | }, 1233 | "node_modules/esrecurse/node_modules/estraverse": { 1234 | "version": "5.3.0", 1235 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1236 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1237 | "dev": true, 1238 | "engines": { 1239 | "node": ">=4.0" 1240 | } 1241 | }, 1242 | "node_modules/estraverse": { 1243 | "version": "4.3.0", 1244 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 1245 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 1246 | "dev": true, 1247 | "engines": { 1248 | "node": ">=4.0" 1249 | } 1250 | }, 1251 | "node_modules/esutils": { 1252 | "version": "2.0.3", 1253 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1254 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1255 | "dev": true, 1256 | "peer": true, 1257 | "engines": { 1258 | "node": ">=0.10.0" 1259 | } 1260 | }, 1261 | "node_modules/fast-deep-equal": { 1262 | "version": "3.1.3", 1263 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1264 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1265 | "dev": true, 1266 | "peer": true 1267 | }, 1268 | "node_modules/fast-glob": { 1269 | "version": "3.3.2", 1270 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 1271 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 1272 | "dev": true, 1273 | "dependencies": { 1274 | "@nodelib/fs.stat": "^2.0.2", 1275 | "@nodelib/fs.walk": "^1.2.3", 1276 | "glob-parent": "^5.1.2", 1277 | "merge2": "^1.3.0", 1278 | "micromatch": "^4.0.4" 1279 | }, 1280 | "engines": { 1281 | "node": ">=8.6.0" 1282 | } 1283 | }, 1284 | "node_modules/fast-glob/node_modules/glob-parent": { 1285 | "version": "5.1.2", 1286 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1287 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1288 | "dev": true, 1289 | "dependencies": { 1290 | "is-glob": "^4.0.1" 1291 | }, 1292 | "engines": { 1293 | "node": ">= 6" 1294 | } 1295 | }, 1296 | "node_modules/fast-json-stable-stringify": { 1297 | "version": "2.1.0", 1298 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1299 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1300 | "dev": true, 1301 | "peer": true 1302 | }, 1303 | "node_modules/fast-levenshtein": { 1304 | "version": "2.0.6", 1305 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1306 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 1307 | "dev": true, 1308 | "peer": true 1309 | }, 1310 | "node_modules/fastq": { 1311 | "version": "1.17.1", 1312 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 1313 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 1314 | "dev": true, 1315 | "dependencies": { 1316 | "reusify": "^1.0.4" 1317 | } 1318 | }, 1319 | "node_modules/file-entry-cache": { 1320 | "version": "6.0.1", 1321 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 1322 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 1323 | "dev": true, 1324 | "peer": true, 1325 | "dependencies": { 1326 | "flat-cache": "^3.0.4" 1327 | }, 1328 | "engines": { 1329 | "node": "^10.12.0 || >=12.0.0" 1330 | } 1331 | }, 1332 | "node_modules/fill-range": { 1333 | "version": "7.1.1", 1334 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1335 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1336 | "dev": true, 1337 | "dependencies": { 1338 | "to-regex-range": "^5.0.1" 1339 | }, 1340 | "engines": { 1341 | "node": ">=8" 1342 | } 1343 | }, 1344 | "node_modules/find-up": { 1345 | "version": "5.0.0", 1346 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1347 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1348 | "dev": true, 1349 | "peer": true, 1350 | "dependencies": { 1351 | "locate-path": "^6.0.0", 1352 | "path-exists": "^4.0.0" 1353 | }, 1354 | "engines": { 1355 | "node": ">=10" 1356 | }, 1357 | "funding": { 1358 | "url": "https://github.com/sponsors/sindresorhus" 1359 | } 1360 | }, 1361 | "node_modules/flat-cache": { 1362 | "version": "3.2.0", 1363 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", 1364 | "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", 1365 | "dev": true, 1366 | "peer": true, 1367 | "dependencies": { 1368 | "flatted": "^3.2.9", 1369 | "keyv": "^4.5.3", 1370 | "rimraf": "^3.0.2" 1371 | }, 1372 | "engines": { 1373 | "node": "^10.12.0 || >=12.0.0" 1374 | } 1375 | }, 1376 | "node_modules/flatted": { 1377 | "version": "3.3.1", 1378 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", 1379 | "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", 1380 | "dev": true, 1381 | "peer": true 1382 | }, 1383 | "node_modules/fs.realpath": { 1384 | "version": "1.0.0", 1385 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1386 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1387 | "dev": true, 1388 | "peer": true 1389 | }, 1390 | "node_modules/functional-red-black-tree": { 1391 | "version": "1.0.1", 1392 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 1393 | "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", 1394 | "dev": true 1395 | }, 1396 | "node_modules/glob": { 1397 | "version": "7.2.3", 1398 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1399 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1400 | "deprecated": "Glob versions prior to v9 are no longer supported", 1401 | "dev": true, 1402 | "peer": true, 1403 | "dependencies": { 1404 | "fs.realpath": "^1.0.0", 1405 | "inflight": "^1.0.4", 1406 | "inherits": "2", 1407 | "minimatch": "^3.1.1", 1408 | "once": "^1.3.0", 1409 | "path-is-absolute": "^1.0.0" 1410 | }, 1411 | "engines": { 1412 | "node": "*" 1413 | }, 1414 | "funding": { 1415 | "url": "https://github.com/sponsors/isaacs" 1416 | } 1417 | }, 1418 | "node_modules/glob-parent": { 1419 | "version": "6.0.2", 1420 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1421 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1422 | "dev": true, 1423 | "peer": true, 1424 | "dependencies": { 1425 | "is-glob": "^4.0.3" 1426 | }, 1427 | "engines": { 1428 | "node": ">=10.13.0" 1429 | } 1430 | }, 1431 | "node_modules/globals": { 1432 | "version": "13.24.0", 1433 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", 1434 | "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", 1435 | "dev": true, 1436 | "peer": true, 1437 | "dependencies": { 1438 | "type-fest": "^0.20.2" 1439 | }, 1440 | "engines": { 1441 | "node": ">=8" 1442 | }, 1443 | "funding": { 1444 | "url": "https://github.com/sponsors/sindresorhus" 1445 | } 1446 | }, 1447 | "node_modules/globby": { 1448 | "version": "11.1.0", 1449 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", 1450 | "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", 1451 | "dev": true, 1452 | "dependencies": { 1453 | "array-union": "^2.1.0", 1454 | "dir-glob": "^3.0.1", 1455 | "fast-glob": "^3.2.9", 1456 | "ignore": "^5.2.0", 1457 | "merge2": "^1.4.1", 1458 | "slash": "^3.0.0" 1459 | }, 1460 | "engines": { 1461 | "node": ">=10" 1462 | }, 1463 | "funding": { 1464 | "url": "https://github.com/sponsors/sindresorhus" 1465 | } 1466 | }, 1467 | "node_modules/graphemer": { 1468 | "version": "1.4.0", 1469 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1470 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1471 | "dev": true, 1472 | "peer": true 1473 | }, 1474 | "node_modules/has-flag": { 1475 | "version": "4.0.0", 1476 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1477 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1478 | "dev": true, 1479 | "peer": true, 1480 | "engines": { 1481 | "node": ">=8" 1482 | } 1483 | }, 1484 | "node_modules/ignore": { 1485 | "version": "5.3.2", 1486 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1487 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1488 | "dev": true, 1489 | "engines": { 1490 | "node": ">= 4" 1491 | } 1492 | }, 1493 | "node_modules/import-fresh": { 1494 | "version": "3.3.0", 1495 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1496 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1497 | "dev": true, 1498 | "peer": true, 1499 | "dependencies": { 1500 | "parent-module": "^1.0.0", 1501 | "resolve-from": "^4.0.0" 1502 | }, 1503 | "engines": { 1504 | "node": ">=6" 1505 | }, 1506 | "funding": { 1507 | "url": "https://github.com/sponsors/sindresorhus" 1508 | } 1509 | }, 1510 | "node_modules/imurmurhash": { 1511 | "version": "0.1.4", 1512 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1513 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1514 | "dev": true, 1515 | "peer": true, 1516 | "engines": { 1517 | "node": ">=0.8.19" 1518 | } 1519 | }, 1520 | "node_modules/inflight": { 1521 | "version": "1.0.6", 1522 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1523 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1524 | "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.", 1525 | "dev": true, 1526 | "peer": true, 1527 | "dependencies": { 1528 | "once": "^1.3.0", 1529 | "wrappy": "1" 1530 | } 1531 | }, 1532 | "node_modules/inherits": { 1533 | "version": "2.0.4", 1534 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1535 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1536 | "dev": true, 1537 | "peer": true 1538 | }, 1539 | "node_modules/is-extglob": { 1540 | "version": "2.1.1", 1541 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1542 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1543 | "dev": true, 1544 | "engines": { 1545 | "node": ">=0.10.0" 1546 | } 1547 | }, 1548 | "node_modules/is-glob": { 1549 | "version": "4.0.3", 1550 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1551 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1552 | "dev": true, 1553 | "dependencies": { 1554 | "is-extglob": "^2.1.1" 1555 | }, 1556 | "engines": { 1557 | "node": ">=0.10.0" 1558 | } 1559 | }, 1560 | "node_modules/is-number": { 1561 | "version": "7.0.0", 1562 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1563 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1564 | "dev": true, 1565 | "engines": { 1566 | "node": ">=0.12.0" 1567 | } 1568 | }, 1569 | "node_modules/is-path-inside": { 1570 | "version": "3.0.3", 1571 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 1572 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", 1573 | "dev": true, 1574 | "peer": true, 1575 | "engines": { 1576 | "node": ">=8" 1577 | } 1578 | }, 1579 | "node_modules/isexe": { 1580 | "version": "2.0.0", 1581 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1582 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1583 | "dev": true, 1584 | "peer": true 1585 | }, 1586 | "node_modules/js-yaml": { 1587 | "version": "4.1.0", 1588 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1589 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1590 | "dev": true, 1591 | "peer": true, 1592 | "dependencies": { 1593 | "argparse": "^2.0.1" 1594 | }, 1595 | "bin": { 1596 | "js-yaml": "bin/js-yaml.js" 1597 | } 1598 | }, 1599 | "node_modules/json-buffer": { 1600 | "version": "3.0.1", 1601 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1602 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1603 | "dev": true, 1604 | "peer": true 1605 | }, 1606 | "node_modules/json-schema-traverse": { 1607 | "version": "0.4.1", 1608 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1609 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1610 | "dev": true, 1611 | "peer": true 1612 | }, 1613 | "node_modules/json-stable-stringify-without-jsonify": { 1614 | "version": "1.0.1", 1615 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1616 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1617 | "dev": true, 1618 | "peer": true 1619 | }, 1620 | "node_modules/keyv": { 1621 | "version": "4.5.4", 1622 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1623 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1624 | "dev": true, 1625 | "peer": true, 1626 | "dependencies": { 1627 | "json-buffer": "3.0.1" 1628 | } 1629 | }, 1630 | "node_modules/levn": { 1631 | "version": "0.4.1", 1632 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1633 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1634 | "dev": true, 1635 | "peer": true, 1636 | "dependencies": { 1637 | "prelude-ls": "^1.2.1", 1638 | "type-check": "~0.4.0" 1639 | }, 1640 | "engines": { 1641 | "node": ">= 0.8.0" 1642 | } 1643 | }, 1644 | "node_modules/locate-path": { 1645 | "version": "6.0.0", 1646 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1647 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1648 | "dev": true, 1649 | "peer": true, 1650 | "dependencies": { 1651 | "p-locate": "^5.0.0" 1652 | }, 1653 | "engines": { 1654 | "node": ">=10" 1655 | }, 1656 | "funding": { 1657 | "url": "https://github.com/sponsors/sindresorhus" 1658 | } 1659 | }, 1660 | "node_modules/lodash.merge": { 1661 | "version": "4.6.2", 1662 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1663 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1664 | "dev": true, 1665 | "peer": true 1666 | }, 1667 | "node_modules/merge2": { 1668 | "version": "1.4.1", 1669 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1670 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1671 | "dev": true, 1672 | "engines": { 1673 | "node": ">= 8" 1674 | } 1675 | }, 1676 | "node_modules/micromatch": { 1677 | "version": "4.0.8", 1678 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1679 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1680 | "dev": true, 1681 | "dependencies": { 1682 | "braces": "^3.0.3", 1683 | "picomatch": "^2.3.1" 1684 | }, 1685 | "engines": { 1686 | "node": ">=8.6" 1687 | } 1688 | }, 1689 | "node_modules/minimatch": { 1690 | "version": "3.1.2", 1691 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1692 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1693 | "dev": true, 1694 | "peer": true, 1695 | "dependencies": { 1696 | "brace-expansion": "^1.1.7" 1697 | }, 1698 | "engines": { 1699 | "node": "*" 1700 | } 1701 | }, 1702 | "node_modules/moment": { 1703 | "version": "2.29.4", 1704 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", 1705 | "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", 1706 | "dev": true, 1707 | "engines": { 1708 | "node": "*" 1709 | } 1710 | }, 1711 | "node_modules/ms": { 1712 | "version": "2.1.2", 1713 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1714 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1715 | "dev": true 1716 | }, 1717 | "node_modules/natural-compare": { 1718 | "version": "1.4.0", 1719 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1720 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1721 | "dev": true, 1722 | "peer": true 1723 | }, 1724 | "node_modules/obsidian": { 1725 | "version": "1.6.6", 1726 | "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.6.6.tgz", 1727 | "integrity": "sha512-GZHzeOiwmw/wBjB5JwrsxAZBLqxGQmqtEKSvJJvT0LtTcqeOFnV8jv0ZK5kO7hBb44WxJc+LdS7mZgLXbb+qXQ==", 1728 | "dev": true, 1729 | "dependencies": { 1730 | "@types/codemirror": "5.60.8", 1731 | "moment": "2.29.4" 1732 | }, 1733 | "peerDependencies": { 1734 | "@codemirror/state": "^6.0.0", 1735 | "@codemirror/view": "^6.0.0" 1736 | } 1737 | }, 1738 | "node_modules/once": { 1739 | "version": "1.4.0", 1740 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1741 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1742 | "dev": true, 1743 | "peer": true, 1744 | "dependencies": { 1745 | "wrappy": "1" 1746 | } 1747 | }, 1748 | "node_modules/optionator": { 1749 | "version": "0.9.4", 1750 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 1751 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 1752 | "dev": true, 1753 | "peer": true, 1754 | "dependencies": { 1755 | "deep-is": "^0.1.3", 1756 | "fast-levenshtein": "^2.0.6", 1757 | "levn": "^0.4.1", 1758 | "prelude-ls": "^1.2.1", 1759 | "type-check": "^0.4.0", 1760 | "word-wrap": "^1.2.5" 1761 | }, 1762 | "engines": { 1763 | "node": ">= 0.8.0" 1764 | } 1765 | }, 1766 | "node_modules/p-limit": { 1767 | "version": "3.1.0", 1768 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1769 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1770 | "dev": true, 1771 | "peer": true, 1772 | "dependencies": { 1773 | "yocto-queue": "^0.1.0" 1774 | }, 1775 | "engines": { 1776 | "node": ">=10" 1777 | }, 1778 | "funding": { 1779 | "url": "https://github.com/sponsors/sindresorhus" 1780 | } 1781 | }, 1782 | "node_modules/p-locate": { 1783 | "version": "5.0.0", 1784 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1785 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1786 | "dev": true, 1787 | "peer": true, 1788 | "dependencies": { 1789 | "p-limit": "^3.0.2" 1790 | }, 1791 | "engines": { 1792 | "node": ">=10" 1793 | }, 1794 | "funding": { 1795 | "url": "https://github.com/sponsors/sindresorhus" 1796 | } 1797 | }, 1798 | "node_modules/parent-module": { 1799 | "version": "1.0.1", 1800 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1801 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1802 | "dev": true, 1803 | "peer": true, 1804 | "dependencies": { 1805 | "callsites": "^3.0.0" 1806 | }, 1807 | "engines": { 1808 | "node": ">=6" 1809 | } 1810 | }, 1811 | "node_modules/path-exists": { 1812 | "version": "4.0.0", 1813 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1814 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1815 | "dev": true, 1816 | "peer": true, 1817 | "engines": { 1818 | "node": ">=8" 1819 | } 1820 | }, 1821 | "node_modules/path-is-absolute": { 1822 | "version": "1.0.1", 1823 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1824 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1825 | "dev": true, 1826 | "peer": true, 1827 | "engines": { 1828 | "node": ">=0.10.0" 1829 | } 1830 | }, 1831 | "node_modules/path-key": { 1832 | "version": "3.1.1", 1833 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1834 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1835 | "dev": true, 1836 | "peer": true, 1837 | "engines": { 1838 | "node": ">=8" 1839 | } 1840 | }, 1841 | "node_modules/path-type": { 1842 | "version": "4.0.0", 1843 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1844 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1845 | "dev": true, 1846 | "engines": { 1847 | "node": ">=8" 1848 | } 1849 | }, 1850 | "node_modules/picomatch": { 1851 | "version": "2.3.1", 1852 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1853 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1854 | "dev": true, 1855 | "engines": { 1856 | "node": ">=8.6" 1857 | }, 1858 | "funding": { 1859 | "url": "https://github.com/sponsors/jonschlinkert" 1860 | } 1861 | }, 1862 | "node_modules/prelude-ls": { 1863 | "version": "1.2.1", 1864 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1865 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1866 | "dev": true, 1867 | "peer": true, 1868 | "engines": { 1869 | "node": ">= 0.8.0" 1870 | } 1871 | }, 1872 | "node_modules/punycode": { 1873 | "version": "2.3.1", 1874 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1875 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1876 | "dev": true, 1877 | "peer": true, 1878 | "engines": { 1879 | "node": ">=6" 1880 | } 1881 | }, 1882 | "node_modules/queue-microtask": { 1883 | "version": "1.2.3", 1884 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1885 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1886 | "dev": true, 1887 | "funding": [ 1888 | { 1889 | "type": "github", 1890 | "url": "https://github.com/sponsors/feross" 1891 | }, 1892 | { 1893 | "type": "patreon", 1894 | "url": "https://www.patreon.com/feross" 1895 | }, 1896 | { 1897 | "type": "consulting", 1898 | "url": "https://feross.org/support" 1899 | } 1900 | ] 1901 | }, 1902 | "node_modules/regexpp": { 1903 | "version": "3.2.0", 1904 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 1905 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 1906 | "dev": true, 1907 | "engines": { 1908 | "node": ">=8" 1909 | }, 1910 | "funding": { 1911 | "url": "https://github.com/sponsors/mysticatea" 1912 | } 1913 | }, 1914 | "node_modules/resolve-from": { 1915 | "version": "4.0.0", 1916 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1917 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1918 | "dev": true, 1919 | "peer": true, 1920 | "engines": { 1921 | "node": ">=4" 1922 | } 1923 | }, 1924 | "node_modules/reusify": { 1925 | "version": "1.0.4", 1926 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1927 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1928 | "dev": true, 1929 | "engines": { 1930 | "iojs": ">=1.0.0", 1931 | "node": ">=0.10.0" 1932 | } 1933 | }, 1934 | "node_modules/rimraf": { 1935 | "version": "3.0.2", 1936 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1937 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1938 | "deprecated": "Rimraf versions prior to v4 are no longer supported", 1939 | "dev": true, 1940 | "peer": true, 1941 | "dependencies": { 1942 | "glob": "^7.1.3" 1943 | }, 1944 | "bin": { 1945 | "rimraf": "bin.js" 1946 | }, 1947 | "funding": { 1948 | "url": "https://github.com/sponsors/isaacs" 1949 | } 1950 | }, 1951 | "node_modules/run-parallel": { 1952 | "version": "1.2.0", 1953 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1954 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1955 | "dev": true, 1956 | "funding": [ 1957 | { 1958 | "type": "github", 1959 | "url": "https://github.com/sponsors/feross" 1960 | }, 1961 | { 1962 | "type": "patreon", 1963 | "url": "https://www.patreon.com/feross" 1964 | }, 1965 | { 1966 | "type": "consulting", 1967 | "url": "https://feross.org/support" 1968 | } 1969 | ], 1970 | "dependencies": { 1971 | "queue-microtask": "^1.2.2" 1972 | } 1973 | }, 1974 | "node_modules/semver": { 1975 | "version": "7.6.3", 1976 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 1977 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 1978 | "dev": true, 1979 | "bin": { 1980 | "semver": "bin/semver.js" 1981 | }, 1982 | "engines": { 1983 | "node": ">=10" 1984 | } 1985 | }, 1986 | "node_modules/shebang-command": { 1987 | "version": "2.0.0", 1988 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1989 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1990 | "dev": true, 1991 | "peer": true, 1992 | "dependencies": { 1993 | "shebang-regex": "^3.0.0" 1994 | }, 1995 | "engines": { 1996 | "node": ">=8" 1997 | } 1998 | }, 1999 | "node_modules/shebang-regex": { 2000 | "version": "3.0.0", 2001 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2002 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2003 | "dev": true, 2004 | "peer": true, 2005 | "engines": { 2006 | "node": ">=8" 2007 | } 2008 | }, 2009 | "node_modules/slash": { 2010 | "version": "3.0.0", 2011 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 2012 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 2013 | "dev": true, 2014 | "engines": { 2015 | "node": ">=8" 2016 | } 2017 | }, 2018 | "node_modules/strip-ansi": { 2019 | "version": "6.0.1", 2020 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2021 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2022 | "dev": true, 2023 | "peer": true, 2024 | "dependencies": { 2025 | "ansi-regex": "^5.0.1" 2026 | }, 2027 | "engines": { 2028 | "node": ">=8" 2029 | } 2030 | }, 2031 | "node_modules/strip-json-comments": { 2032 | "version": "3.1.1", 2033 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2034 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2035 | "dev": true, 2036 | "peer": true, 2037 | "engines": { 2038 | "node": ">=8" 2039 | }, 2040 | "funding": { 2041 | "url": "https://github.com/sponsors/sindresorhus" 2042 | } 2043 | }, 2044 | "node_modules/style-mod": { 2045 | "version": "4.1.2", 2046 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 2047 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", 2048 | "dev": true, 2049 | "peer": true 2050 | }, 2051 | "node_modules/supports-color": { 2052 | "version": "7.2.0", 2053 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2054 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2055 | "dev": true, 2056 | "peer": true, 2057 | "dependencies": { 2058 | "has-flag": "^4.0.0" 2059 | }, 2060 | "engines": { 2061 | "node": ">=8" 2062 | } 2063 | }, 2064 | "node_modules/text-table": { 2065 | "version": "0.2.0", 2066 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 2067 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 2068 | "dev": true, 2069 | "peer": true 2070 | }, 2071 | "node_modules/to-regex-range": { 2072 | "version": "5.0.1", 2073 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2074 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2075 | "dev": true, 2076 | "dependencies": { 2077 | "is-number": "^7.0.0" 2078 | }, 2079 | "engines": { 2080 | "node": ">=8.0" 2081 | } 2082 | }, 2083 | "node_modules/tslib": { 2084 | "version": "2.4.0", 2085 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 2086 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", 2087 | "dev": true 2088 | }, 2089 | "node_modules/tsutils": { 2090 | "version": "3.21.0", 2091 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", 2092 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", 2093 | "dev": true, 2094 | "dependencies": { 2095 | "tslib": "^1.8.1" 2096 | }, 2097 | "engines": { 2098 | "node": ">= 6" 2099 | }, 2100 | "peerDependencies": { 2101 | "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" 2102 | } 2103 | }, 2104 | "node_modules/tsutils/node_modules/tslib": { 2105 | "version": "1.14.1", 2106 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 2107 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 2108 | "dev": true 2109 | }, 2110 | "node_modules/type-check": { 2111 | "version": "0.4.0", 2112 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 2113 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 2114 | "dev": true, 2115 | "peer": true, 2116 | "dependencies": { 2117 | "prelude-ls": "^1.2.1" 2118 | }, 2119 | "engines": { 2120 | "node": ">= 0.8.0" 2121 | } 2122 | }, 2123 | "node_modules/type-fest": { 2124 | "version": "0.20.2", 2125 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 2126 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 2127 | "dev": true, 2128 | "peer": true, 2129 | "engines": { 2130 | "node": ">=10" 2131 | }, 2132 | "funding": { 2133 | "url": "https://github.com/sponsors/sindresorhus" 2134 | } 2135 | }, 2136 | "node_modules/typescript": { 2137 | "version": "4.7.4", 2138 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 2139 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 2140 | "dev": true, 2141 | "bin": { 2142 | "tsc": "bin/tsc", 2143 | "tsserver": "bin/tsserver" 2144 | }, 2145 | "engines": { 2146 | "node": ">=4.2.0" 2147 | } 2148 | }, 2149 | "node_modules/uri-js": { 2150 | "version": "4.4.1", 2151 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2152 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2153 | "dev": true, 2154 | "peer": true, 2155 | "dependencies": { 2156 | "punycode": "^2.1.0" 2157 | } 2158 | }, 2159 | "node_modules/w3c-keyname": { 2160 | "version": "2.2.8", 2161 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 2162 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", 2163 | "dev": true, 2164 | "peer": true 2165 | }, 2166 | "node_modules/which": { 2167 | "version": "2.0.2", 2168 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2169 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2170 | "dev": true, 2171 | "peer": true, 2172 | "dependencies": { 2173 | "isexe": "^2.0.0" 2174 | }, 2175 | "bin": { 2176 | "node-which": "bin/node-which" 2177 | }, 2178 | "engines": { 2179 | "node": ">= 8" 2180 | } 2181 | }, 2182 | "node_modules/word-wrap": { 2183 | "version": "1.2.5", 2184 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 2185 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 2186 | "dev": true, 2187 | "peer": true, 2188 | "engines": { 2189 | "node": ">=0.10.0" 2190 | } 2191 | }, 2192 | "node_modules/wrappy": { 2193 | "version": "1.0.2", 2194 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2195 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2196 | "dev": true, 2197 | "peer": true 2198 | }, 2199 | "node_modules/yocto-queue": { 2200 | "version": "0.1.0", 2201 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2202 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2203 | "dev": true, 2204 | "peer": true, 2205 | "engines": { 2206 | "node": ">=10" 2207 | }, 2208 | "funding": { 2209 | "url": "https://github.com/sponsors/sindresorhus" 2210 | } 2211 | } 2212 | } 2213 | } 2214 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spaced-everything", 3 | "version": "1.0.0", 4 | "description": "Apply spaced repetition algorithms to everything in your vault.", 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": "Zach Mueller", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/frontmatterQueue.ts: -------------------------------------------------------------------------------- 1 | import { TFile, App } from 'obsidian'; 2 | 3 | export class FrontmatterQueue { 4 | private queue: Map> = new Map(); 5 | private app: App; 6 | 7 | constructor(app: App) { 8 | this.app = app; 9 | } 10 | 11 | add(file: TFile, updates: Record) { 12 | const path = file.path; 13 | if (!this.queue.has(path)) { 14 | this.queue.set(path, {}); 15 | } 16 | const fileUpdates = this.queue.get(path)!; 17 | Object.assign(fileUpdates, updates); 18 | } 19 | 20 | async process() { 21 | for (const [path, updates] of this.queue) { 22 | const file = this.app.vault.getAbstractFileByPath(path); 23 | if (file instanceof TFile) { 24 | await this.updateFrontmatter(file, updates); 25 | } 26 | } 27 | this.queue.clear(); 28 | } 29 | 30 | private async updateFrontmatter(file: TFile, updates: Record) { 31 | return new Promise((resolve) => { 32 | this.app.fileManager.processFrontMatter(file, (frontmatter) => { 33 | Object.entries(updates).forEach(([key, value]) => { 34 | if (value === undefined) { 35 | delete frontmatter[key]; 36 | } else { 37 | frontmatter[key] = value; 38 | } 39 | }); 40 | resolve(); 41 | }); 42 | }); 43 | } 44 | } -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { TFile, App } from 'obsidian'; 2 | import { SpacedEverythingPluginSettings } from './settings'; 3 | 4 | export class Logger { 5 | private app: App; 6 | private settings: SpacedEverythingPluginSettings; 7 | private logFilePath: string; 8 | 9 | constructor(app: App, settings: SpacedEverythingPluginSettings) { 10 | this.app = app; 11 | this.settings = settings; 12 | this.logFilePath = settings.logFilePath; 13 | } 14 | 15 | async log(action: string, file: TFile, frontmatter: any = {}, reviewScore?: number, newInterval?: number, newEaseFactor?: number) { 16 | if (this.logFilePath === '') return; // Return early if log file path is not set 17 | 18 | const logData = this.generateLogData(action, file, frontmatter, reviewScore, newInterval, newEaseFactor); 19 | await this.appendToLogFile(logData); 20 | } 21 | 22 | private generateLogData(action: string, file: TFile, frontmatter: any, reviewScore?: number, newInterval?: number, newEaseFactor?: number): string { 23 | const logData: Record = { 24 | action, 25 | timestamp: new Date().toISOString(), 26 | }; 27 | 28 | if (this.settings.logNoteTitle) { 29 | logData.noteTitle = file.basename; 30 | } 31 | 32 | if (this.settings.logFrontMatterProperties.length > 0) { 33 | logData.frontmatter = {}; 34 | const arr = this.settings.logFrontMatterProperties; 35 | if (Array.isArray(arr) && arr.length === 1 && arr[0] === '*') { 36 | logData.frontmatter = frontmatter; 37 | } else { 38 | for (const property of this.settings.logFrontMatterProperties) { 39 | if (frontmatter[property]) { 40 | logData.frontmatter[property] = frontmatter[property]; 41 | } 42 | } 43 | } 44 | } 45 | 46 | if (reviewScore) { 47 | logData.reviewScore = reviewScore; 48 | } 49 | 50 | if (newInterval) { 51 | logData.newInterval = newInterval; 52 | } 53 | 54 | if (newEaseFactor) { 55 | logData.newEaseFactor = newEaseFactor; 56 | } 57 | 58 | return JSON.stringify(logData) + '\n'; 59 | } 60 | 61 | private async appendToLogFile(logData: string) { 62 | try { 63 | let logFile = this.app.vault.getAbstractFileByPath(this.logFilePath); 64 | if (!logFile) { 65 | console.log(`Log file ${this.logFilePath} does not exist. Creating a new file.`); 66 | await this.app.vault.create(this.logFilePath, ''); 67 | logFile = this.app.vault.getAbstractFileByPath(this.logFilePath); 68 | } 69 | 70 | if (logFile) { 71 | const fileObj = this.app.vault.getFileByPath(logFile.path); 72 | if (fileObj) { 73 | await this.app.vault.append(fileObj, logData); 74 | } else { 75 | console.error(`Error: Unable to access log file ${this.logFilePath}`); 76 | } 77 | } else { 78 | console.error(`Error: Unable to create or access log file ${this.logFilePath}`); 79 | } 80 | } catch (error) { 81 | console.error(`Error writing to log file: ${error}`); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { App, Editor, MarkdownView, TFile, Notice, Plugin, Modal } from 'obsidian'; 2 | import { Context, ReviewOption, SpacingMethod } from './types'; 3 | import { Logger } from './logger'; 4 | import { SpacedEverythingPluginSettings, SpacedEverythingSettingTab } from './settings'; 5 | import { Suggester, suggester } from './suggester'; 6 | import { FrontmatterQueue } from './frontmatterQueue'; 7 | 8 | const DEFAULT_SETTINGS: SpacedEverythingPluginSettings = { 9 | logFilePath: "", // defaults to no logging 10 | logOnboardAction: true, 11 | logRemoveAction: true, 12 | logNoteTitle: true, 13 | logFrontMatterProperties: [], 14 | contexts: [], 15 | spacingMethods: [ 16 | { 17 | name: "SuperMemo 2.0 (Simplified)", 18 | spacingAlgorithm: "SuperMemo2.0", 19 | customScriptFileName: "", 20 | reviewOptions: [ 21 | { name: 'Fruitful', score: 1 }, 22 | { name: 'Ignore', score: 3 }, 23 | { name: 'Unfruitful', score: 5 }, 24 | ], 25 | defaultInterval: 1, 26 | defaultEaseFactor: 2.5, 27 | }, 28 | ], 29 | capturedThoughtTitleTemplate: "Inbox {{unixtime}}", 30 | capturedThoughtDirectory: "", 31 | capturedThoughtNoteTemplate: "## Captured thought\n{{thought}}", 32 | includeShortThoughtInAlias: true, 33 | shortCapturedThoughtThreshold: 200, 34 | openCapturedThoughtInNewTab: false, 35 | onboardingExcludedFolders: [] 36 | } 37 | 38 | export default class SpacedEverythingPlugin extends Plugin { 39 | settings: SpacedEverythingPluginSettings; 40 | logger: Logger; 41 | private frontmatterQueue: FrontmatterQueue; 42 | 43 | async onload() { 44 | await this.loadSettings(); 45 | this.logger = new Logger(this.app, this.settings); 46 | this.frontmatterQueue = new FrontmatterQueue(this.app); 47 | 48 | // This adds a settings tab so the user can configure various aspects of the plugin 49 | this.addSettingTab(new SpacedEverythingSettingTab(this.app, this)); 50 | 51 | // Command to log the review outcome 52 | this.addCommand({ 53 | id: 'log-review-outcome', 54 | name: 'Log review outcome', 55 | editorCallback: (editor: Editor, view: MarkdownView) => { 56 | this.logReviewOutcome(editor, view); 57 | } 58 | }); 59 | 60 | // Command to open the next review item 61 | this.addCommand({ 62 | id: 'open-next-review-item', 63 | name: 'Open next review item', 64 | editorCallback: (editor: Editor, view: MarkdownView) => { 65 | this.openNextReviewItem(editor, view) 66 | } 67 | }); 68 | 69 | // Command to toggle contexts for a note 70 | this.addCommand({ 71 | id: 'toggle-note-contexts', 72 | name: 'Toggle note contexts', 73 | editorCallback: (editor: Editor, view: MarkdownView) => { 74 | this.toggleNoteContextsWrapper(editor, view) 75 | } 76 | }); 77 | 78 | // Command to capture thoughts 79 | this.addCommand({ 80 | id: 'capture-thought', 81 | name: 'Capture thought', 82 | callback: () => { 83 | this.captureThought() 84 | } 85 | }); 86 | 87 | // Command to update the spacing method for a note 88 | this.addCommand({ 89 | id: 'update-spacing-method', 90 | name: 'Update spacing method', 91 | editorCallback: (editor: Editor, view: MarkdownView) => { 92 | this.updateSpacingMethod(editor, view); 93 | } 94 | }); 95 | } 96 | 97 | // Helper method to add updates to the queue 98 | queueFrontmatterUpdate(file: TFile, updates: Record) { 99 | this.frontmatterQueue.add(file, updates); 100 | } 101 | 102 | // Helper method to process all queued updates 103 | async processFrontmatterQueue() { 104 | await this.frontmatterQueue.process(); 105 | } 106 | 107 | async captureThought() { 108 | // craft modal for collecting user input 109 | const modal = new Modal(this.app); 110 | modal.contentEl.createEl("h3", { text: "Capture thought" }); 111 | 112 | // Create a container element to hold the variable names and commas 113 | const variableNamesContainer = modal.contentEl.createEl("span"); 114 | 115 | // Create elements for each variable name wrapped in tags 116 | const variableNameElements = [ 117 | variableNamesContainer.createEl("code", { text: "{{unixtime}}" }), 118 | variableNamesContainer.createEl("span", { text: ", " }), 119 | variableNamesContainer.createEl("code", { text: "{{date}}" }), 120 | variableNamesContainer.createEl("span", { text: ", and " }), 121 | variableNamesContainer.createEl("code", { text: "{{time}}" }), 122 | ]; 123 | 124 | variableNamesContainer.createEl("span", { text: `Write out your thought here. You have access to the following variables: ` }); 125 | variableNameElements.forEach(el => variableNamesContainer.appendChild(el)); 126 | 127 | modal.contentEl.createEl("br", {}); 128 | modal.contentEl.createEl("br", {}); 129 | 130 | const textArea = modal.contentEl.createEl("textarea"); 131 | textArea.style.height = "100%"; 132 | textArea.style.width = "100%"; 133 | 134 | // add usage footnotes 135 | modal.contentEl.createEl("small", { text: "Shift + Enter for new lines." }); 136 | modal.contentEl.createEl("br", {}); 137 | modal.contentEl.createEl("small", { text: "Enter to submit." }); 138 | 139 | const handleSubmit = async (thought: string | null): Promise => { 140 | if (thought === null || thought === "") { 141 | new Notice("Cancelled by the user"); 142 | return; 143 | } 144 | 145 | modal.close(); 146 | 147 | const now = new Date(); 148 | thought = this.processCapturedThoughtNewNoteContents(thought, now); 149 | const newNoteFile = await this.createNewNoteFile(thought, now); 150 | await this.openNewNote(newNoteFile); 151 | let aliases: string[] = []; 152 | await this.onboardNoteToSpacedEverything(newNoteFile, {}); 153 | 154 | await this.frontmatterQueue.add(newNoteFile, { 155 | "se-capture-time": Math.floor(now.getTime() / 1000).toString(), 156 | "aliases": this.settings.includeShortThoughtInAlias && thought 157 | && thought.length <= this.settings.shortCapturedThoughtThreshold ? [thought] : undefined 158 | }); 159 | await this.processFrontmatterQueue(); 160 | }; 161 | 162 | textArea.addEventListener("keydown", (event) => { 163 | if (event.key === "Enter" && !event.shiftKey) { 164 | event.preventDefault(); 165 | handleSubmit(textArea.value || null); 166 | } 167 | }); 168 | 169 | modal.open(); 170 | } 171 | 172 | private processCapturedThoughtNewNoteContents(thought: string, now: Date): string { 173 | thought = thought.trim(); 174 | thought = this.replaceCapturedThoughtVariables(thought, now); 175 | 176 | return thought; 177 | } 178 | 179 | private replaceCapturedThoughtVariables(content: string, now: Date): string { 180 | const unixTime = Math.floor(now.getTime() / 1000).toString(); 181 | const dateString = now.toISOString().split("T")[0]; 182 | const timeString = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); 183 | content = content.replace(/{{unixtime}}/g, unixTime.toString()) 184 | .replace(/{{date}}/g, dateString) 185 | .replace(/{{time}}/g, timeString); 186 | 187 | return content; 188 | } 189 | 190 | private async generateUniqueFilePath(filename: string, extension: string = '.md'): Promise { 191 | let uniqueFilename = `${filename}${extension}`; 192 | 193 | // return initial filename, if not already taken 194 | if (!(await this.app.vault.adapter.exists(uniqueFilename))) { 195 | return uniqueFilename; 196 | } 197 | 198 | // iteratively add counters until find available file path 199 | let counter = 1; 200 | while (true) { 201 | const filePath = `${filename}-${counter}${extension}`; 202 | const fileExists = await this.app.vault.adapter.exists(filePath); 203 | 204 | if (!fileExists) { 205 | return filePath; 206 | } 207 | 208 | uniqueFilename = `${filename} ${counter}`; 209 | counter++; 210 | } 211 | } 212 | 213 | async createNewNoteFile(thought: string, now: Date): Promise { 214 | const noteTitle = this.replaceCapturedThoughtVariables(this.settings.capturedThoughtTitleTemplate, now); 215 | const noteDirectory = this.settings.capturedThoughtDirectory || ""; 216 | const newNotePath = await this.generateUniqueFilePath(`${noteDirectory}/${noteTitle}`); 217 | 218 | const templateContent = this.settings.capturedThoughtNoteTemplate; 219 | const hasThoughtVariable = templateContent.includes("{{thought}}"); 220 | 221 | let newNoteContent: string; 222 | 223 | if (hasThoughtVariable) { 224 | newNoteContent = templateContent.replace(/{{thought}}/g, thought); 225 | } else { 226 | newNoteContent = `${templateContent}\n\n---\n\n## Thought\n${thought}\n\n`; 227 | newNoteContent += 'Note: Your `Capture thought -> New note template` setting does not contain the `{{thought}}` variable, thus your captured thought was appended below your existing template. Please visit your Spaced Everything plugin settings to update the template and prevent this message from arising in the future.'; 228 | } 229 | 230 | const newNoteFile = await this.app.vault.create(newNotePath, newNoteContent); 231 | 232 | return newNoteFile; 233 | } 234 | 235 | async openNewNote(newNoteFile: TFile) { 236 | const { openCapturedThoughtInNewTab } = this.settings; 237 | 238 | if (!openCapturedThoughtInNewTab) { 239 | const activeLeaf = this.app.workspace.activeLeaf; 240 | if (activeLeaf) { 241 | await activeLeaf.openFile(newNoteFile); 242 | return; 243 | } else { 244 | new Notice('No active editor, opened note in new tab'); 245 | } 246 | } 247 | await this.app.workspace.openLinkText(newNoteFile.path, newNoteFile.path, true, { active: true }); 248 | } 249 | 250 | async toggleNoteContextsWrapper(editor?: Editor, view?: MarkdownView) { 251 | const activeFile = this.app.workspace.getActiveFile(); 252 | if (!activeFile) { 253 | new Notice("No active file to toggle contexts."); 254 | return; 255 | } 256 | 257 | await this.toggleNoteContexts(activeFile); 258 | await this.processFrontmatterQueue(); 259 | } 260 | 261 | async toggleNoteContexts(file: TFile) { 262 | if (this.settings.contexts.length === 0) { 263 | // no contexts to toggle 264 | new Notice('Spaced Everything: No contexts defined'); 265 | return; 266 | } 267 | 268 | const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter; 269 | const currentContexts = frontmatter && frontmatter["se-contexts"] ? frontmatter["se-contexts"] : []; 270 | 271 | const choices = this.settings.contexts.map(context => { 272 | const isSelected = currentContexts.includes(context.name); 273 | return `${isSelected ? '☑' : '☐'} ${context.name}`; 274 | }); 275 | 276 | const selectedChoice = await suggester(choices, "Select contexts for this note:"); 277 | 278 | if (selectedChoice) { 279 | const selectedContext = selectedChoice.replace(/(?:☑|☐)\s/, ''); 280 | const updatedContexts = currentContexts.filter((context: string) => context !== selectedContext); 281 | 282 | if (!currentContexts.includes(selectedContext)) { 283 | updatedContexts.push(selectedContext); 284 | } 285 | 286 | await this.frontmatterQueue.add(file, { 287 | "se-contexts": updatedContexts 288 | }); 289 | } 290 | } 291 | 292 | async logReviewOutcome(editor: Editor, view: MarkdownView) { 293 | const activeFile = this.app.workspace.getActiveFile(); 294 | if (!activeFile) { 295 | new Notice('No active file to review.'); 296 | return; 297 | } 298 | 299 | // capture current timestamp 300 | const now = new Date().toISOString().split('.')[0]; 301 | 302 | // check whether note already onboarded to Spaced Everything 303 | const frontmatter = this.app.metadataCache.getFileCache(activeFile)?.frontmatter; 304 | const noteOnboarded = await this.isNoteOnboarded(activeFile, frontmatter); 305 | 306 | if (noteOnboarded) { 307 | const activeSpacingMethod = await this.getActiveSpacingMethod(activeFile, frontmatter); 308 | if (!activeSpacingMethod) { 309 | new Notice('Error: No active spacing method found for this note.'); 310 | return; 311 | } 312 | 313 | const reviewOptions = [...activeSpacingMethod.reviewOptions.map((option) => option.name), 'Remove']; 314 | const reviewResult = await suggester(reviewOptions, 'Select review outcome:'); 315 | 316 | if (!reviewResult) { 317 | // exit if user presses Esc on the suggester 318 | new Notice('Spaced Everything review cancelled by user'); 319 | return; 320 | } 321 | 322 | if (reviewResult === 'Remove') { 323 | await this.removeNoteFromSpacedEverything(activeFile, frontmatter); 324 | } else { 325 | const selectedOption = activeSpacingMethod.reviewOptions.find((option) => option.name === reviewResult); 326 | 327 | // check whether valid option selected 328 | if (!selectedOption) { 329 | new Notice('Error: Review option not found in settings. Please check your settings.'); 330 | return; 331 | } 332 | 333 | // check whether valid review quality score set for 334 | if (selectedOption.score === undefined || selectedOption.score === null) { 335 | new Notice(`Error: Review option score is not set in settings. Please set a score for the selected review option: ${selectedOption.name}`); 336 | return; 337 | } 338 | 339 | // perform action to update the interval 340 | const { newInterval, newEaseFactor } = await this.updateInterval(activeFile, frontmatter, selectedOption.score, now, activeSpacingMethod); 341 | } 342 | } else { 343 | await this.onboardNoteToSpacedEverything(activeFile, frontmatter); 344 | } 345 | 346 | // Process all queued frontmatter updates 347 | await this.processFrontmatterQueue(); 348 | } 349 | 350 | private filterNotesByContext(files: TFile[]): TFile[] { 351 | const activeContexts = this.settings.contexts.filter(context => context.isActive).map(context => context.name); 352 | 353 | // Case 1: No defined at all contexts 354 | if (this.settings.contexts.length === 0) { 355 | // If no contexts are defined or all are inactive, return all files 356 | return files; 357 | } 358 | 359 | // Case 2: All contexts are inactive 360 | if (this.settings.contexts.length > 0 && activeContexts.length === 0) { 361 | // If all contexts are defined but none are active, return no files 362 | new Notice('Spaced everything: No active contexts'); 363 | return []; 364 | } 365 | 366 | return files.filter(file => { 367 | const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter; 368 | const noteContexts = frontmatter?.['se-contexts'] || []; 369 | 370 | // Case 3: If noteContexts is empty, always include 371 | if (noteContexts.length === 0) { 372 | return true; 373 | } 374 | 375 | // Case 4: Some contexts are active 376 | const hasActiveContext = noteContexts.some((noteContext: string) => activeContexts.includes(noteContext)); 377 | 378 | // If any of the note's contexts match the active contexts, include it 379 | return hasActiveContext; 380 | }); 381 | } 382 | 383 | // Function to open the next item in the review queue 384 | async openNextReviewItem(editor: Editor, view: MarkdownView) { 385 | const vault = this.app.vault; 386 | const files = vault.getMarkdownFiles(); 387 | 388 | // Filter notes based on the review criteria 389 | const filteredPages = this.filterNotesByContext(files) 390 | .filter(file => { 391 | const metadata = this.app.metadataCache.getFileCache(file)?.frontmatter; 392 | if (!metadata || metadata["se-interval"] === undefined) return false; 393 | 394 | const currentTime = Date.now(); 395 | const timeDiff = metadata["se-interval"] * 24 * 60 * 60 * 1000; 396 | const lastReviewed = metadata["se-last-reviewed"] 397 | ? new Date(metadata["se-last-reviewed"]).getTime() 398 | : 0; 399 | 400 | const isDue = currentTime > (lastReviewed + timeDiff); 401 | 402 | return isDue; 403 | }) 404 | .sort((a, b) => { 405 | const aMetadata = this.app.metadataCache.getFileCache(a)?.frontmatter; 406 | const bMetadata = this.app.metadataCache.getFileCache(b)?.frontmatter; 407 | 408 | const aLastReviewed = aMetadata?.["se-last-reviewed"] 409 | ? new Date(aMetadata["se-last-reviewed"]).getTime() 410 | : 0; 411 | const bLastReviewed = bMetadata?.["se-last-reviewed"] 412 | ? new Date(bMetadata["se-last-reviewed"]).getTime() 413 | : 0; 414 | 415 | const aInterval = aMetadata?.["se-interval"] * 24 * 60 * 60 * 1000; 416 | const bInterval = bMetadata?.["se-interval"] * 24 * 60 * 60 * 1000; 417 | 418 | const aDueTime = aLastReviewed + (aInterval || 0); 419 | const bDueTime = bLastReviewed + (bInterval || 0); 420 | 421 | return aDueTime - bDueTime; 422 | }); 423 | 424 | // Open the first due note in the queue 425 | if (filteredPages.length === 0) { 426 | new Notice("No notes to review, enjoy some fresh air!"); 427 | } else { 428 | const file = filteredPages[0]; 429 | const leaf = this.app.workspace.getLeaf(false); // false = open in the current tab 430 | leaf.openFile(file); 431 | } 432 | } 433 | 434 | async selectContext(validContexts: string[]): Promise { 435 | const promptText = "Select a context for this note:"; 436 | return suggester(validContexts, promptText); 437 | } 438 | 439 | async isNoteOnboarded(file: TFile, frontmatter: any): Promise { 440 | return Object.keys(frontmatter || {}).includes('se-interval'); 441 | } 442 | 443 | async onboardNoteToSpacedEverything(file: TFile, frontmatter: any): Promise { 444 | const now = new Date().toISOString().split('.')[0]; 445 | 446 | // prompt user to select contexts 447 | await this.toggleNoteContexts(file); 448 | 449 | let activeSpacingMethod: SpacingMethod; 450 | const spacingMethods = this.settings.spacingMethods; 451 | 452 | // If there's only one spacing method, use that 453 | if (spacingMethods.length === 1) { 454 | activeSpacingMethod = spacingMethods[0]; 455 | } else { 456 | // Prompt the user to select a spacing method 457 | const spacingMethodNames = spacingMethods.map(method => method.name); 458 | const selectedMethod = await suggester(spacingMethodNames, 'Select a spacing method for this note:'); 459 | 460 | if (selectedMethod) { 461 | activeSpacingMethod = spacingMethods.find(method => method.name === selectedMethod)!; 462 | } else { 463 | new Notice('Onboarding cancelled by user.'); 464 | return false; 465 | } 466 | } 467 | 468 | // add standard Spaced Everything frontmatter properties and values 469 | await this.queueFrontmatterUpdate(file, { 470 | 'se-interval': activeSpacingMethod.defaultInterval, 471 | 'se-last-reviewed': now, 472 | 'se-ease': activeSpacingMethod.defaultEaseFactor, 473 | 'se-method': activeSpacingMethod.name 474 | }); 475 | 476 | if (this.settings.logOnboardAction) { 477 | this.logger.log('onboarded', file, frontmatter); 478 | } 479 | 480 | new Notice(`Onboarded note to Spaced Everything: ${file.basename}`); 481 | return true; 482 | } 483 | 484 | async removeNoteFromSpacedEverything(file: TFile, frontmatter: any): Promise { 485 | await this.queueFrontmatterUpdate(file, { 486 | 'se-interval': undefined, 487 | 'se-ease': undefined, 488 | 'se-last-reviewed': undefined, 489 | 'se-contexts': undefined 490 | }); 491 | new Notice(`Removed note from Spaced Everything: ${file.basename}`); 492 | if (this.settings.logRemoveAction) { 493 | this.logger.log('removed', file, frontmatter); 494 | } 495 | } 496 | 497 | async updateInterval(file: TFile, frontmatter: any, reviewScore: number, now: string, activeSpacingMethod: SpacingMethod): Promise<{ newInterval: number; newEaseFactor: number; }> { 498 | let prevInterval = 1; 499 | let prevEaseFactor = 2.5; 500 | let newInterval = 0; 501 | let newEaseFactor = 0; 502 | 503 | await this.app.fileManager.processFrontMatter(file, (frontmatter) => { 504 | // Get the previous interval and ease factor from the frontmatter 505 | prevInterval = Number(frontmatter['se-interval'] || activeSpacingMethod?.defaultInterval || 1); 506 | prevEaseFactor = Number(frontmatter['se-ease'] || activeSpacingMethod?.defaultEaseFactor || 2.5); 507 | 508 | // Calculate the new ease factor based on the review score 509 | newEaseFactor = prevEaseFactor + (0.1 - (5 - reviewScore) * (0.08 + (5 - reviewScore) * 0.02)); 510 | newEaseFactor = Math.max(1.3, parseFloat(newEaseFactor.toFixed(4))); 511 | 512 | // Calculate the new interval using the SuperMemo 2.0 formula 513 | newInterval = Math.max(1, prevInterval * newEaseFactor); 514 | newInterval = parseFloat(newInterval.toFixed(4)); 515 | 516 | // Override interval to 1 day if the review score less than 3 517 | if (reviewScore < 3) { 518 | newInterval = 1; 519 | } 520 | 521 | if (this.settings.logFilePath) { 522 | this.logger.log('review', file, frontmatter, reviewScore, newInterval, newEaseFactor); 523 | } 524 | }); 525 | 526 | // Update the frontmatter with the new interval and ease factor 527 | await this.queueFrontmatterUpdate(file, { 528 | 'se-interval': newInterval, 529 | 'se-ease': newEaseFactor, 530 | 'se-last-reviewed': now 531 | }); 532 | 533 | // Notify the user of the interval change 534 | new Notice(`Interval updated from ${prevInterval} to ${newInterval}`); 535 | 536 | return { newInterval, newEaseFactor }; 537 | } 538 | 539 | async getActiveSpacingMethod(file: TFile, frontmatter: any): Promise { 540 | const seMethod = frontmatter?.['se-method']; 541 | 542 | // If se-method is set, try to find the corresponding spacing method 543 | let activeSpacingMethod = this.settings.spacingMethods.find(method => method.name === seMethod); 544 | 545 | // If se-method doesn't match any existing spacing method, proceed with fallback logic 546 | if (!activeSpacingMethod) { 547 | const noteContexts = frontmatter?.['se-contexts'] || []; 548 | 549 | // If no contexts are defined for the note, use the first spacing method 550 | if (noteContexts.length === 0) { 551 | activeSpacingMethod = this.settings.spacingMethods[0]; 552 | new Notice(`Set 'se-method' to '${activeSpacingMethod.name}' for this note (no context defined).`); 553 | await this.queueFrontmatterUpdate(file, {'se-method': activeSpacingMethod.name}); 554 | return activeSpacingMethod; 555 | } 556 | 557 | // Get the first context from the list 558 | const firstContext = noteContexts[0]; 559 | 560 | // Find the spacing method associated with the first context 561 | const contextSpacingMethod = this.settings.contexts.find( 562 | context => context.name === firstContext 563 | )?.spacingMethodName; 564 | 565 | if (contextSpacingMethod) { 566 | activeSpacingMethod = this.settings.spacingMethods.find(method => method.name === contextSpacingMethod); 567 | if (activeSpacingMethod) { 568 | new Notice(`Set 'se-method' to '${activeSpacingMethod.name}' for this note (based on '${firstContext}' context).`); 569 | await this.queueFrontmatterUpdate(file, {'se-method': activeSpacingMethod.name}); 570 | return activeSpacingMethod; 571 | } 572 | } 573 | 574 | // If no context is mapped to a spacing method, use the first spacing method 575 | activeSpacingMethod = this.settings.spacingMethods[0]; 576 | new Notice(`Set 'se-method' to '${activeSpacingMethod.name}' for this note (no context mapped to a spacing method).`); 577 | await this.queueFrontmatterUpdate(file, {'se-method': activeSpacingMethod.name}); 578 | return activeSpacingMethod; 579 | } 580 | 581 | // If se-method matches an existing spacing method, return it 582 | return activeSpacingMethod; 583 | } 584 | 585 | async updateSpacingMethod(editor: Editor, view: MarkdownView) { 586 | const activeFile = this.app.workspace.getActiveFile(); 587 | if (!activeFile) { 588 | new Notice('No active file to update spacing method.'); 589 | return; 590 | } 591 | 592 | const spacingMethodNames = this.settings.spacingMethods.map(method => method.name); 593 | const selectedMethod = await suggester(spacingMethodNames, 'Select a spacing method:'); 594 | 595 | if (selectedMethod) { 596 | await this.queueFrontmatterUpdate(activeFile, { 597 | 'se-method': selectedMethod 598 | }); 599 | 600 | new Notice(`Updated spacing method to '${selectedMethod}' for ${activeFile.basename}`); 601 | } else { 602 | new Notice('Spacing method update cancelled by user.'); 603 | } 604 | } 605 | 606 | onunload() { 607 | 608 | } 609 | 610 | async loadSettings() { 611 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 612 | } 613 | 614 | async saveSettings() { 615 | await this.saveData(this.settings); 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, Notice, PluginSettingTab, Setting, normalizePath, Modal, TAbstractFile, TFile, TFolder } from 'obsidian'; 2 | import { Context, ReviewOption, SpacingMethod } from './types'; 3 | import SpacedEverythingPlugin from './main'; 4 | 5 | interface SpacedEverythingPluginSettings { 6 | logFilePath: string; 7 | logOnboardAction: boolean; 8 | logRemoveAction: boolean; 9 | logNoteTitle: boolean; 10 | logFrontMatterProperties: string[]; 11 | contexts: Context[]; 12 | spacingMethods: SpacingMethod[]; 13 | capturedThoughtTitleTemplate: string; 14 | capturedThoughtDirectory: string; 15 | capturedThoughtNoteTemplate: string; 16 | includeShortThoughtInAlias: boolean; 17 | shortCapturedThoughtThreshold: number; 18 | openCapturedThoughtInNewTab: boolean; 19 | onboardingExcludedFolders: string[]; 20 | } 21 | 22 | export type { SpacedEverythingPluginSettings }; 23 | 24 | export class SpacedEverythingSettingTab extends PluginSettingTab { 25 | plugin: SpacedEverythingPlugin; 26 | 27 | constructor(app: App, plugin: SpacedEverythingPlugin) { 28 | super(app, plugin); 29 | this.plugin = plugin; 30 | } 31 | 32 | display(): void { 33 | const {containerEl} = this; 34 | 35 | containerEl.empty(); 36 | 37 | new Setting(containerEl).setName('Spacing methods').setHeading(); 38 | const spacingMethodsSettingDiv = containerEl.createDiv(); 39 | const spacingMethodsDiv = containerEl.createDiv(); 40 | const addSpacingMethodDiv = containerEl.createDiv(); 41 | 42 | // construct description to include link to docs 43 | const desc = document.createDocumentFragment(); 44 | desc.append( 45 | 'Define and manage the spacing methods you want to use for your spaced everything practice. ', 46 | 'You can create multiple spacing methods and map them to different contexts. ', 47 | 'Check the ', 48 | desc.createEl('a', { 49 | href: 'https://github.com/zachmueller/spaced-everything/blob/main/README.md', 50 | text: 'documentation', 51 | }), 52 | ' for more information.' 53 | ); 54 | 55 | new Setting(spacingMethodsSettingDiv) 56 | .setDesc(desc); 57 | 58 | this.plugin.settings.spacingMethods.forEach((spacingMethod, index) => { 59 | this.renderSpacingMethodSetting(spacingMethodsDiv, spacingMethod, index); 60 | }); 61 | 62 | new Setting(addSpacingMethodDiv) 63 | .addButton((button) => 64 | button 65 | .setButtonText('Add spacing method') 66 | .setTooltip('Add a new spacing method') 67 | .onClick(async () => { 68 | const newSpacingMethod: SpacingMethod = { 69 | name: `Spacing method #${this.plugin.settings.spacingMethods.length + 1}`, 70 | spacingAlgorithm: 'SuperMemo2.0', 71 | customScriptFileName: '', 72 | reviewOptions: [], 73 | defaultInterval: 1, 74 | defaultEaseFactor: 2.5, 75 | }; 76 | this.plugin.settings.spacingMethods.push(newSpacingMethod); 77 | await this.plugin.saveSettings(); 78 | this.renderSpacingMethodSetting(spacingMethodsDiv, newSpacingMethod, this.plugin.settings.spacingMethods.length - 1); 79 | }) 80 | ); 81 | 82 | // review contexts 83 | new Setting(containerEl).setName('Contexts').setHeading(); 84 | const contextsSettingDiv = containerEl.createDiv(); 85 | const addContextDiv = containerEl.createDiv(); 86 | const contextsDiv = containerEl.createDiv(); 87 | 88 | // Add button to create a new context 89 | new Setting(contextsSettingDiv) 90 | .setDesc('Define and manage the contexts you want to use for categorizing notes in your spaced everything practice. You can toggle the active state of each context to control which notes will be included in the review queue. Note: leaving this empty will ignore the use of contexts in the review system (i.e., all notes onboarded to Spaced Everything are in scope for reviews).') 91 | 92 | // Render existing contexts 93 | this.plugin.settings.contexts.forEach((context, index) => { 94 | this.renderContextSetting(contextsDiv, context, index); 95 | }); 96 | 97 | new Setting(addContextDiv) 98 | // TODO::make this render in a better location to make it 99 | // more clearly distinct from review options expansion:: 100 | .addButton((button) => 101 | button 102 | .setButtonText('+') 103 | .setIcon('plus') 104 | .setTooltip('Add new context') 105 | .onClick(async () => { 106 | const newContext: Context = { 107 | name: '', 108 | isActive: false, 109 | }; 110 | this.plugin.settings.contexts.push(newContext); 111 | await this.plugin.saveSettings(); 112 | this.renderContextSetting(contextsDiv, newContext, this.plugin.settings.contexts.length - 1); 113 | }) 114 | ); 115 | 116 | // logging 117 | new Setting(containerEl).setName('Logging').setHeading(); 118 | 119 | new Setting(containerEl) 120 | .setName('Log spaced everything practice activity') 121 | .setDesc('Choose the file path where Spaced Everything logs are stored. Leave blank to not capture logs. Note: output data format is JSONL (i.e., `.jsonl` filename extension recommended).') 122 | .addText(text => text 123 | .setValue(this.plugin.settings.logFilePath) 124 | .onChange(async (value) => { 125 | const normalizedPath = normalizePath(value); 126 | this.plugin.settings.logFilePath = normalizedPath; 127 | await this.plugin.saveSettings(); 128 | }) 129 | ); 130 | 131 | new Setting(containerEl) 132 | .setName('Log action: note onboarded to Spaced Everything') 133 | .setDesc('Whether to log the action of onboarding a new note to Spaced Everything') 134 | .addToggle(toggle => toggle 135 | .setValue(this.plugin.settings.logOnboardAction) 136 | .onChange(async (value) => { 137 | this.plugin.settings.logOnboardAction = value; 138 | await this.plugin.saveSettings(); 139 | })); 140 | 141 | new Setting(containerEl) 142 | .setName('Log action: note removed from Spaced Everything') 143 | .setDesc('Whether to log the action of removing a note from Spaced Everything') 144 | .addToggle(toggle => toggle 145 | .setValue(this.plugin.settings.logRemoveAction) 146 | .onChange(async (value) => { 147 | this.plugin.settings.logRemoveAction = value; 148 | await this.plugin.saveSettings(); 149 | })); 150 | 151 | new Setting(containerEl) 152 | .setName('Log note title') 153 | .setDesc('Whether to include the note title in the log') 154 | .addToggle(toggle => toggle 155 | .setValue(this.plugin.settings.logNoteTitle) 156 | .onChange(async (value) => { 157 | this.plugin.settings.logNoteTitle = value; 158 | await this.plugin.saveSettings(); 159 | })); 160 | 161 | new Setting(containerEl) 162 | .setName('Log frontmatter properties') 163 | .setDesc('Provide a list (one per line) of frontmatter properties you would like to include in the Spaced Everything logs. Input just an asterisk (*) to include all frontmatter properties.') 164 | .addTextArea(textArea => { 165 | const properties = this.plugin.settings.logFrontMatterProperties ?? []; 166 | const propertyStr = properties.join('\n'); 167 | textArea 168 | .setPlaceholder('Enter frontmatter properties, one per line') 169 | .setValue(propertyStr) 170 | .onChange(async (value) => { 171 | if (value.trim() === '*') { 172 | this.plugin.settings.logFrontMatterProperties = ['*']; 173 | } else { 174 | const properties = value.trim().split('\n').filter(property => property.trim() !== ''); 175 | this.plugin.settings.logFrontMatterProperties = properties; 176 | } 177 | await this.plugin.saveSettings(); 178 | }); 179 | }); 180 | 181 | // Capture thoughts settings 182 | new Setting(containerEl).setName('Capture thought').setHeading(); 183 | 184 | new Setting(containerEl) 185 | .setName('Note title template') 186 | .setDesc('Template for generating the title of the new note') 187 | .addText((text) => 188 | text 189 | .setPlaceholder('Enter your template here') 190 | .setValue(this.plugin.settings.capturedThoughtTitleTemplate) 191 | .onChange(async (value) => { 192 | this.plugin.settings.capturedThoughtTitleTemplate = value; 193 | await this.plugin.saveSettings(); 194 | }) 195 | ); 196 | 197 | new Setting(containerEl) 198 | .setName('Note directory') 199 | .setDesc('Directory where the new note should be created (leave empty for default directory)') 200 | .addText((text) => 201 | text 202 | .setPlaceholder('Enter your directory path here') 203 | .setValue(this.plugin.settings.capturedThoughtDirectory) 204 | .onChange(async (value) => { 205 | const trimmedValue = value.replace(/\/+$/, ''); // Remove trailing slashes 206 | this.plugin.settings.capturedThoughtDirectory = trimmedValue; 207 | await this.plugin.saveSettings(); 208 | }) 209 | ); 210 | 211 | new Setting(containerEl) 212 | .setName('New note template') 213 | .setDesc('Template for the initial content of the new note') 214 | .addTextArea((ta) => 215 | ta 216 | .setPlaceholder('Enter your template here') 217 | .setValue(this.plugin.settings.capturedThoughtNoteTemplate) 218 | .onChange(async (value) => { 219 | this.plugin.settings.capturedThoughtNoteTemplate = value; 220 | await this.plugin.saveSettings(); 221 | }) 222 | ); 223 | 224 | new Setting(containerEl) 225 | .setName('Include short thought in alias') 226 | .setDesc('Include the thought as an alias in the frontmatter if it\'s shorter than the threshold') 227 | .addToggle((toggle) => 228 | toggle 229 | .setValue(this.plugin.settings.includeShortThoughtInAlias) 230 | .onChange(async (value) => { 231 | this.plugin.settings.includeShortThoughtInAlias = value; 232 | await this.plugin.saveSettings(); 233 | }) 234 | ); 235 | 236 | new Setting(containerEl) 237 | .setName('Short thought threshold') 238 | .setDesc('Maximum length of the thought (in characters) for it to be included as an alias') 239 | .addText((text) => 240 | text 241 | .setPlaceholder('Enter your threshold here') 242 | .setValue(this.plugin.settings.shortCapturedThoughtThreshold.toString()) 243 | .onChange(async (value) => { 244 | const numericValue = parseInt(value, 10); 245 | if (!isNaN(numericValue) && numericValue >= 0) { 246 | this.plugin.settings.shortCapturedThoughtThreshold = numericValue; 247 | await this.plugin.saveSettings(); 248 | } 249 | }) 250 | ); 251 | 252 | new Setting(containerEl) 253 | .setName('Open in new tab') 254 | .setDesc('Open the newly created note in a new tab. If turned off, captured thought note opens in currently active tab.') 255 | .addToggle((toggle) => 256 | toggle 257 | .setValue(this.plugin.settings.openCapturedThoughtInNewTab) 258 | .onChange(async (value) => { 259 | this.plugin.settings.openCapturedThoughtInNewTab = value; 260 | await this.plugin.saveSettings(); 261 | }) 262 | ); 263 | 264 | 265 | // Onboard all notes 266 | new Setting(containerEl).setName('Onboard all notes (beta)') 267 | .setHeading() 268 | .setDesc('This provides an optional means of onboarding every note in your vault to the Spaced Everything system. Importantly, the plugin uses frontmatter properties on notes to track relevant metadata to perform the spacing algorithm actions. So it is recommended to use the "Excluded folders" setting below to filter out subsets of notes that you wish to avoid onboarding. Performing this action will not change any existing Spaced Everything frontmatter if you already have some notes oboarded.\n\nThis is still a beta feature. Currently, it asusmes to only apply the settings from the first Spacing Method (defined above) and assumes to not set any context for notes onboarded in this manner.'); 269 | 270 | new Setting(containerEl) 271 | .setName('Excluded folders') 272 | .setDesc('Enter the paths of any folders you want to exclude from the onboarding process (one per line). Consider adding folders that contain things like templates or scripts that may not work if frontmatter properties are added to them.') 273 | .addTextArea((textArea) => { 274 | textArea.setValue(this.plugin.settings.onboardingExcludedFolders.join('\n')).onChange(async (value) => { 275 | this.plugin.settings.onboardingExcludedFolders = value.trim().split('\n').filter((v) => v); 276 | await this.plugin.saveSettings(); 277 | }); 278 | }); 279 | 280 | new Setting(containerEl) 281 | .setName('Onboard all notes') 282 | .setDesc('Click the button to add the required frontmatter properties to all notes in your vault, excluding the folders specified above.') 283 | .addButton((button) => 284 | button 285 | .setButtonText('Onboard all notes') 286 | .onClick(async () => this.showConfirmationModal()) 287 | ); 288 | } 289 | 290 | 291 | // functions for onboarding all notes 292 | async showConfirmationModal() { 293 | const modal = new ConfirmationModal(this.app, this.plugin); 294 | modal.open(); 295 | } 296 | 297 | async addFrontMatterPropertiesToAllNotes() { 298 | const files = this.app.vault.getMarkdownFiles(); 299 | 300 | for (const file of files) { 301 | if (!this.isFileExcluded(file)) { 302 | await this.addFrontMatterPropertiesToNote(file); 303 | } 304 | } 305 | } 306 | 307 | isFileExcluded(file: TAbstractFile): boolean { 308 | const excludedFolders = this.plugin.settings.onboardingExcludedFolders; 309 | let parent: TFolder | null = file.parent; 310 | 311 | while (parent) { 312 | if (excludedFolders.includes(parent.path)) { 313 | return true; 314 | } 315 | parent = parent.parent; 316 | } 317 | 318 | return false; 319 | } 320 | 321 | async addFrontMatterPropertiesToNote(file: TFile) { 322 | const frontMatter = this.app.metadataCache.getCache(file.path)?.frontmatter; 323 | const modifiedFrontMatter = { 324 | 'se-interval': frontMatter?.['se-interval'] || this.plugin.settings.spacingMethods[0].defaultInterval, 325 | 'se-last-reviewed': frontMatter?.['se-last-reviewed'] || new Date().toISOString().split('.')[0], 326 | 'se-ease': frontMatter?.['se-ease'] || this.plugin.settings.spacingMethods[0].defaultEaseFactor, 327 | }; 328 | 329 | await this.app.fileManager.processFrontMatter(file, async (frontmatter: any) => { 330 | frontmatter["se-interval"] = modifiedFrontMatter["se-interval"]; 331 | frontmatter["se-last-reviewed"] = modifiedFrontMatter["se-last-reviewed"]; 332 | frontmatter["se-ease"] = modifiedFrontMatter["se-ease"]; 333 | }); 334 | } 335 | 336 | 337 | // Functions for rendering subsets of the settings 338 | renderSpacingMethodSetting(containerEl: HTMLElement, spacingMethod: SpacingMethod, index: number) { 339 | const settingEl = containerEl.createDiv('spacing-method-settings-items'); 340 | const settingHeader = settingEl.createDiv('spacing-method-header'); 341 | const settingBody = settingEl.createDiv('spacing-method-body'); 342 | 343 | const defaultName = `Spacing method #${index + 1}`; 344 | 345 | new Setting(settingHeader) 346 | .setName(defaultName) 347 | .setDesc('Configure the settings for this spacing method.'); 348 | 349 | const generalSettingsDiv = settingBody.createDiv('general-settings'); 350 | 351 | new Setting(generalSettingsDiv) 352 | .setName('Name') 353 | .setDesc('Enter a name for this spacing method') 354 | .addText((text) => { 355 | const textComponent = text 356 | .setPlaceholder('Name') 357 | .setValue(spacingMethod.name || defaultName) 358 | .onChange(async (value) => { 359 | if (!value.trim()) { 360 | spacingMethod.name = defaultName; 361 | } else { 362 | spacingMethod.name = value; 363 | } 364 | await this.plugin.saveSettings(); 365 | }); 366 | 367 | return textComponent; 368 | }); 369 | 370 | new Setting(generalSettingsDiv) 371 | .setName('Default interval') 372 | .setDesc('The default interval length, in days') 373 | .addText((text) => 374 | text 375 | .setPlaceholder('Default interval') 376 | .setValue(spacingMethod.defaultInterval.toString()) 377 | .onChange(async (value) => { 378 | const numericValue = parseFloat(value); 379 | if (!isNaN(numericValue)) { 380 | spacingMethod.defaultInterval = numericValue; 381 | await this.plugin.saveSettings(); 382 | } else { 383 | new Notice('Default interval must be a number.'); 384 | } 385 | }) 386 | ); 387 | 388 | new Setting(generalSettingsDiv) 389 | .setName('Spacing algorithm') 390 | .setDesc('Select which spacing algorithm approach to apply') 391 | .addDropdown((dropdown) => 392 | dropdown 393 | .addOptions({ 394 | 'SuperMemo2.0': 'SuperMemo 2.0', 395 | 'Custom': 'Custom script', 396 | }) 397 | .setValue(spacingMethod.spacingAlgorithm) 398 | .onChange(async (value) => { 399 | spacingMethod.spacingAlgorithm = value; 400 | await this.plugin.saveSettings(); 401 | // Update the visibility of the settings based on the selected value 402 | customScriptSettingContainer.style.display = value === 'Custom' ? 'block' : 'none'; 403 | defaultEaseFactorSettingContainer.style.display = value === 'SuperMemo2.0' ? 'block' : 'none'; 404 | }) 405 | ); 406 | 407 | const customScriptSettingContainer = generalSettingsDiv.createDiv(); 408 | const customScriptSetting = new Setting(customScriptSettingContainer) 409 | .setName('Custom script') 410 | .setDesc('>>>NOT YET IMPLEMENTED<<< —— Input the location of your custom script file that implements a spacing algorithm') 411 | .addText((text) => 412 | text 413 | .setPlaceholder('Custom script file name') 414 | .setValue(spacingMethod.customScriptFileName) 415 | .onChange(async (value) => { 416 | // TODO::implement helper stuff for auto-completing paths/filenames:: 417 | spacingMethod.customScriptFileName = value; 418 | await this.plugin.saveSettings(); 419 | }) 420 | .setDisabled(true) 421 | ); 422 | 423 | const defaultEaseFactorSettingContainer = generalSettingsDiv.createDiv(); 424 | const defaultEaseFactorSetting = new Setting(defaultEaseFactorSettingContainer) 425 | .setName('Default ease factor') 426 | .setDesc('The default ease factor') 427 | .addText((text) => 428 | text 429 | .setPlaceholder('Default ease factor') 430 | .setValue(spacingMethod.defaultEaseFactor?.toString() || '') 431 | .onChange(async (value) => { 432 | const numericValue = parseFloat(value); 433 | if (!isNaN(numericValue)) { 434 | spacingMethod.defaultEaseFactor = numericValue; 435 | await this.plugin.saveSettings(); 436 | } else { 437 | new Notice('Default ease factor must be a number.'); 438 | } 439 | }) 440 | ); 441 | 442 | // Set initial visibility of settings based on the current value of the 'Spacing algorithm' dropdown 443 | customScriptSettingContainer.style.display = spacingMethod.spacingAlgorithm === 'Custom' ? 'block' : 'none'; 444 | defaultEaseFactorSettingContainer.style.display = spacingMethod.spacingAlgorithm === 'SuperMemo2.0' ? 'block' : 'none'; 445 | 446 | // Render review options for the spacing method 447 | const reviewOptionsDiv = settingBody.createDiv('review-options'); 448 | new Setting(reviewOptionsDiv) 449 | .setHeading() 450 | .setName('Review options') 451 | .setDesc('Customize the review options and scores to use in this spacing method. For the SuperMemo-2.0 spacing algorithm, review scores must be a number from 0 to 5.'); 452 | 453 | const addReviewOptionDiv = reviewOptionsDiv.createDiv(); 454 | new Setting(addReviewOptionDiv) 455 | .addButton((button) => 456 | button 457 | .setButtonText('+') 458 | .setIcon('plus') 459 | .setTooltip('Add a new review option') 460 | .onClick(async () => { 461 | const newOption = { name: '', score: 0 }; 462 | spacingMethod.reviewOptions.push(newOption); 463 | await this.plugin.saveSettings(); 464 | this.renderReviewOptionSetting(reviewOptionsDiv, newOption, spacingMethod.reviewOptions.length - 1, index); 465 | }) 466 | ); 467 | 468 | spacingMethod.reviewOptions.forEach((option, optionIndex) => { 469 | this.renderReviewOptionSetting(reviewOptionsDiv, option, optionIndex, index); 470 | }); 471 | 472 | // Add delete button for the spacing method 473 | new Setting(settingEl) 474 | // TODO::make this render in a better location to make it 475 | // more clearly distinct from review options expansion:: 476 | .addExtraButton((cb) => { 477 | cb.setIcon('cross') 478 | .setTooltip('Delete spacing method') 479 | .onClick(async () => { 480 | if (this.plugin.settings.spacingMethods.length === 1) { 481 | new Notice('Cannot delete the last spacing method'); 482 | } else { 483 | this.plugin.settings.spacingMethods.splice(index, 1); 484 | await this.plugin.saveSettings(); 485 | this.display(); // Re-render the settings tab 486 | } 487 | }); 488 | }); 489 | } 490 | 491 | renderContextSetting(containerEl: HTMLElement, context: Context, index: number) { 492 | const settingEl = containerEl.createDiv('context-settings-items'); 493 | 494 | new Setting(settingEl) 495 | .setName(`(${index + 1})`) 496 | .addText((text) => 497 | text 498 | .setValue(context.name) 499 | .onChange(async (value) => { 500 | context.name = value; 501 | await this.plugin.saveSettings(); 502 | }) 503 | ) 504 | .addToggle((toggle) => 505 | toggle 506 | .setValue(context.isActive) 507 | .onChange(async (value) => { 508 | context.isActive = value; 509 | await this.plugin.saveSettings(); 510 | }) 511 | ) 512 | .addExtraButton((cb) => { 513 | cb.setIcon('cross') 514 | .setTooltip('Delete') 515 | .onClick(async () => { 516 | this.plugin.settings.contexts.splice(index, 1); 517 | await this.plugin.saveSettings(); 518 | this.display(); // Re-render the settings tab 519 | }); 520 | }); 521 | } 522 | 523 | renderReviewOptionSetting(containerEl: HTMLElement, option: ReviewOption, optionIndex: number, spacingMethodIndex: number) { 524 | const settingEl = containerEl.createDiv('review-option-settings-items'); 525 | 526 | new Setting(settingEl) 527 | .setName(`(${optionIndex + 1})`) 528 | .addText((text) => 529 | text 530 | .setPlaceholder('Name') 531 | .setValue(option.name) 532 | .onChange(async (value) => { 533 | option.name = value; 534 | await this.plugin.saveSettings(); 535 | }) 536 | ) 537 | .addText((text) => 538 | text 539 | .setPlaceholder('Review score') 540 | .setValue(option.score.toString()) 541 | .onChange(async (value) => { 542 | const numericValue = parseFloat(value); 543 | if (value === '' || (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 5)) { 544 | option.score = numericValue; 545 | await this.plugin.saveSettings(); 546 | } else { 547 | new Notice('Review score must be a number from 0 to 5'); 548 | } 549 | }) 550 | ) 551 | .addExtraButton((cb) => { 552 | cb.setIcon('cross') 553 | .setTooltip('Delete') 554 | .onClick(async () => { 555 | this.plugin.settings.spacingMethods[spacingMethodIndex].reviewOptions.splice(optionIndex, 1); 556 | await this.plugin.saveSettings(); 557 | this.display(); // Re-render the settings tab 558 | }); 559 | }); 560 | } 561 | } 562 | 563 | class ConfirmationModal extends Modal { 564 | plugin: SpacedEverythingPlugin; 565 | settingsTab: SpacedEverythingSettingTab; 566 | 567 | constructor(app: App, plugin: SpacedEverythingPlugin) { 568 | super(app); 569 | this.plugin = plugin; 570 | this.settingsTab = new SpacedEverythingSettingTab(app, plugin); 571 | } 572 | 573 | onOpen() { 574 | const { contentEl } = this; 575 | contentEl.createEl('h2', { text: 'Confirm Action' }); 576 | contentEl.createEl('p', { text: 'Are you sure you want to onboard to all notes in your vault? This action cannot be undone. It is highly recommended you create a full backup of your vault prior to running this vault-wide action, in case any unexpected changes result.' }); 577 | 578 | const confirmButton = new Setting(contentEl) 579 | .addButton((button) => { 580 | button 581 | .setButtonText('Confirm') 582 | .setCta() 583 | .onClick(async () => { 584 | await this.settingsTab.addFrontMatterPropertiesToAllNotes(); 585 | this.close(); 586 | new Notice('All notes onboarded'); 587 | }); 588 | }); 589 | 590 | const cancelButton = new Setting(contentEl) 591 | .addButton((button) => { 592 | button 593 | .setButtonText('Cancel') 594 | .onClick(() => { 595 | this.close(); 596 | }); 597 | }); 598 | } 599 | 600 | onClose() { 601 | this.contentEl.empty(); 602 | } 603 | } 604 | -------------------------------------------------------------------------------- /src/suggester.ts: -------------------------------------------------------------------------------- 1 | import { App, SuggestModal } from 'obsidian'; 2 | 3 | export class Suggester extends SuggestModal { 4 | promptText: string; 5 | items: string[]; 6 | onChooseItem: (item: string) => void; 7 | 8 | constructor(app: App, promptText: string, items: string[]) { 9 | super(app); 10 | this.promptText = promptText; 11 | this.items = items; 12 | this.onChooseItem = () => {}; 13 | } 14 | 15 | getSuggestions(query: string): string[] { 16 | return this.items.filter(item => item.toLowerCase().includes(query.toLowerCase())); 17 | } 18 | 19 | renderSuggestion(item: string, el: HTMLElement) { 20 | el.createEl("div", { text: item }); 21 | } 22 | 23 | onChooseSuggestion(item: string, evt: MouseEvent | KeyboardEvent) { 24 | this.onChooseItem(item); 25 | } 26 | } 27 | 28 | export async function suggester(options: string[], promptText: string): Promise { 29 | return new Promise((resolve) => { 30 | const modal = new Suggester(this.app, promptText, options); 31 | modal.onChooseItem = resolve; 32 | modal.open(); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Context { 3 | name: string; 4 | isActive: boolean; 5 | spacingMethodName?: string; 6 | } 7 | 8 | export interface ReviewOption { 9 | name: string; 10 | score: number; 11 | } 12 | 13 | export interface SpacingMethod { 14 | name: string; 15 | spacingAlgorithm: string; 16 | customScriptFileName: string; 17 | reviewOptions: ReviewOption[]; 18 | defaultInterval: number; 19 | defaultEaseFactor?: number; // optional because may only be relevant to SM-2 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0" 3 | } 4 | --------------------------------------------------------------------------------