├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── esbuild.config.mjs ├── manifest.json ├── package.json ├── pnpm-lock.yaml ├── src ├── main.ts ├── onLinkHover.ts ├── popover.ts ├── settings │ └── settings.ts ├── types │ └── obsidian.d.ts └── utils │ ├── measure.ts │ └── misc.ts ├── styles.css ├── tsconfig.json └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build 3 | .eslintrc.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | ecmaVersion: 2018, 6 | sourceType: "module", 7 | project: ["tsconfig.json", ".eslintrc.js"], 8 | tsconfigRootDir: __dirname, 9 | }, 10 | extends: [ 11 | "plugin:import/typescript", 12 | "eslint:recommended", 13 | "plugin:prettier/recommended", 14 | "plugin:@typescript-eslint/eslint-recommended", 15 | "plugin:@typescript-eslint/recommended", 16 | "prettier" 17 | ], 18 | env: { 19 | browser: true, 20 | node: true, 21 | }, 22 | plugins: [ 23 | "@typescript-eslint", 24 | ], 25 | settings: { 26 | "import/parsers": { 27 | "@typescript-eslint/parser": [".ts", ".tsx"], 28 | }, 29 | }, 30 | rules: { 31 | "no-unused-vars": "off", 32 | "max-len": [ 33 | "error", 34 | 120, 35 | 2, 36 | { 37 | ignoreUrls: true, 38 | ignoreComments: true, 39 | ignoreRegExpLiterals: true, 40 | ignoreStrings: true, 41 | ignoreTemplateLiterals: true, 42 | }, 43 | ], 44 | "react/jsx-filename-extension": "off", 45 | "import/no-extraneous-dependencies": "off", 46 | "import/no-unresolved": "off", 47 | "linebreak-style": ["error", "unix"], 48 | "@typescript-eslint/no-non-null-assertion": "off", 49 | "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], 50 | "@typescript-eslint/ban-ts-comment": "off", 51 | "@typescript-eslint/ban-types": [ 52 | "error", 53 | { 54 | types: { 55 | Function: false, 56 | }, 57 | extendDefaults: true, 58 | }, 59 | ], 60 | "comma-dangle": ["error", "only-multiline"], 61 | "arrow-parens": ["error", "as-needed"], 62 | "no-empty": ["error", { allowEmptyCatch: true }], 63 | "@typescript-eslint/no-this-alias": [ 64 | "error", 65 | { 66 | allowedNames: ["self", "plugin"], 67 | }, 68 | ], 69 | "no-prototype-builtins": "off", 70 | "function-paren-newline": "off", 71 | "@typescript-eslint/no-empty-function": "off", 72 | }, 73 | overrides: [ 74 | { 75 | files: ["*.ts"], 76 | rules: { 77 | "no-undef": "off", 78 | "no-extra-parens": "off", 79 | "@typescript-eslint/no-extra-parens": "off", 80 | }, 81 | }, 82 | ], 83 | }; 84 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build obsidian plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: ophidian-lib/build@v1 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | release-notes: ${{ github.event.commits[0].message }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | package-lock.json 11 | 12 | # Don't include the compiled main.js file in the repo. 13 | # They should be uploaded to GitHub releases instead. 14 | main.js 15 | 16 | # Exclude sourcemaps 17 | *.map 18 | 19 | # obsidian 20 | data.json 21 | dist 22 | 23 | # yarn 24 | yarn.lock 25 | yarn-error.log 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "trailingComma": "all", 5 | "printWidth": 120, 6 | "singleQuote": false, 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018-2021 by NothingIsLost and others 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Obsidian Hover Editor 2 | 3 | This plugin enhances the core "Page Preview" plugin by turning the hover popover into a full featured editor instance. 4 | 5 | ### Disclaimer 6 | 7 | This plugin leverages Obsidian functionality that is not currently exposed in the official API. As a result, future Obsidian updates may introduce breaking changes. 8 | 9 | I will attempt to keep this plugin working across Obsidian updates but my goal is to either have this functionality implemented directly into Obsidian core or switch over to using the official API for popovers, once it is made available. 10 | 11 | ### Features 12 | 13 | - The page preview popover is now an actual editor instance 14 | - Most editor functionality is supported including switching between modes 15 | - The popover is now draggable and resizable 16 | - The popover can now be pinned to prevent it from auto closing 17 | - Popovers will auto pin when dragged or resized 18 | - With pinning, multiple popovers can be open at the same time 19 | - When opening a popover, it will become the active pane and receive focus 20 | - This means you can use keyboard shortcuts like ctrl+e to switch modes after triggering a popover 21 | - When the popover closes, focus will revert back to the previous document 22 | - The popover now has a nav bar which includes the document title and editor controls 23 | - The top drag handle can be double clicked to minimize the popover 24 | - There is a plugin setting that allows for setting the default editor mode 25 | - Options are: "Open in Reading mode", "Open in Editing mode", or "Match the mode of the current document" 26 | - When hovering a link containing header or block ref, the editor will open and auto scroll to the ref location 27 | - When multiple popovers are active and on top of each other, the currently active popover will remain on top 28 | 29 | ### Demo 30 | 31 | https://user-images.githubusercontent.com/89109712/160023366-7a1ca044-5725-4d30-a0a7-f7e0664281da.mp4 32 | 33 | ### Installing 34 | 35 | Hover Editor can be found and installed via the Obsidian Community Plugins browser 36 | 37 | ### Installing via BRAT 38 | 39 | If you want to participate in early testing you can install the plugin using BRAT. 40 | 41 | Install the BRAT plugin via the Obsidian Plugin Browser and then add the beta repository "nothingislost/obsidian-hover-editor" 42 | 43 | ### Manually installing the plugin 44 | 45 | - Copy over `main.js`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/obsidian-hover-editor/`. 46 | 47 | ### Acknowledgments 48 | 49 | Thanks to pjeby for contributing a ton of core functionality related to making Hover Editors interop properly with native Obsidian components 50 | 51 | Thanks to boninall for contributing the "open in new popover" functionality 52 | 53 | Thanks to murf, liam, obadiahcruz, and javalent for the early testing and feedback 54 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | import copy from 'esbuild-plugin-copy'; 5 | 6 | const banner = `/* 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 staticAssetsPlugin = { 13 | name: 'static-assets-plugin', 14 | setup(build) { 15 | build.onLoad({ filter: /.+/ }, (args) => { 16 | return { 17 | watchFiles: ['styles.css', 'esbuild.config.mjs'], 18 | }; 19 | }); 20 | }, 21 | }; 22 | 23 | const prod = process.argv[2] === "production"; 24 | 25 | esbuild 26 | .build({ 27 | banner: { 28 | js: banner, 29 | }, 30 | minify: prod ? true : false, 31 | entryPoints: ["src/main.ts"], 32 | bundle: true, 33 | external: [ 34 | "obsidian", 35 | "electron", 36 | "codemirror", 37 | "@codemirror/view", 38 | ...builtins, 39 | ], 40 | format: "cjs", 41 | watch: !prod, 42 | target: "ES2018", 43 | logLevel: "info", 44 | sourcemap: prod ? false : "inline", 45 | treeShaking: true, 46 | outfile: "dist/main.js", 47 | plugins: [ 48 | staticAssetsPlugin, 49 | copy.default({ 50 | verbose: false, 51 | assets: { 52 | from: ['manifest*', 'styles.css'], 53 | to: ['.'], 54 | } 55 | }), 56 | ], 57 | }) 58 | .catch(() => process.exit(1)); 59 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-hover-editor", 3 | "name": "Hover Editor", 4 | "version": "0.11.26", 5 | "minAppVersion": "1.5.8", 6 | "description": "Transform the Page Preview hover popover into a fully working editor instance", 7 | "author": "NothingIsLost", 8 | "authorUrl": "https://github.com/nothingislost", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-hover-editor", 3 | "version": "0.10.0", 4 | "description": "Transform the Page Preview hover popover into a fully working editor instance", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "node esbuild.config.mjs production", 9 | "prettier": "prettier --write 'src/**/*.+(ts|tsx|json|html|css)' 'styles.css'", 10 | "eslint": "eslint . --ext .ts,.tsx --fix" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@codemirror/view": "^6.0.0", 17 | "@interactjs/types": "1.10.11", 18 | "@nothingislost/interactjs": "1.10.11", 19 | "@ophidian/core": "0.0.24", 20 | "@types/node": "^16.11.6", 21 | "@typescript-eslint/eslint-plugin": "^5.2.0", 22 | "@typescript-eslint/parser": "^5.18.0", 23 | "builtin-modules": "^3.2.0", 24 | "esbuild": "0.13.12", 25 | "esbuild-plugin-copy": "1.2.1", 26 | "eslint": "^8.12.0", 27 | "eslint-config-prettier": "^8.5.0", 28 | "eslint-plugin-import": "^2.26.0", 29 | "eslint-plugin-prefer-arrow": "^1.2.3", 30 | "eslint-plugin-prettier": "^4.0.0", 31 | "monkey-around": "^3", 32 | "obsidian": "1.3.5", 33 | "parse-unit": "^1.0.1", 34 | "prettier": "^2.6.2", 35 | "tslib": "2.3.1", 36 | "typescript": "4.4.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | '@codemirror/view': ^6.0.0 5 | '@interactjs/types': 1.10.11 6 | '@nothingislost/interactjs': 1.10.11 7 | '@ophidian/core': 0.0.24 8 | '@types/node': ^16.11.6 9 | '@typescript-eslint/eslint-plugin': ^5.2.0 10 | '@typescript-eslint/parser': ^5.18.0 11 | builtin-modules: ^3.2.0 12 | esbuild: 0.13.12 13 | esbuild-plugin-copy: 1.2.1 14 | eslint: ^8.12.0 15 | eslint-config-prettier: ^8.5.0 16 | eslint-plugin-import: ^2.26.0 17 | eslint-plugin-prefer-arrow: ^1.2.3 18 | eslint-plugin-prettier: ^4.0.0 19 | monkey-around: ^3 20 | obsidian: 1.3.5 21 | parse-unit: ^1.0.1 22 | prettier: ^2.6.2 23 | tslib: 2.3.1 24 | typescript: 4.4.4 25 | 26 | devDependencies: 27 | '@codemirror/view': 6.14.1 28 | '@interactjs/types': 1.10.11 29 | '@nothingislost/interactjs': 1.10.11 30 | '@ophidian/core': 0.0.24_@codemirror+view@6.14.1 31 | '@types/node': 16.18.38 32 | '@typescript-eslint/eslint-plugin': 5.62.0_p3ikbjph34ysacct4522dgq6ya 33 | '@typescript-eslint/parser': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 34 | builtin-modules: 3.3.0 35 | esbuild: 0.13.12 36 | esbuild-plugin-copy: 1.2.1_esbuild@0.13.12 37 | eslint: 8.45.0 38 | eslint-config-prettier: 8.8.0_eslint@8.45.0 39 | eslint-plugin-import: 2.27.5_vdlnholh74uk2lamdhhi4g2qza 40 | eslint-plugin-prefer-arrow: 1.2.3_eslint@8.45.0 41 | eslint-plugin-prettier: 4.2.1_6nuyjbnyo6rwr4pwddenxlklsi 42 | monkey-around: 3.0.0 43 | obsidian: 1.3.5_@codemirror+view@6.14.1 44 | parse-unit: 1.0.1 45 | prettier: 2.8.8 46 | tslib: 2.3.1 47 | typescript: 4.4.4 48 | 49 | packages: 50 | 51 | /@aashutoshrathi/word-wrap/1.2.6: 52 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 53 | engines: {node: '>=0.10.0'} 54 | dev: true 55 | 56 | /@babel/runtime/7.26.0: 57 | resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} 58 | engines: {node: '>=6.9.0'} 59 | dependencies: 60 | regenerator-runtime: 0.14.1 61 | dev: true 62 | 63 | /@codemirror/state/6.2.1: 64 | resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==} 65 | dev: true 66 | 67 | /@codemirror/view/6.14.1: 68 | resolution: {integrity: sha512-ofcsI7lRFo4N0rfnd+V3Gh2boQU3DmaaSKhDOvXUWjeOeuupMXer2e/3i9TUFN7aEIntv300EFBWPEiYVm2svg==} 69 | dependencies: 70 | '@codemirror/state': 6.2.1 71 | style-mod: 4.0.3 72 | w3c-keyname: 2.2.8 73 | dev: true 74 | 75 | /@eslint-community/eslint-utils/4.4.0_eslint@8.45.0: 76 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 77 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 78 | peerDependencies: 79 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 80 | dependencies: 81 | eslint: 8.45.0 82 | eslint-visitor-keys: 3.4.1 83 | dev: true 84 | 85 | /@eslint-community/regexpp/4.5.1: 86 | resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} 87 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 88 | dev: true 89 | 90 | /@eslint/eslintrc/2.1.0: 91 | resolution: {integrity: sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==} 92 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 93 | dependencies: 94 | ajv: 6.12.6 95 | debug: 4.3.4 96 | espree: 9.6.1 97 | globals: 13.20.0 98 | ignore: 5.2.4 99 | import-fresh: 3.3.0 100 | js-yaml: 4.1.0 101 | minimatch: 3.1.2 102 | strip-json-comments: 3.1.1 103 | transitivePeerDependencies: 104 | - supports-color 105 | dev: true 106 | 107 | /@eslint/js/8.44.0: 108 | resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} 109 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 110 | dev: true 111 | 112 | /@humanwhocodes/config-array/0.11.10: 113 | resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} 114 | engines: {node: '>=10.10.0'} 115 | dependencies: 116 | '@humanwhocodes/object-schema': 1.2.1 117 | debug: 4.3.4 118 | minimatch: 3.1.2 119 | transitivePeerDependencies: 120 | - supports-color 121 | dev: true 122 | 123 | /@humanwhocodes/module-importer/1.0.1: 124 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 125 | engines: {node: '>=12.22'} 126 | dev: true 127 | 128 | /@humanwhocodes/object-schema/1.2.1: 129 | resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} 130 | dev: true 131 | 132 | /@interactjs/types/1.10.11: 133 | resolution: {integrity: sha512-YRsVFWjL8Gkkvlx3qnjeaxW4fnibSJ9791g8BA7Pv5ANByI64WmtR1vU7A2rXcrOn8XvyCEfY0ss1s8NhZP+MA==} 134 | dev: true 135 | 136 | /@nodelib/fs.scandir/2.1.5: 137 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 138 | engines: {node: '>= 8'} 139 | dependencies: 140 | '@nodelib/fs.stat': 2.0.5 141 | run-parallel: 1.2.0 142 | dev: true 143 | 144 | /@nodelib/fs.stat/2.0.5: 145 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 146 | engines: {node: '>= 8'} 147 | dev: true 148 | 149 | /@nodelib/fs.walk/1.2.8: 150 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 151 | engines: {node: '>= 8'} 152 | dependencies: 153 | '@nodelib/fs.scandir': 2.1.5 154 | fastq: 1.15.0 155 | dev: true 156 | 157 | /@nothingislost/interactjs/1.10.11: 158 | resolution: {integrity: sha512-Q9kKNxvMqfNlfXRcfIdIsz4PcK+stf93nWClgzblVeZj9S2FtBLa17YXbzjyU5BF9XArBWBpkt7Zjzf4GoUbRQ==} 159 | dependencies: 160 | '@interactjs/types': 1.10.11 161 | dev: true 162 | 163 | /@ophidian/core/0.0.24_@codemirror+view@6.14.1: 164 | resolution: {integrity: sha512-VaHJsM0YNhLT8NTap6Dw/gnHgdei94WuyzHTHg9K/PBCOau+gWtBQUs7+TS+/QQ8obUjAmqzcguI5QtZY5vNNQ==} 165 | dependencies: 166 | '@preact/signals-core': 1.8.0 167 | defaults: 2.0.2 168 | i18next: 20.6.1 169 | monkey-around: 3.0.0 170 | obsidian: 1.3.5_@codemirror+view@6.14.1 171 | to-use: 0.3.3 172 | wonka: 6.3.4 173 | transitivePeerDependencies: 174 | - '@codemirror/state' 175 | - '@codemirror/view' 176 | dev: true 177 | 178 | /@preact/signals-core/1.8.0: 179 | resolution: {integrity: sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==} 180 | dev: true 181 | 182 | /@types/codemirror/5.60.8: 183 | resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==} 184 | dependencies: 185 | '@types/tern': 0.23.4 186 | dev: true 187 | 188 | /@types/estree/1.0.1: 189 | resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} 190 | dev: true 191 | 192 | /@types/json-schema/7.0.12: 193 | resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} 194 | dev: true 195 | 196 | /@types/json5/0.0.29: 197 | resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} 198 | dev: true 199 | 200 | /@types/node/16.18.38: 201 | resolution: {integrity: sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==} 202 | dev: true 203 | 204 | /@types/semver/7.5.0: 205 | resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} 206 | dev: true 207 | 208 | /@types/tern/0.23.4: 209 | resolution: {integrity: sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==} 210 | dependencies: 211 | '@types/estree': 1.0.1 212 | dev: true 213 | 214 | /@typescript-eslint/eslint-plugin/5.62.0_p3ikbjph34ysacct4522dgq6ya: 215 | resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} 216 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 217 | peerDependencies: 218 | '@typescript-eslint/parser': ^5.0.0 219 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 220 | typescript: '*' 221 | peerDependenciesMeta: 222 | typescript: 223 | optional: true 224 | dependencies: 225 | '@eslint-community/regexpp': 4.5.1 226 | '@typescript-eslint/parser': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 227 | '@typescript-eslint/scope-manager': 5.62.0 228 | '@typescript-eslint/type-utils': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 229 | '@typescript-eslint/utils': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 230 | debug: 4.3.4 231 | eslint: 8.45.0 232 | graphemer: 1.4.0 233 | ignore: 5.2.4 234 | natural-compare-lite: 1.4.0 235 | semver: 7.5.4 236 | tsutils: 3.21.0_typescript@4.4.4 237 | typescript: 4.4.4 238 | transitivePeerDependencies: 239 | - supports-color 240 | dev: true 241 | 242 | /@typescript-eslint/parser/5.62.0_uehp6ybvdwvysdrlciovnyarwm: 243 | resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} 244 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 245 | peerDependencies: 246 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 247 | typescript: '*' 248 | peerDependenciesMeta: 249 | typescript: 250 | optional: true 251 | dependencies: 252 | '@typescript-eslint/scope-manager': 5.62.0 253 | '@typescript-eslint/types': 5.62.0 254 | '@typescript-eslint/typescript-estree': 5.62.0_typescript@4.4.4 255 | debug: 4.3.4 256 | eslint: 8.45.0 257 | typescript: 4.4.4 258 | transitivePeerDependencies: 259 | - supports-color 260 | dev: true 261 | 262 | /@typescript-eslint/scope-manager/5.62.0: 263 | resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} 264 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 265 | dependencies: 266 | '@typescript-eslint/types': 5.62.0 267 | '@typescript-eslint/visitor-keys': 5.62.0 268 | dev: true 269 | 270 | /@typescript-eslint/type-utils/5.62.0_uehp6ybvdwvysdrlciovnyarwm: 271 | resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} 272 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 273 | peerDependencies: 274 | eslint: '*' 275 | typescript: '*' 276 | peerDependenciesMeta: 277 | typescript: 278 | optional: true 279 | dependencies: 280 | '@typescript-eslint/typescript-estree': 5.62.0_typescript@4.4.4 281 | '@typescript-eslint/utils': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 282 | debug: 4.3.4 283 | eslint: 8.45.0 284 | tsutils: 3.21.0_typescript@4.4.4 285 | typescript: 4.4.4 286 | transitivePeerDependencies: 287 | - supports-color 288 | dev: true 289 | 290 | /@typescript-eslint/types/5.62.0: 291 | resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} 292 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 293 | dev: true 294 | 295 | /@typescript-eslint/typescript-estree/5.62.0_typescript@4.4.4: 296 | resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} 297 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 298 | peerDependencies: 299 | typescript: '*' 300 | peerDependenciesMeta: 301 | typescript: 302 | optional: true 303 | dependencies: 304 | '@typescript-eslint/types': 5.62.0 305 | '@typescript-eslint/visitor-keys': 5.62.0 306 | debug: 4.3.4 307 | globby: 11.1.0 308 | is-glob: 4.0.3 309 | semver: 7.5.4 310 | tsutils: 3.21.0_typescript@4.4.4 311 | typescript: 4.4.4 312 | transitivePeerDependencies: 313 | - supports-color 314 | dev: true 315 | 316 | /@typescript-eslint/utils/5.62.0_uehp6ybvdwvysdrlciovnyarwm: 317 | resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} 318 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 319 | peerDependencies: 320 | eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 321 | dependencies: 322 | '@eslint-community/eslint-utils': 4.4.0_eslint@8.45.0 323 | '@types/json-schema': 7.0.12 324 | '@types/semver': 7.5.0 325 | '@typescript-eslint/scope-manager': 5.62.0 326 | '@typescript-eslint/types': 5.62.0 327 | '@typescript-eslint/typescript-estree': 5.62.0_typescript@4.4.4 328 | eslint: 8.45.0 329 | eslint-scope: 5.1.1 330 | semver: 7.5.4 331 | transitivePeerDependencies: 332 | - supports-color 333 | - typescript 334 | dev: true 335 | 336 | /@typescript-eslint/visitor-keys/5.62.0: 337 | resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} 338 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 339 | dependencies: 340 | '@typescript-eslint/types': 5.62.0 341 | eslint-visitor-keys: 3.4.1 342 | dev: true 343 | 344 | /acorn-jsx/5.3.2_acorn@8.10.0: 345 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 346 | peerDependencies: 347 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 348 | dependencies: 349 | acorn: 8.10.0 350 | dev: true 351 | 352 | /acorn/8.10.0: 353 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} 354 | engines: {node: '>=0.4.0'} 355 | hasBin: true 356 | dev: true 357 | 358 | /ajv/6.12.6: 359 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 360 | dependencies: 361 | fast-deep-equal: 3.1.3 362 | fast-json-stable-stringify: 2.1.0 363 | json-schema-traverse: 0.4.1 364 | uri-js: 4.4.1 365 | dev: true 366 | 367 | /ansi-regex/5.0.1: 368 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 369 | engines: {node: '>=8'} 370 | dev: true 371 | 372 | /ansi-styles/4.3.0: 373 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 374 | engines: {node: '>=8'} 375 | dependencies: 376 | color-convert: 2.0.1 377 | dev: true 378 | 379 | /argparse/2.0.1: 380 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 381 | dev: true 382 | 383 | /array-buffer-byte-length/1.0.0: 384 | resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} 385 | dependencies: 386 | call-bind: 1.0.2 387 | is-array-buffer: 3.0.2 388 | dev: true 389 | 390 | /array-includes/3.1.6: 391 | resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} 392 | engines: {node: '>= 0.4'} 393 | dependencies: 394 | call-bind: 1.0.2 395 | define-properties: 1.2.0 396 | es-abstract: 1.21.3 397 | get-intrinsic: 1.2.1 398 | is-string: 1.0.7 399 | dev: true 400 | 401 | /array-union/2.1.0: 402 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 403 | engines: {node: '>=8'} 404 | dev: true 405 | 406 | /array.prototype.flat/1.3.1: 407 | resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} 408 | engines: {node: '>= 0.4'} 409 | dependencies: 410 | call-bind: 1.0.2 411 | define-properties: 1.2.0 412 | es-abstract: 1.21.3 413 | es-shim-unscopables: 1.0.0 414 | dev: true 415 | 416 | /array.prototype.flatmap/1.3.1: 417 | resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} 418 | engines: {node: '>= 0.4'} 419 | dependencies: 420 | call-bind: 1.0.2 421 | define-properties: 1.2.0 422 | es-abstract: 1.21.3 423 | es-shim-unscopables: 1.0.0 424 | dev: true 425 | 426 | /available-typed-arrays/1.0.5: 427 | resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 428 | engines: {node: '>= 0.4'} 429 | dev: true 430 | 431 | /balanced-match/1.0.2: 432 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 433 | dev: true 434 | 435 | /brace-expansion/1.1.11: 436 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 437 | dependencies: 438 | balanced-match: 1.0.2 439 | concat-map: 0.0.1 440 | dev: true 441 | 442 | /braces/3.0.2: 443 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 444 | engines: {node: '>=8'} 445 | dependencies: 446 | fill-range: 7.0.1 447 | dev: true 448 | 449 | /builtin-modules/3.3.0: 450 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 451 | engines: {node: '>=6'} 452 | dev: true 453 | 454 | /call-bind/1.0.2: 455 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 456 | dependencies: 457 | function-bind: 1.1.1 458 | get-intrinsic: 1.2.1 459 | dev: true 460 | 461 | /callsites/3.1.0: 462 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 463 | engines: {node: '>=6'} 464 | dev: true 465 | 466 | /chalk/4.1.2: 467 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 468 | engines: {node: '>=10'} 469 | dependencies: 470 | ansi-styles: 4.3.0 471 | supports-color: 7.2.0 472 | dev: true 473 | 474 | /color-convert/2.0.1: 475 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 476 | engines: {node: '>=7.0.0'} 477 | dependencies: 478 | color-name: 1.1.4 479 | dev: true 480 | 481 | /color-name/1.1.4: 482 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 483 | dev: true 484 | 485 | /concat-map/0.0.1: 486 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 487 | dev: true 488 | 489 | /cross-spawn/7.0.3: 490 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 491 | engines: {node: '>= 8'} 492 | dependencies: 493 | path-key: 3.1.1 494 | shebang-command: 2.0.0 495 | which: 2.0.2 496 | dev: true 497 | 498 | /debug/3.2.7: 499 | resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 500 | peerDependencies: 501 | supports-color: '*' 502 | peerDependenciesMeta: 503 | supports-color: 504 | optional: true 505 | dependencies: 506 | ms: 2.1.3 507 | dev: true 508 | 509 | /debug/4.3.4: 510 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 511 | engines: {node: '>=6.0'} 512 | peerDependencies: 513 | supports-color: '*' 514 | peerDependenciesMeta: 515 | supports-color: 516 | optional: true 517 | dependencies: 518 | ms: 2.1.2 519 | dev: true 520 | 521 | /deep-is/0.1.4: 522 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 523 | dev: true 524 | 525 | /defaults/2.0.2: 526 | resolution: {integrity: sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==} 527 | engines: {node: '>=16'} 528 | dev: true 529 | 530 | /define-properties/1.2.0: 531 | resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} 532 | engines: {node: '>= 0.4'} 533 | dependencies: 534 | has-property-descriptors: 1.0.0 535 | object-keys: 1.1.1 536 | dev: true 537 | 538 | /dir-glob/3.0.1: 539 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 540 | engines: {node: '>=8'} 541 | dependencies: 542 | path-type: 4.0.0 543 | dev: true 544 | 545 | /doctrine/2.1.0: 546 | resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 547 | engines: {node: '>=0.10.0'} 548 | dependencies: 549 | esutils: 2.0.3 550 | dev: true 551 | 552 | /doctrine/3.0.0: 553 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 554 | engines: {node: '>=6.0.0'} 555 | dependencies: 556 | esutils: 2.0.3 557 | dev: true 558 | 559 | /es-abstract/1.21.3: 560 | resolution: {integrity: sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==} 561 | engines: {node: '>= 0.4'} 562 | dependencies: 563 | array-buffer-byte-length: 1.0.0 564 | available-typed-arrays: 1.0.5 565 | call-bind: 1.0.2 566 | es-set-tostringtag: 2.0.1 567 | es-to-primitive: 1.2.1 568 | function.prototype.name: 1.1.5 569 | get-intrinsic: 1.2.1 570 | get-symbol-description: 1.0.0 571 | globalthis: 1.0.3 572 | gopd: 1.0.1 573 | has: 1.0.3 574 | has-property-descriptors: 1.0.0 575 | has-proto: 1.0.1 576 | has-symbols: 1.0.3 577 | internal-slot: 1.0.5 578 | is-array-buffer: 3.0.2 579 | is-callable: 1.2.7 580 | is-negative-zero: 2.0.2 581 | is-regex: 1.1.4 582 | is-shared-array-buffer: 1.0.2 583 | is-string: 1.0.7 584 | is-typed-array: 1.1.10 585 | is-weakref: 1.0.2 586 | object-inspect: 1.12.3 587 | object-keys: 1.1.1 588 | object.assign: 4.1.4 589 | regexp.prototype.flags: 1.5.0 590 | safe-regex-test: 1.0.0 591 | string.prototype.trim: 1.2.7 592 | string.prototype.trimend: 1.0.6 593 | string.prototype.trimstart: 1.0.6 594 | typed-array-byte-offset: 1.0.0 595 | typed-array-length: 1.0.4 596 | unbox-primitive: 1.0.2 597 | which-typed-array: 1.1.10 598 | dev: true 599 | 600 | /es-set-tostringtag/2.0.1: 601 | resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} 602 | engines: {node: '>= 0.4'} 603 | dependencies: 604 | get-intrinsic: 1.2.1 605 | has: 1.0.3 606 | has-tostringtag: 1.0.0 607 | dev: true 608 | 609 | /es-shim-unscopables/1.0.0: 610 | resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} 611 | dependencies: 612 | has: 1.0.3 613 | dev: true 614 | 615 | /es-to-primitive/1.2.1: 616 | resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 617 | engines: {node: '>= 0.4'} 618 | dependencies: 619 | is-callable: 1.2.7 620 | is-date-object: 1.0.5 621 | is-symbol: 1.0.4 622 | dev: true 623 | 624 | /esbuild-android-arm64/0.13.12: 625 | resolution: {integrity: sha512-TSVZVrb4EIXz6KaYjXfTzPyyRpXV5zgYIADXtQsIenjZ78myvDGaPi11o4ZSaHIwFHsuwkB6ne5SZRBwAQ7maw==} 626 | cpu: [arm64] 627 | os: [android] 628 | requiresBuild: true 629 | dev: true 630 | optional: true 631 | 632 | /esbuild-darwin-64/0.13.12: 633 | resolution: {integrity: sha512-c51C+N+UHySoV2lgfWSwwmlnLnL0JWj/LzuZt9Ltk9ub1s2Y8cr6SQV5W3mqVH1egUceew6KZ8GyI4nwu+fhsw==} 634 | cpu: [x64] 635 | os: [darwin] 636 | requiresBuild: true 637 | dev: true 638 | optional: true 639 | 640 | /esbuild-darwin-arm64/0.13.12: 641 | resolution: {integrity: sha512-JvAMtshP45Hd8A8wOzjkY1xAnTKTYuP/QUaKp5eUQGX+76GIie3fCdUUr2ZEKdvpSImNqxiZSIMziEiGB5oUmQ==} 642 | cpu: [arm64] 643 | os: [darwin] 644 | requiresBuild: true 645 | dev: true 646 | optional: true 647 | 648 | /esbuild-freebsd-64/0.13.12: 649 | resolution: {integrity: sha512-r6On/Skv9f0ZjTu6PW5o7pdXr8aOgtFOEURJZYf1XAJs0IQ+gW+o1DzXjVkIoT+n1cm3N/t1KRJfX71MPg/ZUA==} 650 | cpu: [x64] 651 | os: [freebsd] 652 | requiresBuild: true 653 | dev: true 654 | optional: true 655 | 656 | /esbuild-freebsd-arm64/0.13.12: 657 | resolution: {integrity: sha512-F6LmI2Q1gii073kmBE3NOTt/6zLL5zvZsxNLF8PMAwdHc+iBhD1vzfI8uQZMJA1IgXa3ocr3L3DJH9fLGXy6Yw==} 658 | cpu: [arm64] 659 | os: [freebsd] 660 | requiresBuild: true 661 | dev: true 662 | optional: true 663 | 664 | /esbuild-linux-32/0.13.12: 665 | resolution: {integrity: sha512-U1UZwG3UIwF7/V4tCVAo/nkBV9ag5KJiJTt+gaCmLVWH3bPLX7y+fNlhIWZy8raTMnXhMKfaTvWZ9TtmXzvkuQ==} 666 | cpu: [ia32] 667 | os: [linux] 668 | requiresBuild: true 669 | dev: true 670 | optional: true 671 | 672 | /esbuild-linux-64/0.13.12: 673 | resolution: {integrity: sha512-YpXSwtu2NxN3N4ifJxEdsgd6Q5d8LYqskrAwjmoCT6yQnEHJSF5uWcxv783HWN7lnGpJi9KUtDvYsnMdyGw71Q==} 674 | cpu: [x64] 675 | os: [linux] 676 | requiresBuild: true 677 | dev: true 678 | optional: true 679 | 680 | /esbuild-linux-arm/0.13.12: 681 | resolution: {integrity: sha512-SyiT/JKxU6J+DY2qUiSLZJqCAftIt3uoGejZ0HDnUM2MGJqEGSGh7p1ecVL2gna3PxS4P+j6WAehCwgkBPXNIw==} 682 | cpu: [arm] 683 | os: [linux] 684 | requiresBuild: true 685 | dev: true 686 | optional: true 687 | 688 | /esbuild-linux-arm64/0.13.12: 689 | resolution: {integrity: sha512-sgDNb8kb3BVodtAlcFGgwk+43KFCYjnFOaOfJibXnnIojNWuJHpL6aQJ4mumzNWw8Rt1xEtDQyuGK9f+Y24jGA==} 690 | cpu: [arm64] 691 | os: [linux] 692 | requiresBuild: true 693 | dev: true 694 | optional: true 695 | 696 | /esbuild-linux-mips64le/0.13.12: 697 | resolution: {integrity: sha512-qQJHlZBG+QwVIA8AbTEtbvF084QgDi4DaUsUnA+EolY1bxrG+UyOuGflM2ZritGhfS/k7THFjJbjH2wIeoKA2g==} 698 | cpu: [mips64el] 699 | os: [linux] 700 | requiresBuild: true 701 | dev: true 702 | optional: true 703 | 704 | /esbuild-linux-ppc64le/0.13.12: 705 | resolution: {integrity: sha512-2dSnm1ldL7Lppwlo04CGQUpwNn5hGqXI38OzaoPOkRsBRWFBozyGxTFSee/zHFS+Pdh3b28JJbRK3owrrRgWNw==} 706 | cpu: [ppc64] 707 | os: [linux] 708 | requiresBuild: true 709 | dev: true 710 | optional: true 711 | 712 | /esbuild-netbsd-64/0.13.12: 713 | resolution: {integrity: sha512-D4raxr02dcRiQNbxOLzpqBzcJNFAdsDNxjUbKkDMZBkL54Z0vZh4LRndycdZAMcIdizC/l/Yp/ZsBdAFxc5nbA==} 714 | cpu: [x64] 715 | os: [netbsd] 716 | requiresBuild: true 717 | dev: true 718 | optional: true 719 | 720 | /esbuild-openbsd-64/0.13.12: 721 | resolution: {integrity: sha512-KuLCmYMb2kh05QuPJ+va60bKIH5wHL8ypDkmpy47lzwmdxNsuySeCMHuTv5o2Af1RUn5KLO5ZxaZeq4GEY7DaQ==} 722 | cpu: [x64] 723 | os: [openbsd] 724 | requiresBuild: true 725 | dev: true 726 | optional: true 727 | 728 | /esbuild-plugin-copy/1.2.1_esbuild@0.13.12: 729 | resolution: {integrity: sha512-DIdiDOZHGw7sA7AF41T4iiWsQGmcNkDke3wGuMJQv3PYN2m7XGJF4BuTSfu1d+Br1iGiUT9ZRwW+7VIG9+VxSg==} 730 | peerDependencies: 731 | esbuild: ^0.14.0 732 | dependencies: 733 | chalk: 4.1.2 734 | esbuild: 0.13.12 735 | fs-extra: 10.1.0 736 | globby: 11.1.0 737 | dev: true 738 | 739 | /esbuild-sunos-64/0.13.12: 740 | resolution: {integrity: sha512-jBsF+e0woK3miKI8ufGWKG3o3rY9DpHvCVRn5eburMIIE+2c+y3IZ1srsthKyKI6kkXLvV4Cf/E7w56kLipMXw==} 741 | cpu: [x64] 742 | os: [sunos] 743 | requiresBuild: true 744 | dev: true 745 | optional: true 746 | 747 | /esbuild-windows-32/0.13.12: 748 | resolution: {integrity: sha512-L9m4lLFQrFeR7F+eLZXG82SbXZfUhyfu6CexZEil6vm+lc7GDCE0Q8DiNutkpzjv1+RAbIGVva9muItQ7HVTkQ==} 749 | cpu: [ia32] 750 | os: [win32] 751 | requiresBuild: true 752 | dev: true 753 | optional: true 754 | 755 | /esbuild-windows-64/0.13.12: 756 | resolution: {integrity: sha512-k4tX4uJlSbSkfs78W5d9+I9gpd+7N95W7H2bgOMFPsYREVJs31+Q2gLLHlsnlY95zBoPQMIzHooUIsixQIBjaQ==} 757 | cpu: [x64] 758 | os: [win32] 759 | requiresBuild: true 760 | dev: true 761 | optional: true 762 | 763 | /esbuild-windows-arm64/0.13.12: 764 | resolution: {integrity: sha512-2tTv/BpYRIvuwHpp2M960nG7uvL+d78LFW/ikPItO+2GfK51CswIKSetSpDii+cjz8e9iSPgs+BU4o8nWICBwQ==} 765 | cpu: [arm64] 766 | os: [win32] 767 | requiresBuild: true 768 | dev: true 769 | optional: true 770 | 771 | /esbuild/0.13.12: 772 | resolution: {integrity: sha512-vTKKUt+yoz61U/BbrnmlG9XIjwpdIxmHB8DlPR0AAW6OdS+nBQBci6LUHU2q9WbBobMEIQxxDpKbkmOGYvxsow==} 773 | hasBin: true 774 | requiresBuild: true 775 | optionalDependencies: 776 | esbuild-android-arm64: 0.13.12 777 | esbuild-darwin-64: 0.13.12 778 | esbuild-darwin-arm64: 0.13.12 779 | esbuild-freebsd-64: 0.13.12 780 | esbuild-freebsd-arm64: 0.13.12 781 | esbuild-linux-32: 0.13.12 782 | esbuild-linux-64: 0.13.12 783 | esbuild-linux-arm: 0.13.12 784 | esbuild-linux-arm64: 0.13.12 785 | esbuild-linux-mips64le: 0.13.12 786 | esbuild-linux-ppc64le: 0.13.12 787 | esbuild-netbsd-64: 0.13.12 788 | esbuild-openbsd-64: 0.13.12 789 | esbuild-sunos-64: 0.13.12 790 | esbuild-windows-32: 0.13.12 791 | esbuild-windows-64: 0.13.12 792 | esbuild-windows-arm64: 0.13.12 793 | dev: true 794 | 795 | /escape-string-regexp/4.0.0: 796 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 797 | engines: {node: '>=10'} 798 | dev: true 799 | 800 | /eslint-config-prettier/8.8.0_eslint@8.45.0: 801 | resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} 802 | hasBin: true 803 | peerDependencies: 804 | eslint: '>=7.0.0' 805 | dependencies: 806 | eslint: 8.45.0 807 | dev: true 808 | 809 | /eslint-import-resolver-node/0.3.7: 810 | resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} 811 | dependencies: 812 | debug: 3.2.7 813 | is-core-module: 2.12.1 814 | resolve: 1.22.2 815 | transitivePeerDependencies: 816 | - supports-color 817 | dev: true 818 | 819 | /eslint-module-utils/2.8.0_h2tu665zyzp3li3mokdnt2kw74: 820 | resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} 821 | engines: {node: '>=4'} 822 | peerDependencies: 823 | '@typescript-eslint/parser': '*' 824 | eslint: '*' 825 | eslint-import-resolver-node: '*' 826 | eslint-import-resolver-typescript: '*' 827 | eslint-import-resolver-webpack: '*' 828 | peerDependenciesMeta: 829 | '@typescript-eslint/parser': 830 | optional: true 831 | eslint: 832 | optional: true 833 | eslint-import-resolver-node: 834 | optional: true 835 | eslint-import-resolver-typescript: 836 | optional: true 837 | eslint-import-resolver-webpack: 838 | optional: true 839 | dependencies: 840 | '@typescript-eslint/parser': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 841 | debug: 3.2.7 842 | eslint: 8.45.0 843 | eslint-import-resolver-node: 0.3.7 844 | transitivePeerDependencies: 845 | - supports-color 846 | dev: true 847 | 848 | /eslint-plugin-import/2.27.5_vdlnholh74uk2lamdhhi4g2qza: 849 | resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} 850 | engines: {node: '>=4'} 851 | peerDependencies: 852 | '@typescript-eslint/parser': '*' 853 | eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 854 | peerDependenciesMeta: 855 | '@typescript-eslint/parser': 856 | optional: true 857 | dependencies: 858 | '@typescript-eslint/parser': 5.62.0_uehp6ybvdwvysdrlciovnyarwm 859 | array-includes: 3.1.6 860 | array.prototype.flat: 1.3.1 861 | array.prototype.flatmap: 1.3.1 862 | debug: 3.2.7 863 | doctrine: 2.1.0 864 | eslint: 8.45.0 865 | eslint-import-resolver-node: 0.3.7 866 | eslint-module-utils: 2.8.0_h2tu665zyzp3li3mokdnt2kw74 867 | has: 1.0.3 868 | is-core-module: 2.12.1 869 | is-glob: 4.0.3 870 | minimatch: 3.1.2 871 | object.values: 1.1.6 872 | resolve: 1.22.2 873 | semver: 6.3.1 874 | tsconfig-paths: 3.14.2 875 | transitivePeerDependencies: 876 | - eslint-import-resolver-typescript 877 | - eslint-import-resolver-webpack 878 | - supports-color 879 | dev: true 880 | 881 | /eslint-plugin-prefer-arrow/1.2.3_eslint@8.45.0: 882 | resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==} 883 | peerDependencies: 884 | eslint: '>=2.0.0' 885 | dependencies: 886 | eslint: 8.45.0 887 | dev: true 888 | 889 | /eslint-plugin-prettier/4.2.1_6nuyjbnyo6rwr4pwddenxlklsi: 890 | resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} 891 | engines: {node: '>=12.0.0'} 892 | peerDependencies: 893 | eslint: '>=7.28.0' 894 | eslint-config-prettier: '*' 895 | prettier: '>=2.0.0' 896 | peerDependenciesMeta: 897 | eslint-config-prettier: 898 | optional: true 899 | dependencies: 900 | eslint: 8.45.0 901 | eslint-config-prettier: 8.8.0_eslint@8.45.0 902 | prettier: 2.8.8 903 | prettier-linter-helpers: 1.0.0 904 | dev: true 905 | 906 | /eslint-scope/5.1.1: 907 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 908 | engines: {node: '>=8.0.0'} 909 | dependencies: 910 | esrecurse: 4.3.0 911 | estraverse: 4.3.0 912 | dev: true 913 | 914 | /eslint-scope/7.2.1: 915 | resolution: {integrity: sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==} 916 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 917 | dependencies: 918 | esrecurse: 4.3.0 919 | estraverse: 5.3.0 920 | dev: true 921 | 922 | /eslint-visitor-keys/3.4.1: 923 | resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} 924 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 925 | dev: true 926 | 927 | /eslint/8.45.0: 928 | resolution: {integrity: sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==} 929 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 930 | hasBin: true 931 | dependencies: 932 | '@eslint-community/eslint-utils': 4.4.0_eslint@8.45.0 933 | '@eslint-community/regexpp': 4.5.1 934 | '@eslint/eslintrc': 2.1.0 935 | '@eslint/js': 8.44.0 936 | '@humanwhocodes/config-array': 0.11.10 937 | '@humanwhocodes/module-importer': 1.0.1 938 | '@nodelib/fs.walk': 1.2.8 939 | ajv: 6.12.6 940 | chalk: 4.1.2 941 | cross-spawn: 7.0.3 942 | debug: 4.3.4 943 | doctrine: 3.0.0 944 | escape-string-regexp: 4.0.0 945 | eslint-scope: 7.2.1 946 | eslint-visitor-keys: 3.4.1 947 | espree: 9.6.1 948 | esquery: 1.5.0 949 | esutils: 2.0.3 950 | fast-deep-equal: 3.1.3 951 | file-entry-cache: 6.0.1 952 | find-up: 5.0.0 953 | glob-parent: 6.0.2 954 | globals: 13.20.0 955 | graphemer: 1.4.0 956 | ignore: 5.2.4 957 | imurmurhash: 0.1.4 958 | is-glob: 4.0.3 959 | is-path-inside: 3.0.3 960 | js-yaml: 4.1.0 961 | json-stable-stringify-without-jsonify: 1.0.1 962 | levn: 0.4.1 963 | lodash.merge: 4.6.2 964 | minimatch: 3.1.2 965 | natural-compare: 1.4.0 966 | optionator: 0.9.3 967 | strip-ansi: 6.0.1 968 | text-table: 0.2.0 969 | transitivePeerDependencies: 970 | - supports-color 971 | dev: true 972 | 973 | /espree/9.6.1: 974 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 975 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 976 | dependencies: 977 | acorn: 8.10.0 978 | acorn-jsx: 5.3.2_acorn@8.10.0 979 | eslint-visitor-keys: 3.4.1 980 | dev: true 981 | 982 | /esquery/1.5.0: 983 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 984 | engines: {node: '>=0.10'} 985 | dependencies: 986 | estraverse: 5.3.0 987 | dev: true 988 | 989 | /esrecurse/4.3.0: 990 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 991 | engines: {node: '>=4.0'} 992 | dependencies: 993 | estraverse: 5.3.0 994 | dev: true 995 | 996 | /estraverse/4.3.0: 997 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 998 | engines: {node: '>=4.0'} 999 | dev: true 1000 | 1001 | /estraverse/5.3.0: 1002 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 1003 | engines: {node: '>=4.0'} 1004 | dev: true 1005 | 1006 | /esutils/2.0.3: 1007 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 1008 | engines: {node: '>=0.10.0'} 1009 | dev: true 1010 | 1011 | /fast-deep-equal/3.1.3: 1012 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1013 | dev: true 1014 | 1015 | /fast-diff/1.3.0: 1016 | resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} 1017 | dev: true 1018 | 1019 | /fast-glob/3.3.0: 1020 | resolution: {integrity: sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==} 1021 | engines: {node: '>=8.6.0'} 1022 | dependencies: 1023 | '@nodelib/fs.stat': 2.0.5 1024 | '@nodelib/fs.walk': 1.2.8 1025 | glob-parent: 5.1.2 1026 | merge2: 1.4.1 1027 | micromatch: 4.0.5 1028 | dev: true 1029 | 1030 | /fast-json-stable-stringify/2.1.0: 1031 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1032 | dev: true 1033 | 1034 | /fast-levenshtein/2.0.6: 1035 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 1036 | dev: true 1037 | 1038 | /fastq/1.15.0: 1039 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 1040 | dependencies: 1041 | reusify: 1.0.4 1042 | dev: true 1043 | 1044 | /file-entry-cache/6.0.1: 1045 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 1046 | engines: {node: ^10.12.0 || >=12.0.0} 1047 | dependencies: 1048 | flat-cache: 3.0.4 1049 | dev: true 1050 | 1051 | /fill-range/7.0.1: 1052 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 1053 | engines: {node: '>=8'} 1054 | dependencies: 1055 | to-regex-range: 5.0.1 1056 | dev: true 1057 | 1058 | /find-up/5.0.0: 1059 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 1060 | engines: {node: '>=10'} 1061 | dependencies: 1062 | locate-path: 6.0.0 1063 | path-exists: 4.0.0 1064 | dev: true 1065 | 1066 | /flat-cache/3.0.4: 1067 | resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} 1068 | engines: {node: ^10.12.0 || >=12.0.0} 1069 | dependencies: 1070 | flatted: 3.2.7 1071 | rimraf: 3.0.2 1072 | dev: true 1073 | 1074 | /flatted/3.2.7: 1075 | resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} 1076 | dev: true 1077 | 1078 | /for-each/0.3.3: 1079 | resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 1080 | dependencies: 1081 | is-callable: 1.2.7 1082 | dev: true 1083 | 1084 | /fs-extra/10.1.0: 1085 | resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} 1086 | engines: {node: '>=12'} 1087 | dependencies: 1088 | graceful-fs: 4.2.11 1089 | jsonfile: 6.1.0 1090 | universalify: 2.0.0 1091 | dev: true 1092 | 1093 | /fs.realpath/1.0.0: 1094 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 1095 | dev: true 1096 | 1097 | /function-bind/1.1.1: 1098 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 1099 | dev: true 1100 | 1101 | /function.prototype.name/1.1.5: 1102 | resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} 1103 | engines: {node: '>= 0.4'} 1104 | dependencies: 1105 | call-bind: 1.0.2 1106 | define-properties: 1.2.0 1107 | es-abstract: 1.21.3 1108 | functions-have-names: 1.2.3 1109 | dev: true 1110 | 1111 | /functions-have-names/1.2.3: 1112 | resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 1113 | dev: true 1114 | 1115 | /get-intrinsic/1.2.1: 1116 | resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} 1117 | dependencies: 1118 | function-bind: 1.1.1 1119 | has: 1.0.3 1120 | has-proto: 1.0.1 1121 | has-symbols: 1.0.3 1122 | dev: true 1123 | 1124 | /get-symbol-description/1.0.0: 1125 | resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 1126 | engines: {node: '>= 0.4'} 1127 | dependencies: 1128 | call-bind: 1.0.2 1129 | get-intrinsic: 1.2.1 1130 | dev: true 1131 | 1132 | /glob-parent/5.1.2: 1133 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1134 | engines: {node: '>= 6'} 1135 | dependencies: 1136 | is-glob: 4.0.3 1137 | dev: true 1138 | 1139 | /glob-parent/6.0.2: 1140 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 1141 | engines: {node: '>=10.13.0'} 1142 | dependencies: 1143 | is-glob: 4.0.3 1144 | dev: true 1145 | 1146 | /glob/7.2.3: 1147 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 1148 | dependencies: 1149 | fs.realpath: 1.0.0 1150 | inflight: 1.0.6 1151 | inherits: 2.0.4 1152 | minimatch: 3.1.2 1153 | once: 1.4.0 1154 | path-is-absolute: 1.0.1 1155 | dev: true 1156 | 1157 | /globals/13.20.0: 1158 | resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} 1159 | engines: {node: '>=8'} 1160 | dependencies: 1161 | type-fest: 0.20.2 1162 | dev: true 1163 | 1164 | /globalthis/1.0.3: 1165 | resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} 1166 | engines: {node: '>= 0.4'} 1167 | dependencies: 1168 | define-properties: 1.2.0 1169 | dev: true 1170 | 1171 | /globby/11.1.0: 1172 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 1173 | engines: {node: '>=10'} 1174 | dependencies: 1175 | array-union: 2.1.0 1176 | dir-glob: 3.0.1 1177 | fast-glob: 3.3.0 1178 | ignore: 5.2.4 1179 | merge2: 1.4.1 1180 | slash: 3.0.0 1181 | dev: true 1182 | 1183 | /gopd/1.0.1: 1184 | resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 1185 | dependencies: 1186 | get-intrinsic: 1.2.1 1187 | dev: true 1188 | 1189 | /graceful-fs/4.2.11: 1190 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 1191 | dev: true 1192 | 1193 | /graphemer/1.4.0: 1194 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 1195 | dev: true 1196 | 1197 | /has-bigints/1.0.2: 1198 | resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 1199 | dev: true 1200 | 1201 | /has-flag/4.0.0: 1202 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1203 | engines: {node: '>=8'} 1204 | dev: true 1205 | 1206 | /has-property-descriptors/1.0.0: 1207 | resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} 1208 | dependencies: 1209 | get-intrinsic: 1.2.1 1210 | dev: true 1211 | 1212 | /has-proto/1.0.1: 1213 | resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 1214 | engines: {node: '>= 0.4'} 1215 | dev: true 1216 | 1217 | /has-symbols/1.0.3: 1218 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 1219 | engines: {node: '>= 0.4'} 1220 | dev: true 1221 | 1222 | /has-tostringtag/1.0.0: 1223 | resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 1224 | engines: {node: '>= 0.4'} 1225 | dependencies: 1226 | has-symbols: 1.0.3 1227 | dev: true 1228 | 1229 | /has/1.0.3: 1230 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 1231 | engines: {node: '>= 0.4.0'} 1232 | dependencies: 1233 | function-bind: 1.1.1 1234 | dev: true 1235 | 1236 | /i18next/20.6.1: 1237 | resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==} 1238 | dependencies: 1239 | '@babel/runtime': 7.26.0 1240 | dev: true 1241 | 1242 | /ignore/5.2.4: 1243 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 1244 | engines: {node: '>= 4'} 1245 | dev: true 1246 | 1247 | /import-fresh/3.3.0: 1248 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 1249 | engines: {node: '>=6'} 1250 | dependencies: 1251 | parent-module: 1.0.1 1252 | resolve-from: 4.0.0 1253 | dev: true 1254 | 1255 | /imurmurhash/0.1.4: 1256 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1257 | engines: {node: '>=0.8.19'} 1258 | dev: true 1259 | 1260 | /inflight/1.0.6: 1261 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 1262 | dependencies: 1263 | once: 1.4.0 1264 | wrappy: 1.0.2 1265 | dev: true 1266 | 1267 | /inherits/2.0.4: 1268 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 1269 | dev: true 1270 | 1271 | /internal-slot/1.0.5: 1272 | resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} 1273 | engines: {node: '>= 0.4'} 1274 | dependencies: 1275 | get-intrinsic: 1.2.1 1276 | has: 1.0.3 1277 | side-channel: 1.0.4 1278 | dev: true 1279 | 1280 | /is-array-buffer/3.0.2: 1281 | resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} 1282 | dependencies: 1283 | call-bind: 1.0.2 1284 | get-intrinsic: 1.2.1 1285 | is-typed-array: 1.1.10 1286 | dev: true 1287 | 1288 | /is-bigint/1.0.4: 1289 | resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 1290 | dependencies: 1291 | has-bigints: 1.0.2 1292 | dev: true 1293 | 1294 | /is-boolean-object/1.1.2: 1295 | resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 1296 | engines: {node: '>= 0.4'} 1297 | dependencies: 1298 | call-bind: 1.0.2 1299 | has-tostringtag: 1.0.0 1300 | dev: true 1301 | 1302 | /is-callable/1.2.7: 1303 | resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1304 | engines: {node: '>= 0.4'} 1305 | dev: true 1306 | 1307 | /is-core-module/2.12.1: 1308 | resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} 1309 | dependencies: 1310 | has: 1.0.3 1311 | dev: true 1312 | 1313 | /is-date-object/1.0.5: 1314 | resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 1315 | engines: {node: '>= 0.4'} 1316 | dependencies: 1317 | has-tostringtag: 1.0.0 1318 | dev: true 1319 | 1320 | /is-extglob/2.1.1: 1321 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1322 | engines: {node: '>=0.10.0'} 1323 | dev: true 1324 | 1325 | /is-glob/4.0.3: 1326 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1327 | engines: {node: '>=0.10.0'} 1328 | dependencies: 1329 | is-extglob: 2.1.1 1330 | dev: true 1331 | 1332 | /is-negative-zero/2.0.2: 1333 | resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 1334 | engines: {node: '>= 0.4'} 1335 | dev: true 1336 | 1337 | /is-number-object/1.0.7: 1338 | resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 1339 | engines: {node: '>= 0.4'} 1340 | dependencies: 1341 | has-tostringtag: 1.0.0 1342 | dev: true 1343 | 1344 | /is-number/7.0.0: 1345 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1346 | engines: {node: '>=0.12.0'} 1347 | dev: true 1348 | 1349 | /is-path-inside/3.0.3: 1350 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 1351 | engines: {node: '>=8'} 1352 | dev: true 1353 | 1354 | /is-regex/1.1.4: 1355 | resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 1356 | engines: {node: '>= 0.4'} 1357 | dependencies: 1358 | call-bind: 1.0.2 1359 | has-tostringtag: 1.0.0 1360 | dev: true 1361 | 1362 | /is-shared-array-buffer/1.0.2: 1363 | resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} 1364 | dependencies: 1365 | call-bind: 1.0.2 1366 | dev: true 1367 | 1368 | /is-string/1.0.7: 1369 | resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 1370 | engines: {node: '>= 0.4'} 1371 | dependencies: 1372 | has-tostringtag: 1.0.0 1373 | dev: true 1374 | 1375 | /is-symbol/1.0.4: 1376 | resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 1377 | engines: {node: '>= 0.4'} 1378 | dependencies: 1379 | has-symbols: 1.0.3 1380 | dev: true 1381 | 1382 | /is-typed-array/1.1.10: 1383 | resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} 1384 | engines: {node: '>= 0.4'} 1385 | dependencies: 1386 | available-typed-arrays: 1.0.5 1387 | call-bind: 1.0.2 1388 | for-each: 0.3.3 1389 | gopd: 1.0.1 1390 | has-tostringtag: 1.0.0 1391 | dev: true 1392 | 1393 | /is-weakref/1.0.2: 1394 | resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 1395 | dependencies: 1396 | call-bind: 1.0.2 1397 | dev: true 1398 | 1399 | /isexe/2.0.0: 1400 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1401 | dev: true 1402 | 1403 | /js-yaml/4.1.0: 1404 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1405 | hasBin: true 1406 | dependencies: 1407 | argparse: 2.0.1 1408 | dev: true 1409 | 1410 | /json-schema-traverse/0.4.1: 1411 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1412 | dev: true 1413 | 1414 | /json-stable-stringify-without-jsonify/1.0.1: 1415 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1416 | dev: true 1417 | 1418 | /json5/1.0.2: 1419 | resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} 1420 | hasBin: true 1421 | dependencies: 1422 | minimist: 1.2.8 1423 | dev: true 1424 | 1425 | /jsonfile/6.1.0: 1426 | resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} 1427 | dependencies: 1428 | universalify: 2.0.0 1429 | optionalDependencies: 1430 | graceful-fs: 4.2.11 1431 | dev: true 1432 | 1433 | /levn/0.4.1: 1434 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1435 | engines: {node: '>= 0.8.0'} 1436 | dependencies: 1437 | prelude-ls: 1.2.1 1438 | type-check: 0.4.0 1439 | dev: true 1440 | 1441 | /locate-path/6.0.0: 1442 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1443 | engines: {node: '>=10'} 1444 | dependencies: 1445 | p-locate: 5.0.0 1446 | dev: true 1447 | 1448 | /lodash.merge/4.6.2: 1449 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1450 | dev: true 1451 | 1452 | /lru-cache/6.0.0: 1453 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1454 | engines: {node: '>=10'} 1455 | dependencies: 1456 | yallist: 4.0.0 1457 | dev: true 1458 | 1459 | /merge2/1.4.1: 1460 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1461 | engines: {node: '>= 8'} 1462 | dev: true 1463 | 1464 | /micromatch/4.0.5: 1465 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1466 | engines: {node: '>=8.6'} 1467 | dependencies: 1468 | braces: 3.0.2 1469 | picomatch: 2.3.1 1470 | dev: true 1471 | 1472 | /minimatch/3.1.2: 1473 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1474 | dependencies: 1475 | brace-expansion: 1.1.11 1476 | dev: true 1477 | 1478 | /minimist/1.2.8: 1479 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1480 | dev: true 1481 | 1482 | /moment/2.29.4: 1483 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 1484 | dev: true 1485 | 1486 | /monkey-around/3.0.0: 1487 | resolution: {integrity: sha512-jL6uY2lEAoaHxZep1cNRkCZjoIWY4g5VYCjriEWmcyHU7w8NU1+JH57xE251UVTohK0lCxMjv0ZV4ByDLIXEpw==} 1488 | dev: true 1489 | 1490 | /ms/2.1.2: 1491 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1492 | dev: true 1493 | 1494 | /ms/2.1.3: 1495 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1496 | dev: true 1497 | 1498 | /natural-compare-lite/1.4.0: 1499 | resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} 1500 | dev: true 1501 | 1502 | /natural-compare/1.4.0: 1503 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1504 | dev: true 1505 | 1506 | /object-inspect/1.12.3: 1507 | resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} 1508 | dev: true 1509 | 1510 | /object-keys/1.1.1: 1511 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1512 | engines: {node: '>= 0.4'} 1513 | dev: true 1514 | 1515 | /object.assign/4.1.4: 1516 | resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} 1517 | engines: {node: '>= 0.4'} 1518 | dependencies: 1519 | call-bind: 1.0.2 1520 | define-properties: 1.2.0 1521 | has-symbols: 1.0.3 1522 | object-keys: 1.1.1 1523 | dev: true 1524 | 1525 | /object.values/1.1.6: 1526 | resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} 1527 | engines: {node: '>= 0.4'} 1528 | dependencies: 1529 | call-bind: 1.0.2 1530 | define-properties: 1.2.0 1531 | es-abstract: 1.21.3 1532 | dev: true 1533 | 1534 | /obsidian/1.3.5_@codemirror+view@6.14.1: 1535 | resolution: {integrity: sha512-2Zg9vlaEZw6fd2AohcdrC1kV+lZcb4a1Ju6GcIwdWaGOWj6l//7wbKD6vVhO2GlfoQRGARYu++eLo7FEc+f6Tw==} 1536 | peerDependencies: 1537 | '@codemirror/state': ^6.0.0 1538 | '@codemirror/view': ^6.0.0 1539 | dependencies: 1540 | '@codemirror/view': 6.14.1 1541 | '@types/codemirror': 5.60.8 1542 | moment: 2.29.4 1543 | dev: true 1544 | 1545 | /once/1.4.0: 1546 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1547 | dependencies: 1548 | wrappy: 1.0.2 1549 | dev: true 1550 | 1551 | /optionator/0.9.3: 1552 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1553 | engines: {node: '>= 0.8.0'} 1554 | dependencies: 1555 | '@aashutoshrathi/word-wrap': 1.2.6 1556 | deep-is: 0.1.4 1557 | fast-levenshtein: 2.0.6 1558 | levn: 0.4.1 1559 | prelude-ls: 1.2.1 1560 | type-check: 0.4.0 1561 | dev: true 1562 | 1563 | /p-limit/3.1.0: 1564 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1565 | engines: {node: '>=10'} 1566 | dependencies: 1567 | yocto-queue: 0.1.0 1568 | dev: true 1569 | 1570 | /p-locate/5.0.0: 1571 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1572 | engines: {node: '>=10'} 1573 | dependencies: 1574 | p-limit: 3.1.0 1575 | dev: true 1576 | 1577 | /parent-module/1.0.1: 1578 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1579 | engines: {node: '>=6'} 1580 | dependencies: 1581 | callsites: 3.1.0 1582 | dev: true 1583 | 1584 | /parse-unit/1.0.1: 1585 | resolution: {integrity: sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==} 1586 | dev: true 1587 | 1588 | /path-exists/4.0.0: 1589 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1590 | engines: {node: '>=8'} 1591 | dev: true 1592 | 1593 | /path-is-absolute/1.0.1: 1594 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1595 | engines: {node: '>=0.10.0'} 1596 | dev: true 1597 | 1598 | /path-key/3.1.1: 1599 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1600 | engines: {node: '>=8'} 1601 | dev: true 1602 | 1603 | /path-parse/1.0.7: 1604 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1605 | dev: true 1606 | 1607 | /path-type/4.0.0: 1608 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1609 | engines: {node: '>=8'} 1610 | dev: true 1611 | 1612 | /picomatch/2.3.1: 1613 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1614 | engines: {node: '>=8.6'} 1615 | dev: true 1616 | 1617 | /prelude-ls/1.2.1: 1618 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1619 | engines: {node: '>= 0.8.0'} 1620 | dev: true 1621 | 1622 | /prettier-linter-helpers/1.0.0: 1623 | resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} 1624 | engines: {node: '>=6.0.0'} 1625 | dependencies: 1626 | fast-diff: 1.3.0 1627 | dev: true 1628 | 1629 | /prettier/2.8.8: 1630 | resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} 1631 | engines: {node: '>=10.13.0'} 1632 | hasBin: true 1633 | dev: true 1634 | 1635 | /punycode/2.3.0: 1636 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 1637 | engines: {node: '>=6'} 1638 | dev: true 1639 | 1640 | /queue-microtask/1.2.3: 1641 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1642 | dev: true 1643 | 1644 | /regenerator-runtime/0.14.1: 1645 | resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 1646 | dev: true 1647 | 1648 | /regexp.prototype.flags/1.5.0: 1649 | resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} 1650 | engines: {node: '>= 0.4'} 1651 | dependencies: 1652 | call-bind: 1.0.2 1653 | define-properties: 1.2.0 1654 | functions-have-names: 1.2.3 1655 | dev: true 1656 | 1657 | /resolve-from/4.0.0: 1658 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1659 | engines: {node: '>=4'} 1660 | dev: true 1661 | 1662 | /resolve/1.22.2: 1663 | resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} 1664 | hasBin: true 1665 | dependencies: 1666 | is-core-module: 2.12.1 1667 | path-parse: 1.0.7 1668 | supports-preserve-symlinks-flag: 1.0.0 1669 | dev: true 1670 | 1671 | /reusify/1.0.4: 1672 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1673 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1674 | dev: true 1675 | 1676 | /rimraf/3.0.2: 1677 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1678 | hasBin: true 1679 | dependencies: 1680 | glob: 7.2.3 1681 | dev: true 1682 | 1683 | /run-parallel/1.2.0: 1684 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1685 | dependencies: 1686 | queue-microtask: 1.2.3 1687 | dev: true 1688 | 1689 | /safe-regex-test/1.0.0: 1690 | resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} 1691 | dependencies: 1692 | call-bind: 1.0.2 1693 | get-intrinsic: 1.2.1 1694 | is-regex: 1.1.4 1695 | dev: true 1696 | 1697 | /semver/6.3.1: 1698 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1699 | hasBin: true 1700 | dev: true 1701 | 1702 | /semver/7.5.4: 1703 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1704 | engines: {node: '>=10'} 1705 | hasBin: true 1706 | dependencies: 1707 | lru-cache: 6.0.0 1708 | dev: true 1709 | 1710 | /shebang-command/2.0.0: 1711 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1712 | engines: {node: '>=8'} 1713 | dependencies: 1714 | shebang-regex: 3.0.0 1715 | dev: true 1716 | 1717 | /shebang-regex/3.0.0: 1718 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1719 | engines: {node: '>=8'} 1720 | dev: true 1721 | 1722 | /side-channel/1.0.4: 1723 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1724 | dependencies: 1725 | call-bind: 1.0.2 1726 | get-intrinsic: 1.2.1 1727 | object-inspect: 1.12.3 1728 | dev: true 1729 | 1730 | /slash/3.0.0: 1731 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1732 | engines: {node: '>=8'} 1733 | dev: true 1734 | 1735 | /string.prototype.trim/1.2.7: 1736 | resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} 1737 | engines: {node: '>= 0.4'} 1738 | dependencies: 1739 | call-bind: 1.0.2 1740 | define-properties: 1.2.0 1741 | es-abstract: 1.21.3 1742 | dev: true 1743 | 1744 | /string.prototype.trimend/1.0.6: 1745 | resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} 1746 | dependencies: 1747 | call-bind: 1.0.2 1748 | define-properties: 1.2.0 1749 | es-abstract: 1.21.3 1750 | dev: true 1751 | 1752 | /string.prototype.trimstart/1.0.6: 1753 | resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} 1754 | dependencies: 1755 | call-bind: 1.0.2 1756 | define-properties: 1.2.0 1757 | es-abstract: 1.21.3 1758 | dev: true 1759 | 1760 | /strip-ansi/6.0.1: 1761 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1762 | engines: {node: '>=8'} 1763 | dependencies: 1764 | ansi-regex: 5.0.1 1765 | dev: true 1766 | 1767 | /strip-bom/3.0.0: 1768 | resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} 1769 | engines: {node: '>=4'} 1770 | dev: true 1771 | 1772 | /strip-json-comments/3.1.1: 1773 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1774 | engines: {node: '>=8'} 1775 | dev: true 1776 | 1777 | /style-mod/4.0.3: 1778 | resolution: {integrity: sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==} 1779 | dev: true 1780 | 1781 | /supports-color/7.2.0: 1782 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1783 | engines: {node: '>=8'} 1784 | dependencies: 1785 | has-flag: 4.0.0 1786 | dev: true 1787 | 1788 | /supports-preserve-symlinks-flag/1.0.0: 1789 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1790 | engines: {node: '>= 0.4'} 1791 | dev: true 1792 | 1793 | /text-table/0.2.0: 1794 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1795 | dev: true 1796 | 1797 | /to-regex-range/5.0.1: 1798 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1799 | engines: {node: '>=8.0'} 1800 | dependencies: 1801 | is-number: 7.0.0 1802 | dev: true 1803 | 1804 | /to-use/0.3.3: 1805 | resolution: {integrity: sha512-i5hrYhcDyrjl4tF2EvZnmL8VAMdMYnMFeX1bIF0ekn2gKgs76UhZcHt+hLIUKh0fIii+Ix6uUp5W6yYlU5+Z8A==} 1806 | dev: true 1807 | 1808 | /tsconfig-paths/3.14.2: 1809 | resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} 1810 | dependencies: 1811 | '@types/json5': 0.0.29 1812 | json5: 1.0.2 1813 | minimist: 1.2.8 1814 | strip-bom: 3.0.0 1815 | dev: true 1816 | 1817 | /tslib/1.14.1: 1818 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 1819 | dev: true 1820 | 1821 | /tslib/2.3.1: 1822 | resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} 1823 | dev: true 1824 | 1825 | /tsutils/3.21.0_typescript@4.4.4: 1826 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 1827 | engines: {node: '>= 6'} 1828 | peerDependencies: 1829 | 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' 1830 | dependencies: 1831 | tslib: 1.14.1 1832 | typescript: 4.4.4 1833 | dev: true 1834 | 1835 | /type-check/0.4.0: 1836 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1837 | engines: {node: '>= 0.8.0'} 1838 | dependencies: 1839 | prelude-ls: 1.2.1 1840 | dev: true 1841 | 1842 | /type-fest/0.20.2: 1843 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1844 | engines: {node: '>=10'} 1845 | dev: true 1846 | 1847 | /typed-array-byte-offset/1.0.0: 1848 | resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} 1849 | engines: {node: '>= 0.4'} 1850 | dependencies: 1851 | available-typed-arrays: 1.0.5 1852 | call-bind: 1.0.2 1853 | for-each: 0.3.3 1854 | has-proto: 1.0.1 1855 | is-typed-array: 1.1.10 1856 | dev: true 1857 | 1858 | /typed-array-length/1.0.4: 1859 | resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} 1860 | dependencies: 1861 | call-bind: 1.0.2 1862 | for-each: 0.3.3 1863 | is-typed-array: 1.1.10 1864 | dev: true 1865 | 1866 | /typescript/4.4.4: 1867 | resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==} 1868 | engines: {node: '>=4.2.0'} 1869 | hasBin: true 1870 | dev: true 1871 | 1872 | /unbox-primitive/1.0.2: 1873 | resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 1874 | dependencies: 1875 | call-bind: 1.0.2 1876 | has-bigints: 1.0.2 1877 | has-symbols: 1.0.3 1878 | which-boxed-primitive: 1.0.2 1879 | dev: true 1880 | 1881 | /universalify/2.0.0: 1882 | resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} 1883 | engines: {node: '>= 10.0.0'} 1884 | dev: true 1885 | 1886 | /uri-js/4.4.1: 1887 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1888 | dependencies: 1889 | punycode: 2.3.0 1890 | dev: true 1891 | 1892 | /w3c-keyname/2.2.8: 1893 | resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} 1894 | dev: true 1895 | 1896 | /which-boxed-primitive/1.0.2: 1897 | resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 1898 | dependencies: 1899 | is-bigint: 1.0.4 1900 | is-boolean-object: 1.1.2 1901 | is-number-object: 1.0.7 1902 | is-string: 1.0.7 1903 | is-symbol: 1.0.4 1904 | dev: true 1905 | 1906 | /which-typed-array/1.1.10: 1907 | resolution: {integrity: sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==} 1908 | engines: {node: '>= 0.4'} 1909 | dependencies: 1910 | available-typed-arrays: 1.0.5 1911 | call-bind: 1.0.2 1912 | for-each: 0.3.3 1913 | gopd: 1.0.1 1914 | has-tostringtag: 1.0.0 1915 | is-typed-array: 1.1.10 1916 | dev: true 1917 | 1918 | /which/2.0.2: 1919 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1920 | engines: {node: '>= 8'} 1921 | hasBin: true 1922 | dependencies: 1923 | isexe: 2.0.0 1924 | dev: true 1925 | 1926 | /wonka/6.3.4: 1927 | resolution: {integrity: sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==} 1928 | dev: true 1929 | 1930 | /wrappy/1.0.2: 1931 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1932 | dev: true 1933 | 1934 | /yallist/4.0.0: 1935 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1936 | dev: true 1937 | 1938 | /yocto-queue/0.1.0: 1939 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1940 | engines: {node: '>=10'} 1941 | dev: true 1942 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { around } from "monkey-around"; 2 | import { 3 | App, 4 | debounce, 5 | EphemeralState, 6 | HoverParent, 7 | ItemView, 8 | MarkdownPreviewRenderer, 9 | MarkdownPreviewRendererStatic, 10 | MarkdownPreviewView, 11 | MarkdownView, 12 | Menu, 13 | parseLinktext, 14 | Platform, 15 | Plugin, 16 | PopoverState, 17 | requireApiVersion, 18 | setIcon, 19 | setTooltip, 20 | TAbstractFile, 21 | TFile, 22 | View, 23 | ViewState, 24 | Workspace, 25 | WorkspaceContainer, 26 | WorkspaceItem, 27 | WorkspaceLeaf, 28 | } from "obsidian"; 29 | 30 | import { onLinkHover } from "./onLinkHover"; 31 | import { PerWindowComponent, use } from "@ophidian/core"; 32 | import { HoverEditorParent, HoverEditor, isHoverLeaf, setMouseCoords } from "./popover"; 33 | import { DEFAULT_SETTINGS, HoverEditorSettings, SettingTab } from "./settings/settings"; 34 | import { snapActivePopover, snapDirections, restoreActivePopover, minimizeActivePopover } from "./utils/measure"; 35 | import { Scope } from "@interactjs/types"; 36 | import interactStatic from "@nothingislost/interactjs"; 37 | import { isA } from "./utils/misc"; 38 | 39 | class Interactor extends PerWindowComponent { 40 | interact = this.createInteractor(); 41 | plugin = this.use(HoverEditorPlugin); 42 | 43 | createInteractor() { 44 | if (this.win === window) return interactStatic; 45 | const oldScope = (interactStatic as unknown as { scope: Scope }).scope; 46 | const newScope = new (oldScope.constructor as new () => Scope)(); 47 | const interact = newScope.init(this.win).interactStatic; 48 | for (const plugin of oldScope._plugins.list) interact.use(plugin); 49 | return interact; 50 | } 51 | 52 | onload() { 53 | this.win.addEventListener("resize", this.plugin.debouncedPopoverReflow); 54 | } 55 | 56 | onunload() { 57 | this.win.removeEventListener("resize", this.plugin.debouncedPopoverReflow); 58 | try { 59 | this.interact.removeDocument(this.win.document); 60 | } catch (e) { 61 | // Sometimes, interact.removeDocument fails when the plugin unloads in 0.14.x: 62 | // Don't let it stop the plugin from fully unloading 63 | console.error(e); 64 | } 65 | } 66 | } 67 | 68 | export default class HoverEditorPlugin extends Plugin { 69 | use = use.plugin(this); 70 | interact = this.use(Interactor); 71 | settings: HoverEditorSettings; 72 | 73 | settingsTab: SettingTab; 74 | 75 | async onload() { 76 | this.registerActivePopoverHandler(); 77 | this.registerFileRenameHandler(); 78 | this.registerContextMenuHandler(); 79 | this.registerCommands(); 80 | this.patchUnresolvedGraphNodeHover(); 81 | this.patchWorkspace(); 82 | this.patchQuickSwitcher(); 83 | this.patchWorkspaceLeaf(); 84 | this.patchItemView(); 85 | this.patchMarkdownPreviewRenderer(); 86 | this.patchMarkdownPreviewView(); 87 | 88 | await this.loadSettings(); 89 | this.registerSettingsTab(); 90 | 91 | this.app.workspace.onLayoutReady(() => { 92 | this.patchSlidingPanes(); 93 | this.patchLinkHover(); 94 | setTimeout(() => { 95 | // workaround to ensure our plugin shows up properly within Style Settings 96 | this.app.workspace.trigger("css-change"); 97 | }, 2000); 98 | }); 99 | } 100 | 101 | get activePopovers(): HoverEditor[] { 102 | return HoverEditor.activePopovers(); 103 | } 104 | 105 | patchWorkspaceLeaf() { 106 | this.register( 107 | around(WorkspaceLeaf.prototype, { 108 | getRoot(old) { 109 | return function () { 110 | const top = old.call(this); 111 | return top.getRoot === this.getRoot ? top : top.getRoot(); 112 | }; 113 | }, 114 | onResize(old) { 115 | return function () { 116 | this.view?.onResize(); 117 | }; 118 | }, 119 | setViewState(old) { 120 | return async function (viewState: ViewState, eState?: unknown) { 121 | const result = await old.call(this, viewState, eState); 122 | // try and catch files that are opened from outside of the 123 | // HoverEditor class so that we can update the popover title bar 124 | try { 125 | const he = HoverEditor.forLeaf(this); 126 | if (he) { 127 | if (viewState.type) he.hoverEl.setAttribute("data-active-view-type", viewState.type); 128 | const titleEl = he.hoverEl.querySelector(".popover-title"); 129 | if (titleEl) { 130 | titleEl.textContent = this.view?.getDisplayText(); 131 | if (this.view?.file?.path) { 132 | titleEl.setAttribute("data-path", this.view.file.path); 133 | } else { 134 | titleEl.removeAttribute("data-path"); 135 | } 136 | } 137 | } 138 | } catch {} 139 | return result; 140 | }; 141 | }, 142 | setEphemeralState(old) { 143 | return function (state: EphemeralState) { 144 | old.call(this, state); 145 | if (state.focus && this.view?.getViewType() === "empty") { 146 | // Force empty (no-file) view to have focus so dialogs don't reset active pane 147 | this.view.contentEl.tabIndex = -1; 148 | this.view.contentEl.focus(); 149 | } 150 | }; 151 | }, 152 | }), 153 | ); 154 | this.register( 155 | around(WorkspaceItem.prototype, { 156 | getContainer(old) { 157 | return function () { 158 | if (!old) return; // 0.14.x doesn't have this 159 | if (!this.parentSplit || this instanceof WorkspaceContainer) return old.call(this); 160 | return this.parentSplit.getContainer(); 161 | }; 162 | }, 163 | }) 164 | ); 165 | } 166 | 167 | patchQuickSwitcher() { 168 | const plugin = this; 169 | const { QuickSwitcherModal } = this.app.internalPlugins.plugins.switcher.instance; 170 | const uninstaller = around(QuickSwitcherModal.prototype, { 171 | open(old) { 172 | return function () { 173 | const result = old.call(this); 174 | if (this.instructionsEl) { 175 | // Obsidian 1.6 deletes existing instructions on setInstructions(), 176 | // so patch the element to not empty(); setTimeout will remove the 177 | // patch once the current event is over 178 | setTimeout(around(this.instructionsEl, { 179 | empty(next) { 180 | return () => {}; 181 | } 182 | }), 0); 183 | } 184 | this.setInstructions([ 185 | { 186 | command: Platform.isMacOS ? "cmd p" : "ctrl p", 187 | purpose: "to open in new popover", 188 | }, 189 | ]); 190 | this.scope.register(["Mod"], "p", (event: KeyboardEvent) => { 191 | this.close(); 192 | const item = this.chooser.values[this.chooser.selectedItem]; 193 | if (!item?.file) return; 194 | const newLeaf = plugin.spawnPopover(undefined, () => 195 | this.app.workspace.setActiveLeaf(newLeaf, false, true), 196 | ); 197 | newLeaf.openFile(item.file); 198 | return false; 199 | }); 200 | return result; 201 | }; 202 | }, 203 | }); 204 | this.register(uninstaller); 205 | } 206 | 207 | patchItemView() { 208 | const plugin = this; 209 | // Once 0.15.3+ is min. required Obsidian, this can be simplified to View + "onPaneMenu" 210 | const [cls, method] = View.prototype["onPaneMenu"] ? [View, "onPaneMenu"] : [ItemView, "onMoreOptionsMenu"]; 211 | const uninstaller = around(cls.prototype, { 212 | [method](old: (menu: Menu, ...args: unknown[]) => void) { 213 | return function (this: View, menu: Menu, ...args: unknown[]) { 214 | const popover = this.leaf ? HoverEditor.forLeaf(this.leaf) : undefined; 215 | if (!popover) { 216 | menu.addItem(item => { 217 | item 218 | .setIcon("popup-open") 219 | .setTitle("Open in Hover Editor") 220 | .onClick(async () => { 221 | const newLeaf = plugin.spawnPopover(), {autoFocus} = plugin.settings; 222 | await newLeaf.setViewState({...this.leaf.getViewState(), active: autoFocus}, {focus: autoFocus}); 223 | if (autoFocus) { 224 | await sleep(200) 225 | this.app.workspace.setActiveLeaf(newLeaf, {focus: true}); 226 | } 227 | }) 228 | .setSection?.("open"); 229 | }); 230 | menu.addItem(item => { 231 | item 232 | .setIcon("popup-open") 233 | .setTitle("Convert to Hover Editor") 234 | .onClick(() => { 235 | plugin.convertLeafToPopover(this.leaf); 236 | }) 237 | .setSection?.("open"); 238 | }); 239 | } else { 240 | menu.addItem(item => { 241 | item 242 | .setIcon("popup-open") 243 | .setTitle("Dock Hover Editor to workspace") 244 | .onClick(() => { 245 | plugin.dockPopoverToWorkspace(this.leaf); 246 | }) 247 | .setSection?.("open"); 248 | }); 249 | } 250 | return old.call(this, menu, ...args); 251 | }; 252 | }, 253 | }); 254 | this.register(uninstaller); 255 | 256 | // Restore pre-1.6 view header icons so you can drag hover editor leaves back to the workspace 257 | this.register(around(ItemView.prototype, { 258 | load(old) { 259 | return function(this: View) { 260 | if (!this.iconEl) { 261 | const iconEl = this.iconEl = this.headerEl.createDiv("clickable-icon view-header-icon") 262 | this.headerEl.prepend(iconEl) 263 | iconEl.draggable = true 264 | iconEl.addEventListener("dragstart", e => { this.app.workspace.onDragLeaf(e, this.leaf) }) 265 | setIcon(iconEl, this.getIcon()) 266 | setTooltip(iconEl, "Drag to rearrange") 267 | } 268 | return old.call(this) 269 | } 270 | } 271 | })) 272 | } 273 | 274 | patchMarkdownPreviewView() { 275 | // Prevent erratic scrolling of preview views when workspace layout changes 276 | this.register(around(MarkdownPreviewView.prototype, { 277 | onResize(old) { 278 | return function onResize() { 279 | this.renderer.onResize(); 280 | if (this.view.scroll !== null && this.view.scroll !== this.getScroll()) { 281 | this.renderer.applyScrollDelayed(this.view.scroll) 282 | } 283 | } 284 | } 285 | })) 286 | } 287 | 288 | patchMarkdownPreviewRenderer() { 289 | const plugin = this; 290 | const uninstaller = around(MarkdownPreviewRenderer as MarkdownPreviewRendererStatic, { 291 | registerDomEvents(old: Function) { 292 | return function ( 293 | el: HTMLElement, 294 | instance: { getFile?(): TFile; hoverParent?: HoverParent, info?: HoverParent & { getFile(): TFile} }, 295 | ...args: unknown[] 296 | ) { 297 | el?.on("mouseover", ".internal-embed.is-loaded", (event: MouseEvent, targetEl: HTMLElement) => { 298 | if (targetEl && plugin.settings.hoverEmbeds) { 299 | app.workspace.trigger("hover-link", { 300 | event: event, 301 | source: targetEl.matchParent(".markdown-source-view") ? "editor" : "preview", 302 | hoverParent: instance.hoverParent ?? instance.info, 303 | targetEl: targetEl, 304 | linktext: targetEl.getAttribute("src"), 305 | sourcePath: (instance.info ?? instance).getFile?.()?.path || "", 306 | }); 307 | } 308 | }); 309 | return old.call(this, el, instance, ...args); 310 | }; 311 | }, 312 | }); 313 | this.register(uninstaller); 314 | } 315 | 316 | patchWorkspace() { 317 | let layoutChanging = false; 318 | const uninstaller = around(Workspace.prototype, { 319 | changeLayout(old) { 320 | return async function (workspace: unknown) { 321 | layoutChanging = true; 322 | try { 323 | // Don't consider hover popovers part of the workspace while it's changing 324 | await old.call(this, workspace); 325 | } finally { 326 | layoutChanging = false; 327 | } 328 | }; 329 | }, 330 | recordHistory(old) { 331 | return function (leaf: WorkspaceLeaf, pushHistory: boolean, ...args: unknown[]) { 332 | const paneReliefLoaded = this.app.plugins.plugins["pane-relief"]?._loaded; 333 | if (!paneReliefLoaded && isHoverLeaf(leaf)) return; 334 | return old.call(this, leaf, pushHistory, ...args); 335 | }; 336 | }, 337 | iterateLeaves(old) { 338 | type leafIterator = (item: WorkspaceLeaf) => boolean | void; 339 | return function (arg1, arg2) { 340 | // Fast exit if desired leaf found 341 | if (old.call(this, arg1, arg2)) return true; 342 | 343 | // Handle old/new API parameter swap 344 | let cb: leafIterator = (typeof arg1 === "function" ? arg1 : arg2) as leafIterator; 345 | let parent: WorkspaceItem = (typeof arg1 === "function" ? arg2 : arg1) as WorkspaceItem; 346 | 347 | if (!parent) return false; // <- during app startup, rootSplit can be null 348 | if (layoutChanging) return false; // Don't let HEs close during workspace change 349 | 350 | // 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory: 351 | if (parent === app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { 352 | for(const popover of HoverEditor.popoversForWindow((parent as WorkspaceContainer).win)) { 353 | // Use old API here for compat w/0.14.x 354 | if (old.call(this, cb, popover.rootSplit)) return true; 355 | } 356 | } 357 | return false; 358 | }; 359 | }, 360 | getDropLocation(old) { 361 | return function getDropLocation(event: MouseEvent) { 362 | for (const popover of HoverEditor.activePopovers()) { 363 | const dropLoc: any = this.recursiveGetTarget(event, popover.rootSplit); 364 | if (dropLoc) { 365 | if (requireApiVersion && requireApiVersion("0.15.3")) { 366 | // getDropLocation's return signature changed in 0.15.3 367 | // it now only returns the target 368 | return dropLoc; 369 | } else { 370 | return { target: dropLoc, sidedock: false }; 371 | } 372 | } 373 | } 374 | return old.call(this, event); 375 | }; 376 | }, 377 | onDragLeaf(old) { 378 | return function (event: MouseEvent, leaf: WorkspaceLeaf) { 379 | const hoverPopover = HoverEditor.forLeaf(leaf); 380 | hoverPopover?.togglePin(true); 381 | return old.call(this, event, leaf); 382 | }; 383 | }, 384 | }); 385 | this.register(uninstaller); 386 | } 387 | 388 | patchSlidingPanes() { 389 | const SlidingPanesPlugin = this.app.plugins.plugins["sliding-panes-obsidian"]?.constructor; 390 | if (SlidingPanesPlugin) { 391 | const uninstaller = around(SlidingPanesPlugin.prototype, { 392 | handleFileOpen(old: Function) { 393 | return function (...args: unknown[]) { 394 | // sliding panes needs to ignore popover open events or else it freaks out 395 | if (isHoverLeaf(this.app.workspace.activeLeaf)) return; 396 | return old.call(this, ...args); 397 | }; 398 | }, 399 | handleLayoutChange(old: Function) { 400 | return function (...args: unknown[]) { 401 | // sliding panes needs to ignore popovers or else it activates the wrong pane 402 | if (isHoverLeaf(this.app.workspace.activeLeaf)) return; 403 | return old.call(this, ...args); 404 | }; 405 | }, 406 | focusActiveLeaf(old: Function) { 407 | return function (...args: unknown[]) { 408 | // sliding panes tries to add popovers to the root split if we don't exclude them 409 | if (isHoverLeaf(this.app.workspace.activeLeaf)) return; 410 | return old.call(this, ...args); 411 | }; 412 | }, 413 | }); 414 | this.register(uninstaller); 415 | } 416 | } 417 | 418 | patchLinkHover() { 419 | const plugin = this; 420 | const pagePreviewPlugin = this.app.internalPlugins.plugins["page-preview"]; 421 | if (!pagePreviewPlugin.enabled) return; 422 | const uninstaller = around(pagePreviewPlugin.instance.constructor.prototype, { 423 | onHoverLink(old: Function) { 424 | return function (options: { event: MouseEvent }, ...args: unknown[]) { 425 | if (options && isA(options.event, MouseEvent)) setMouseCoords(options.event); 426 | return old.call(this, options, ...args); 427 | }; 428 | }, 429 | onLinkHover(old: Function) { 430 | return function ( 431 | parent: HoverEditorParent, 432 | targetEl: HTMLElement, 433 | linkText: string, 434 | path: string, 435 | state: EphemeralState, 436 | ...args: unknown[] 437 | ) { 438 | const {subpath} = parseLinktext(linkText); 439 | if (subpath && subpath[0] === "#") { 440 | if (subpath.startsWith("#[^")) { 441 | if (plugin.settings.footnotes !== "always") { 442 | return old.call(this, parent, targetEl, linkText, path, state, ...args); 443 | } 444 | } else if (subpath.startsWith("#^")) { 445 | if (plugin.settings.blocks !== "always") { 446 | return old.call(this, parent, targetEl, linkText, path, state, ...args); 447 | } 448 | } else { 449 | if (plugin.settings.headings !== "always") { 450 | return old.call(this, parent, targetEl, linkText, path, state, ...args); 451 | } 452 | } 453 | } 454 | onLinkHover(plugin, parent, targetEl, linkText, path, state, ...args); 455 | }; 456 | }, 457 | }); 458 | this.register(uninstaller); 459 | 460 | // This will recycle the event handlers so that they pick up the patched onLinkHover method 461 | pagePreviewPlugin.disable(); 462 | pagePreviewPlugin.enable(); 463 | 464 | plugin.register(function () { 465 | if (!pagePreviewPlugin.enabled) return; 466 | pagePreviewPlugin.disable(); 467 | pagePreviewPlugin.enable(); 468 | }); 469 | } 470 | 471 | registerContextMenuHandler() { 472 | this.registerEvent( 473 | this.app.workspace.on("file-menu", (menu: Menu, file: TAbstractFile, source: string, leaf?: WorkspaceLeaf) => { 474 | const popover = leaf ? HoverEditor.forLeaf(leaf) : undefined; 475 | if (file instanceof TFile && !popover && !leaf) { 476 | menu.addItem(item => { 477 | item 478 | .setIcon("popup-open") 479 | .setTitle("Open in Hover Editor") 480 | .onClick(() => { 481 | const newLeaf = this.spawnPopover(); 482 | newLeaf.openFile(file); 483 | }) 484 | .setSection?.("open"); 485 | }); 486 | } 487 | }), 488 | ); 489 | } 490 | 491 | registerActivePopoverHandler() { 492 | this.registerEvent( 493 | this.app.workspace.on("active-leaf-change", leaf => { 494 | HoverEditor.activePopover?.hoverEl.removeClass("is-active"); 495 | const hoverEditor = (HoverEditor.activePopover = leaf ? HoverEditor.forLeaf(leaf) : undefined); 496 | if (hoverEditor && leaf) { 497 | hoverEditor.activate(); 498 | hoverEditor.hoverEl.addClass("is-active"); 499 | const titleEl = hoverEditor.hoverEl.querySelector(".popover-title"); 500 | if (!titleEl) return; 501 | titleEl.textContent = leaf.view?.getDisplayText(); 502 | if (leaf?.view?.getViewType()) { 503 | hoverEditor.hoverEl.setAttribute("data-active-view-type", leaf.view.getViewType()); 504 | } 505 | if (leaf.view?.file?.path) { 506 | titleEl.setAttribute("data-path", leaf.view.file.path); 507 | } else { 508 | titleEl.removeAttribute("data-path"); 509 | } 510 | } 511 | }), 512 | ); 513 | } 514 | 515 | registerFileRenameHandler() { 516 | this.app.vault.on("rename", (file, oldPath) => { 517 | HoverEditor.iteratePopoverLeaves(this.app.workspace, leaf => { 518 | if (file === leaf?.view?.file && file instanceof TFile) { 519 | const hoverEditor = HoverEditor.forLeaf(leaf); 520 | if (hoverEditor?.hoverEl) { 521 | const titleEl = hoverEditor.hoverEl.querySelector(".popover-title"); 522 | if (!titleEl) return; 523 | const filePath = titleEl.getAttribute("data-path"); 524 | if (oldPath === filePath) { 525 | titleEl.textContent = leaf.view?.getDisplayText(); 526 | titleEl.setAttribute("data-path", file.path); 527 | } 528 | } 529 | } 530 | }); 531 | }); 532 | } 533 | 534 | debouncedPopoverReflow = debounce( 535 | () => { 536 | HoverEditor.activePopovers().forEach(popover => { 537 | popover.interact?.reflow({ name: "drag", axis: "xy" }); 538 | }); 539 | }, 540 | 100, 541 | true, 542 | ); 543 | 544 | patchUnresolvedGraphNodeHover() { 545 | const leaf = new (WorkspaceLeaf as new (app: App) => WorkspaceLeaf)(this.app); 546 | const view = this.app.internalPlugins.plugins.graph.views.localgraph(leaf); 547 | const GraphEngine = view.engine.constructor; 548 | leaf.detach(); // close the view 549 | view.renderer?.worker?.terminate(); // ensure the worker is terminated 550 | const uninstall = around(GraphEngine.prototype, { 551 | onNodeHover(old: Function) { 552 | return function (event: UIEvent, linkText: string, nodeType: string, ...items: unknown[]) { 553 | if (nodeType === "unresolved") { 554 | if ((this.onNodeUnhover(), isA(event, MouseEvent))) { 555 | if ( 556 | this.hoverPopover && 557 | this.hoverPopover.state !== PopoverState.Hidden && 558 | this.lastHoverLink === linkText 559 | ) { 560 | this.hoverPopover.onTarget = true; 561 | return void this.hoverPopover.transition(); 562 | } 563 | this.lastHoverLink = linkText; 564 | this.app.workspace.trigger("hover-link", { 565 | event: event, 566 | source: "graph", 567 | hoverParent: this, 568 | targetEl: null, 569 | linktext: linkText, 570 | }); 571 | } 572 | } else { 573 | return old.call(this, event, linkText, nodeType, ...items); 574 | } 575 | }; 576 | }, 577 | }); 578 | this.register(uninstall); 579 | leaf.detach(); 580 | } 581 | 582 | onunload(): void { 583 | HoverEditor.activePopovers().forEach(popover => popover.hide()); 584 | } 585 | 586 | async loadSettings() { 587 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 588 | } 589 | 590 | async saveSettings() { 591 | await this.saveData(this.settings); 592 | } 593 | 594 | registerCommands() { 595 | this.addCommand({ 596 | id: "bounce-popovers", 597 | name: "Toggle bouncing popovers", 598 | callback: () => { 599 | this.activePopovers.forEach(popover => { 600 | popover.toggleBounce(); 601 | }); 602 | }, 603 | }); 604 | this.addCommand({ 605 | id: "open-new-popover", 606 | name: "Open new Hover Editor", 607 | callback: () => { 608 | // Focus the leaf after it's shown 609 | const newLeaf = this.spawnPopover(undefined, () => this.app.workspace.setActiveLeaf(newLeaf, false, true)); 610 | }, 611 | }); 612 | this.addCommand({ 613 | id: "open-link-in-new-popover", 614 | name: "Open link under cursor in new Hover Editor", 615 | checkCallback: (checking: boolean) => { 616 | const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); 617 | if (activeView) { 618 | if (!checking) { 619 | const token = activeView.editor.getClickableTokenAt(activeView.editor.getCursor()); 620 | if (token?.type === "internal-link") { 621 | const newLeaf = this.spawnPopover(undefined, () => 622 | this.app.workspace.setActiveLeaf(newLeaf, false, true), 623 | ); 624 | newLeaf.openLinkText(token.text, activeView.file.path); 625 | } 626 | } 627 | return true; 628 | } 629 | return false; 630 | }, 631 | }); 632 | this.addCommand({ 633 | id: "open-current-file-in-new-popover", 634 | name: "Open current file in new Hover Editor", 635 | checkCallback: (checking: boolean) => { 636 | const activeFile = this.app.workspace.activeEditor?.file ?? this.app.workspace.getActiveFile(); 637 | if (activeFile) { 638 | if (!checking) { 639 | const newLeaf = this.spawnPopover(undefined, () => this.app.workspace.setActiveLeaf(newLeaf, false, true)); 640 | newLeaf.openFile(activeFile); 641 | } 642 | return true; 643 | } 644 | return false; 645 | }, 646 | }); 647 | this.addCommand({ 648 | id: "convert-active-pane-to-popover", 649 | name: "Convert active pane to Hover Editor", 650 | checkCallback: (checking: boolean) => { 651 | const { activeLeaf } = this.app.workspace; 652 | if (activeLeaf) { 653 | if (!checking) { 654 | this.convertLeafToPopover(activeLeaf); 655 | } 656 | return true; 657 | } 658 | return false; 659 | }, 660 | }); 661 | this.addCommand({ 662 | id: "dock-active-popover-to-workspace", 663 | name: "Dock active Hover Editor to workspace", 664 | checkCallback: (checking: boolean) => { 665 | const { activeLeaf } = this.app.workspace; 666 | if (activeLeaf && HoverEditor.forLeaf(activeLeaf)) { 667 | if (!checking) { 668 | this.dockPopoverToWorkspace(activeLeaf); 669 | } 670 | return true; 671 | } 672 | return false; 673 | }, 674 | }); 675 | this.addCommand({ 676 | id: `restore-active-popover`, 677 | name: `Restore active Hover Editor`, 678 | checkCallback: (checking: boolean) => { 679 | return restoreActivePopover(checking); 680 | }, 681 | }); 682 | this.addCommand({ 683 | id: `minimize-active-popover`, 684 | name: `Minimize active Hover Editor`, 685 | checkCallback: (checking: boolean) => { 686 | return minimizeActivePopover(checking); 687 | }, 688 | }); 689 | snapDirections.forEach(direction => { 690 | this.addCommand({ 691 | id: `snap-active-popover-to-${direction}`, 692 | name: `Snap active Hover Editor to ${direction}`, 693 | checkCallback: (checking: boolean) => { 694 | return snapActivePopover(direction, checking); 695 | }, 696 | }); 697 | }); 698 | } 699 | 700 | convertLeafToPopover(oldLeaf: WorkspaceLeaf) { 701 | if (!oldLeaf) return; 702 | const newLeaf = this.spawnPopover(undefined, () => { 703 | const { parentSplit: newParentSplit } = newLeaf; 704 | const { parentSplit: oldParentSplit } = oldLeaf; 705 | oldParentSplit.removeChild(oldLeaf); 706 | newParentSplit.replaceChild(0, oldLeaf, true); 707 | this.app.workspace.setActiveLeaf(oldLeaf, {focus: true}); 708 | }); 709 | return newLeaf; 710 | } 711 | 712 | dockPopoverToWorkspace(oldLeaf: WorkspaceLeaf) { 713 | if (!oldLeaf) return; 714 | oldLeaf.parentSplit.removeChild(oldLeaf); 715 | const {rootSplit} = this.app.workspace; 716 | // Add to first pane/tab group 717 | this.app.workspace.iterateLeaves(rootSplit, leaf => { 718 | leaf.parentSplit.insertChild(-1, oldLeaf) 719 | return true 720 | }) 721 | this.app.workspace.activeLeaf = null; // Force re-activation 722 | this.app.workspace.setActiveLeaf(oldLeaf, {focus: true}); 723 | return oldLeaf; 724 | } 725 | 726 | spawnPopover(initiatingEl?: HTMLElement, onShowCallback?: () => unknown): WorkspaceLeaf { 727 | const parent = this.app.workspace.activeLeaf as unknown as HoverEditorParent; 728 | if (!initiatingEl) initiatingEl = parent.containerEl; 729 | const hoverPopover = new HoverEditor(parent, initiatingEl!, this, undefined, onShowCallback); 730 | hoverPopover.togglePin(true); 731 | return hoverPopover.attachLeaf(); 732 | } 733 | 734 | registerSettingsTab() { 735 | this.settingsTab = new SettingTab(this.app, this); 736 | this.addSettingTab(this.settingsTab); 737 | } 738 | } 739 | 740 | export function genId(size: number) { 741 | const chars = []; 742 | for (let n = 0; n < size; n++) chars.push(((16 * Math.random()) | 0).toString(16)); 743 | return chars.join(""); 744 | } 745 | -------------------------------------------------------------------------------- /src/onLinkHover.ts: -------------------------------------------------------------------------------- 1 | import { EphemeralState, PopoverState, Platform } from "obsidian"; 2 | 3 | import HoverEditorPlugin from "./main"; 4 | import { HoverEditorParent, HoverEditor } from "./popover"; 5 | import { isA } from "./utils/misc"; 6 | 7 | const targetPops = new WeakMap(); 8 | 9 | export function onLinkHover( 10 | plugin: HoverEditorPlugin, 11 | parent: HoverEditorParent, 12 | targetEl: HTMLElement, 13 | linkText: string, 14 | path: string, 15 | oldState: EphemeralState, 16 | ...args: unknown[] 17 | ) { 18 | // Tweak the targetEl for calendar to point to the table cell instead of the actual day, 19 | // so the link won't be broken when the day div is recreated by calendar refreshing 20 | if (targetEl && targetEl.matches('.workspace-leaf-content[data-type="calendar"] table.calendar td > div')) 21 | targetEl = targetEl.parentElement!; 22 | 23 | if (oldState && "scroll" in oldState && !("line" in oldState) && targetEl && targetEl.matches(".search-result-file-match")) { 24 | oldState.line = oldState.scroll; 25 | delete oldState.scroll; 26 | } 27 | 28 | // Workaround for bookmarks through 1.3.0 29 | if (targetEl && targetEl.matches(".bookmark .tree-item-inner")) { 30 | if (parent && (parent as any).innerEl === targetEl) { 31 | parent = (parent as any).tree as HoverEditorParent; 32 | } 33 | targetEl = targetEl.parentElement ?? targetEl; 34 | } 35 | 36 | const prevPopover = targetPops.has(targetEl) ? targetPops.get(targetEl) : parent.hoverPopover; 37 | if (prevPopover?.lockedOut) return; 38 | 39 | const parentHasExistingPopover = 40 | prevPopover && 41 | prevPopover.state !== PopoverState.Hidden && 42 | // Don't keep the old popover if manually pinned (so you can tear off multiples) 43 | (!prevPopover.isPinned || plugin.settings.autoPin === "always") && 44 | prevPopover.targetEl !== null && 45 | prevPopover.originalLinkText === linkText && 46 | prevPopover.originalPath === path && 47 | targetEl && 48 | prevPopover.adopt(targetEl); 49 | 50 | if (parentHasExistingPopover) { 51 | targetPops.set(targetEl, prevPopover); 52 | } else { 53 | const editor = new HoverEditor(parent, targetEl, plugin, plugin.settings.triggerDelay); 54 | if (targetEl) targetPops.set(targetEl, editor); 55 | editor.originalLinkText = linkText; 56 | editor.originalPath = path; 57 | parent.hoverPopover = editor; 58 | const controller = editor.abortController!; 59 | 60 | const unlock = function () { 61 | if (!editor) return; 62 | editor.lockedOut = false; 63 | }; 64 | 65 | const onMouseDown = function (event: MouseEvent) { 66 | if (!editor) return; 67 | if (isA(event.target, HTMLElement) && !event.target.closest(".hover-editor, .menu")) { 68 | editor.state = PopoverState.Hidden; 69 | editor.hide(); 70 | editor.lockedOut = true; 71 | setTimeout(unlock, 1000); 72 | } 73 | }; 74 | 75 | const { document } = editor; 76 | 77 | // to prevent mod based keyboard shortcuts from accidentally triggering popovers 78 | const onKeyUp = function (event: KeyboardEvent) { 79 | if (!editor) return; 80 | const modKey = Platform.isMacOS ? "Meta" : "Control"; 81 | if (!editor.onHover && editor.state !== PopoverState.Shown && event.key !== modKey) { 82 | editor.state = PopoverState.Hidden; 83 | editor.hide(); 84 | editor.lockedOut = true; 85 | setTimeout(unlock, 1000); 86 | } else { 87 | document.body.removeEventListener("keyup", onKeyUp, true); 88 | } 89 | }; 90 | 91 | document.addEventListener("pointerdown", onMouseDown, true); 92 | document.addEventListener("mousedown", onMouseDown, true); 93 | document.body.addEventListener("keyup", onKeyUp, true); 94 | controller.register(() => { 95 | document.removeEventListener("pointerdown", onMouseDown, true); 96 | document.removeEventListener("mousedown", onMouseDown, true); 97 | document.body.removeEventListener("keyup", onKeyUp, true); 98 | }); 99 | 100 | setTimeout(() => { 101 | if (editor?.state == PopoverState.Hidden) { 102 | return; 103 | } 104 | editor?.openLink(linkText, path, oldState); 105 | }, 0); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/popover.ts: -------------------------------------------------------------------------------- 1 | import type { ActionMap } from "@interactjs/core/scope"; 2 | import type { Modifier } from "@interactjs/modifiers/base"; 3 | import type { Interactable, InteractEvent, Interaction, ResizeEvent } from "@interactjs/types"; 4 | import { around } from "monkey-around"; 5 | import { 6 | Component, 7 | EphemeralState, 8 | HoverPopover, 9 | MarkdownEditView, 10 | OpenViewState, 11 | parseLinktext, 12 | PopoverState, 13 | MousePos, 14 | requireApiVersion, 15 | resolveSubpath, 16 | setIcon, 17 | TFile, 18 | View, 19 | Workspace, 20 | WorkspaceLeaf, 21 | WorkspaceSplit, 22 | EmptyView, 23 | MarkdownView, 24 | WorkspaceTabs, 25 | debounce, 26 | } from "obsidian"; 27 | 28 | import HoverEditorPlugin, { genId } from "./main"; 29 | import { 30 | restorePopover, 31 | calculateOffsets, 32 | storeDimensions, 33 | snapToEdge, 34 | expandContract, 35 | dragMoveListener, 36 | } from "./utils/measure"; 37 | import { isA } from "./utils/misc"; 38 | 39 | const popovers = new WeakMap(); 40 | export interface HoverEditorParent { 41 | hoverPopover: HoverEditor | null; 42 | containerEl?: HTMLElement; 43 | view?: View; 44 | dom?: HTMLElement; 45 | } 46 | type ConstructableWorkspaceSplit = new (ws: Workspace, dir: "horizontal"|"vertical") => WorkspaceSplit; 47 | 48 | let mouseCoords: MousePos = { x: 0, y: 0 }; 49 | 50 | function nosuper(base: new (...args: unknown[]) => T): new () => T { 51 | const derived = function () { 52 | return Object.setPrototypeOf(new Component, new.target.prototype); 53 | }; 54 | derived.prototype = base.prototype; 55 | return Object.setPrototypeOf(derived, base); 56 | } 57 | 58 | const layers = new WeakMap>(); 59 | 60 | export class HoverEditor extends nosuper(HoverPopover) { 61 | onTarget: boolean; 62 | 63 | onHover: boolean; 64 | 65 | shownPos: MousePos | null; 66 | 67 | isPinned: boolean = this.plugin.settings.autoPin === "always" ? true : false; 68 | 69 | isDragging: boolean; 70 | 71 | isResizing: boolean; 72 | 73 | interact?: Interactable; 74 | 75 | lockedOut: boolean; 76 | 77 | abortController? = this.addChild(new Component()); 78 | 79 | detaching = false; 80 | 81 | opening = false; 82 | 83 | rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(window.app.workspace, "vertical"); 84 | 85 | targetRect = this.targetEl?.getBoundingClientRect(); 86 | 87 | pinEl: HTMLElement; 88 | 89 | titleEl: HTMLElement; 90 | 91 | containerEl: HTMLElement; 92 | 93 | hideNavBarEl: HTMLElement; 94 | 95 | viewHeaderHeight: number; 96 | 97 | oldPopover = this.parent?.hoverPopover; 98 | 99 | document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document; 100 | 101 | interactStatic = this.plugin.interact.forDom(this.document.body).interact; 102 | 103 | constrainAspectRatio: boolean; 104 | 105 | id = genId(8); 106 | 107 | resizeModifiers: Modifier[]; 108 | 109 | dragElementRect: { top: number; left: number; bottom: number; right: number }; 110 | 111 | onMouseIn: (event: MouseEvent) => void; 112 | 113 | onMouseOut: (event: MouseEvent) => void; 114 | 115 | xspeed: number; 116 | 117 | yspeed: number; 118 | 119 | bounce?: NodeJS.Timeout; 120 | 121 | boundOnZoomOut: () => void; 122 | 123 | originalPath: string; // these are kept to avoid adopting targets w/a different link 124 | originalLinkText: string; 125 | 126 | static activePopover?: HoverEditor; 127 | 128 | static activeWindows() { 129 | const windows: Window[] = [window]; 130 | const { floatingSplit } = app.workspace; 131 | if (floatingSplit) { 132 | for (const split of floatingSplit.children) { 133 | if (split.win) windows.push(split.win); 134 | } 135 | } 136 | return windows; 137 | } 138 | 139 | static containerForDocument(doc: Document) { 140 | if (doc !== document && app.workspace.floatingSplit) 141 | for (const container of app.workspace.floatingSplit.children) { 142 | if (container.doc === doc) return container; 143 | } 144 | return app.workspace.rootSplit; 145 | } 146 | 147 | static activePopovers() { 148 | return this.activeWindows().flatMap(this.popoversForWindow); 149 | } 150 | 151 | static popoversForWindow(win?: Window) { 152 | return (Array.prototype.slice.call(win?.document?.body.querySelectorAll(".hover-popover") ?? []) as HTMLElement[]) 153 | .map(el => popovers.get(el)!) 154 | .filter(he => he); 155 | } 156 | 157 | static forLeaf(leaf: WorkspaceLeaf | undefined) { 158 | // leaf can be null such as when right clicking on an internal link 159 | const el = leaf && document.body.matchParent.call(leaf.containerEl, ".hover-popover"); // work around matchParent race condition 160 | return el ? popovers.get(el) : undefined; 161 | } 162 | 163 | static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) { 164 | for (const popover of this.activePopovers()) { 165 | if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true; 166 | } 167 | return false; 168 | } 169 | 170 | hoverEl: HTMLElement = this.document.defaultView!.createDiv({ 171 | cls: "popover hover-popover", 172 | attr: { id: "he" + this.id }, 173 | }); 174 | 175 | constructor( 176 | parent: HoverEditorParent, 177 | public targetEl: HTMLElement, 178 | public plugin: HoverEditorPlugin, 179 | waitTime?: number, 180 | public onShowCallback?: () => unknown, 181 | ) { 182 | // 183 | super(); 184 | 185 | if (waitTime === undefined) { 186 | waitTime = 300; 187 | } 188 | this.onTarget = true; 189 | this.onHover = false; 190 | this.shownPos = null; 191 | this.parent = parent; 192 | this.waitTime = waitTime; 193 | this.state = PopoverState.Showing; 194 | const { hoverEl } = this; 195 | this.onMouseIn = this._onMouseIn.bind(this); 196 | this.onMouseOut = this._onMouseOut.bind(this); 197 | this.abortController!.load(); 198 | 199 | if (targetEl) { 200 | targetEl.addEventListener("mouseover", this.onMouseIn); 201 | targetEl.addEventListener("mouseout", this.onMouseOut); 202 | } 203 | 204 | hoverEl.addEventListener("mouseover", event => { 205 | if (mouseIsOffTarget(event, hoverEl)) { 206 | this.onHover = true; 207 | this.onTarget = false; 208 | this.transition(); 209 | } 210 | }); 211 | hoverEl.addEventListener("mouseout", event => { 212 | if (mouseIsOffTarget(event, hoverEl)) { 213 | this.onHover = false; 214 | this.onTarget = false; 215 | this.transition(); 216 | } 217 | }); 218 | this.timer = window.setTimeout(this.show.bind(this), waitTime); 219 | this.document.addEventListener("mousemove", setMouseCoords); 220 | 221 | // custom logic begin 222 | popovers.set(this.hoverEl, this); 223 | this.hoverEl.addClass("hover-editor"); 224 | this.containerEl = this.hoverEl.createDiv("popover-content"); 225 | this.buildWindowControls(); 226 | this.setInitialDimensions(); 227 | const pinEl = (this.pinEl = this.document.defaultView!.createEl("a", "popover-header-icon mod-pin-popover")); 228 | this.titleEl.prepend(this.pinEl); 229 | pinEl.onclick = () => { 230 | this.togglePin(); 231 | }; 232 | setIcon(pinEl, "lucide-pin"); 233 | this.createResizeHandles(); 234 | if (this.plugin.settings.imageZoom) this.registerZoomImageHandlers(); 235 | } 236 | 237 | adopt(targetEl: HTMLElement) { 238 | if (this.targetEl === targetEl) return true; 239 | const bounds = targetEl?.getBoundingClientRect(); 240 | if (overlaps(this.targetRect, bounds)) { 241 | this.targetEl.removeEventListener("mouseover", this.onMouseIn); 242 | this.targetEl.removeEventListener("mouseout", this.onMouseOut); 243 | targetEl.addEventListener("mouseover", this.onMouseIn); 244 | targetEl.addEventListener("mouseout", this.onMouseOut); 245 | this.targetEl = targetEl; 246 | this.targetRect = bounds; 247 | const { x, y } = mouseCoords; 248 | this.onTarget = overlaps(bounds, { left: x, right: x, top: y, bottom: y } as DOMRect); 249 | this.transition(); 250 | return true; 251 | } else { 252 | this.onTarget = false; 253 | this.transition(); 254 | } 255 | return false; 256 | } 257 | 258 | activate = debounce(() => { 259 | const {win} = this.document; 260 | let layer = layers.get(win); 261 | layer || layers.set(win, layer = new Set()); 262 | layer.delete(this); 263 | layer.add(this); 264 | win.requestAnimationFrame(() => { 265 | let zIndex = 41; 266 | Array.from(layer!).reverse().forEach(he => { 267 | he.hoverEl.style.setProperty("--he-popover-layer-inactive", ""+zIndex); 268 | if (zIndex > 31) zIndex--; 269 | }) 270 | }) 271 | }, 100); 272 | 273 | onZoomOut() { 274 | this.document.body.removeEventListener("mouseup", this.boundOnZoomOut); 275 | this.document.body.removeEventListener("dragend", this.boundOnZoomOut); 276 | if (this.hoverEl.hasClass("do-not-restore")) { 277 | this.hoverEl.removeClass("do-not-restore"); 278 | } else { 279 | restorePopover(this.hoverEl); 280 | } 281 | } 282 | 283 | onZoomIn(event: MouseEvent) { 284 | if (event.button !== 0) { 285 | return; 286 | } 287 | if (this.hoverEl.hasClass("snap-to-viewport")) { 288 | this.hoverEl.addClass("do-not-restore"); 289 | } 290 | this.document.body.addEventListener("mouseup", this.boundOnZoomOut, { 291 | once: true, 292 | }); 293 | this.document.body.addEventListener("dragend", this.boundOnZoomOut, { 294 | once: true, 295 | }); 296 | const offset = calculateOffsets(this.document); 297 | storeDimensions(this.hoverEl); 298 | snapToEdge(this.hoverEl, "viewport", offset); 299 | return false; 300 | } 301 | 302 | registerZoomImageHandlers() { 303 | this.hoverEl.addClass("image-zoom"); 304 | this.boundOnZoomOut = this.onZoomOut.bind(this); 305 | this.hoverEl.on("mousedown", "img", this.onZoomIn.bind(this)); 306 | } 307 | 308 | togglePin(value?: boolean) { 309 | this.activate(); 310 | if (value === undefined) { 311 | value = !this.isPinned; 312 | } 313 | if (value) this.abortController?.unload(); 314 | this.hoverEl.toggleClass("is-pinned", value); 315 | this.pinEl.toggleClass("is-active", value); 316 | this.isPinned = value; 317 | } 318 | 319 | getDefaultMode() { 320 | return this.parent?.view?.getMode ? this.parent.view.getMode() : "preview"; 321 | } 322 | 323 | updateLeaves() { 324 | if (this.onTarget && this.targetEl && !this.document.contains(this.targetEl)) { 325 | this.onTarget = false; 326 | this.transition(); 327 | } 328 | let leafCount = 0; 329 | this.plugin.app.workspace.iterateLeaves(leaf => { 330 | leafCount++; 331 | }, this.rootSplit); 332 | if (leafCount === 0) { 333 | this.hide(); // close if we have no leaves 334 | } else if (leafCount > 1) { 335 | this.toggleConstrainAspectRatio(false); 336 | } 337 | this.hoverEl.setAttribute("data-leaf-count", leafCount.toString()); 338 | } 339 | 340 | get headerHeight() { 341 | const hoverEl = this.hoverEl; 342 | return this.titleEl.getBoundingClientRect().bottom - hoverEl.getBoundingClientRect().top; 343 | } 344 | 345 | toggleMinimized() { 346 | this.activate(); 347 | const hoverEl = this.hoverEl; 348 | const headerHeight = this.headerHeight; 349 | 350 | if (!hoverEl.hasAttribute("data-restore-height")) { 351 | if (this.plugin.settings.rollDown) expandContract(hoverEl, false); 352 | hoverEl.setAttribute("data-restore-height", String(hoverEl.offsetHeight)); 353 | hoverEl.style.minHeight = headerHeight + "px"; 354 | hoverEl.style.maxHeight = headerHeight + "px"; 355 | hoverEl.toggleClass("is-minimized", true); 356 | } else { 357 | const restoreHeight = hoverEl.getAttribute("data-restore-height"); 358 | if (restoreHeight) { 359 | hoverEl.removeAttribute("data-restore-height"); 360 | hoverEl.style.height = restoreHeight + "px"; 361 | } 362 | hoverEl.style.removeProperty("max-height"); 363 | hoverEl.toggleClass("is-minimized", false); 364 | if (this.plugin.settings.rollDown) expandContract(hoverEl, true); 365 | } 366 | this.interact?.reflow({ name: "drag", axis: "xy" }); 367 | } 368 | 369 | attachLeaf(): WorkspaceLeaf { 370 | this.rootSplit.getRoot = () => app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!; 371 | this.rootSplit.getContainer = () => HoverEditor.containerForDocument(this.document); 372 | this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl); 373 | // Obsidian 1.8.7 changed createLeafInParent to make new leaves active, so we have to stop it :-( 374 | const remove = around(this.plugin.app.workspace, {setActiveLeaf() { return () => {}; }}) 375 | let leaf: WorkspaceLeaf 376 | try { 377 | leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0); 378 | } finally { 379 | remove() 380 | } 381 | this.updateLeaves(); 382 | return leaf; 383 | } 384 | 385 | onload(): void { 386 | super.onload(); 387 | this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this)); 388 | this.registerEvent(app.workspace.on("layout-change", () => { 389 | // Ensure that top-level items in a popover are not tabbed 390 | this.rootSplit.children.forEach((item, index) => { 391 | if (item instanceof WorkspaceTabs) { 392 | this.rootSplit.replaceChild(index, item.children[0]); 393 | } 394 | }) 395 | })); 396 | } 397 | 398 | leaves() { 399 | const leaves: WorkspaceLeaf[] = []; 400 | this.plugin.app.workspace.iterateLeaves(leaf => { 401 | leaves.push(leaf); 402 | }, this.rootSplit); 403 | return leaves; 404 | } 405 | 406 | setInitialDimensions() { 407 | this.hoverEl.style.height = this.plugin.settings.initialHeight; 408 | this.hoverEl.style.width = this.plugin.settings.initialWidth; 409 | } 410 | 411 | adjustHeight(byPx: number) { 412 | this.hoverEl.style.height = (this.hoverEl.offsetHeight) + byPx + "px"; 413 | } 414 | 415 | toggleViewHeader(value?: boolean, initial?: boolean) { 416 | this.activate(); 417 | if (value === undefined) value = !this.hoverEl.hasClass("show-navbar"); 418 | this.hideNavBarEl?.toggleClass("is-active", value); 419 | this.hoverEl.toggleClass("show-navbar", value); 420 | const viewHeaderEl = this.hoverEl.querySelector(".view-header"); 421 | if (!viewHeaderEl || initial) return; 422 | const calculatedViewHeaderHeight = parseFloat( 423 | getComputedStyle(viewHeaderEl).getPropertyValue("--he-view-header-height"), 424 | ); 425 | this.hoverEl.style.transition = "height 0.2s"; 426 | this.adjustHeight(value ? calculatedViewHeaderHeight : -calculatedViewHeaderHeight); 427 | setTimeout(() => { 428 | this.hoverEl.style.removeProperty("transition"); 429 | }, 200); 430 | 431 | this.requestLeafMeasure(); 432 | } 433 | 434 | buildWindowControls() { 435 | this.titleEl = this.document.defaultView!.createDiv("popover-titlebar"); 436 | this.titleEl.createDiv("popover-title"); 437 | const popoverActions = this.titleEl.createDiv("popover-actions"); 438 | const hideNavBarEl = (this.hideNavBarEl = popoverActions.createEl("a", "popover-action mod-show-navbar")); 439 | setIcon(hideNavBarEl, "sidebar-open"); 440 | hideNavBarEl.addEventListener("click", event => { 441 | this.toggleViewHeader(); 442 | }); 443 | if (this.plugin.settings.showViewHeader) { 444 | this.toggleViewHeader(true, true); 445 | } 446 | const minEl = popoverActions.createEl("a", "popover-action mod-minimize"); 447 | setIcon(minEl, "minus"); 448 | minEl.addEventListener("click", event => { 449 | restorePopover(this.hoverEl); 450 | this.toggleMinimized(); 451 | }); 452 | const maxEl = popoverActions.createEl("a", "popover-action mod-maximize"); 453 | setIcon(maxEl, "maximize"); 454 | maxEl.addEventListener("click", event => { 455 | this.activate(); 456 | if (this.hoverEl.hasClass("snap-to-viewport")) { 457 | setIcon(maxEl, "maximize"); 458 | restorePopover(this.hoverEl); 459 | return; 460 | } 461 | setIcon(maxEl, "minimize"); 462 | const offset = calculateOffsets(this.document); 463 | storeDimensions(this.hoverEl); 464 | snapToEdge(this.hoverEl, "viewport", offset); 465 | }); 466 | 467 | const closeEl = popoverActions.createEl("a", "popover-action mod-close"); 468 | setIcon(closeEl, "x"); 469 | closeEl.addEventListener("click", event => { 470 | this.hide(); 471 | }); 472 | this.containerEl.prepend(this.titleEl); 473 | } 474 | 475 | requestLeafMeasure() { 476 | // address view height measurement issues triggered by css transitions 477 | // we wait a bit for the transition to finish and remeasure 478 | const leaves = this.leaves(); 479 | if (leaves.length) { 480 | setTimeout(() => { 481 | leaves.forEach(leaf => leaf.onResize()); 482 | }, 200); 483 | } 484 | } 485 | 486 | onShow() { 487 | // Once we've been open for closeDelay, use the closeDelay as a hiding timeout 488 | const { closeDelay } = this.plugin.settings; 489 | setTimeout(() => (this.waitTime = closeDelay), closeDelay); 490 | 491 | this.oldPopover?.hide(); 492 | this.oldPopover = null; 493 | 494 | this.activate(); 495 | this.hoverEl.toggleClass("is-new", true); 496 | 497 | this.document.body.addEventListener( 498 | "click", 499 | () => { 500 | this.hoverEl.toggleClass("is-new", false); 501 | }, 502 | { once: true, capture: true }, 503 | ); 504 | 505 | if (this.parent) { 506 | this.parent.hoverPopover = this; 507 | } 508 | 509 | // Workaround until 0.15.7 510 | if (requireApiVersion("0.15.1") && !requireApiVersion("0.15.7")) 511 | app.workspace.iterateLeaves(leaf => { if (leaf.view instanceof MarkdownView) (leaf.view.editMode as any).reinit?.(); }, this.rootSplit); 512 | 513 | this.togglePin(this.isPinned); 514 | 515 | this.onShowCallback?.(); 516 | this.onShowCallback = undefined; // only call it once 517 | } 518 | 519 | startBounce() { 520 | this.bounce = setTimeout(() => { 521 | this.hoverEl.style.left = parseFloat(this.hoverEl.style.left) + this.xspeed + "px"; 522 | this.hoverEl.style.top = parseFloat(this.hoverEl.style.top) + this.yspeed + "px"; 523 | this.checkHitBox(); 524 | this.startBounce(); 525 | }, 20); 526 | } 527 | 528 | toggleBounce() { 529 | this.xspeed = 7; 530 | this.yspeed = 7; 531 | if (this.bounce) { 532 | clearTimeout(this.bounce); 533 | this.bounce = undefined; 534 | const el = this.hoverEl.querySelector(".view-content") as HTMLElement; 535 | if (el?.style) { 536 | el.style.removeProperty("backgroundColor"); 537 | } 538 | } else { 539 | this.startBounce(); 540 | } 541 | } 542 | 543 | checkHitBox() { 544 | const x = parseFloat(this.hoverEl.style.left); 545 | const y = parseFloat(this.hoverEl.style.top); 546 | const width = parseFloat(this.hoverEl.style.width); 547 | const height = parseFloat(this.hoverEl.style.height); 548 | if (x <= 0 || x + width >= this.document.body.offsetWidth) { 549 | this.xspeed *= -1; 550 | this.pickColor(); 551 | } 552 | 553 | if (y <= 0 || y + height >= this.document.body.offsetHeight) { 554 | this.yspeed *= -1; 555 | this.pickColor(); 556 | } 557 | } 558 | 559 | pickColor() { 560 | const r = Math.random() * (254 - 0) + 0; 561 | const g = Math.random() * (254 - 0) + 0; 562 | const b = Math.random() * (254 - 0) + 0; 563 | const el = this.hoverEl.querySelector(".view-content") as HTMLElement; 564 | if (el?.style) { 565 | el.style.backgroundColor = "rgb(" + r + "," + g + ", " + b + ")"; 566 | } 567 | } 568 | 569 | transition() { 570 | if (this.shouldShow()) { 571 | if (this.state === PopoverState.Hiding) { 572 | this.state = PopoverState.Shown; 573 | clearTimeout(this.timer); 574 | } 575 | } else { 576 | if (this.state === PopoverState.Showing) { 577 | this.hide(); 578 | } else { 579 | if (this.state === PopoverState.Shown) { 580 | this.state = PopoverState.Hiding; 581 | this.timer = window.setTimeout(() => { 582 | if (this.shouldShow()) { 583 | this.transition(); 584 | } else { 585 | this.hide(); 586 | } 587 | }, this.waitTime); 588 | } 589 | } 590 | } 591 | } 592 | 593 | detect(el: HTMLElement) { 594 | // TODO: may not be needed? the mouseover/out handers handle most detection use cases 595 | const { targetEl, hoverEl } = this; 596 | 597 | if (targetEl) { 598 | this.onTarget = el === targetEl || targetEl.contains(el); 599 | } 600 | 601 | this.onHover = el === hoverEl || hoverEl.contains(el); 602 | } 603 | 604 | _onMouseIn(event: MouseEvent) { 605 | if (!(this.targetEl && !mouseIsOffTarget(event, this.targetEl))) { 606 | this.onTarget = true; 607 | this.transition(); 608 | } 609 | } 610 | 611 | _onMouseOut(event: MouseEvent) { 612 | if (!(this.targetEl && !mouseIsOffTarget(event, this.targetEl))) { 613 | this.onTarget = false; 614 | this.transition(); 615 | } 616 | } 617 | 618 | position(pos?: MousePos | null): void { 619 | // without this adjustment, the x dimension keeps sliding over to the left as you progressively mouse over files 620 | // disabling this for now since messing with pos.x like this breaks the detect() logic 621 | // if (pos && pos.x !== undefined) { 622 | // pos.x = pos.x + 20; 623 | // } 624 | 625 | // native obsidian logic 626 | if (pos === undefined) { 627 | pos = this.shownPos; 628 | } 629 | 630 | let rect; 631 | 632 | if (pos) { 633 | rect = { 634 | top: pos.y - 10, 635 | bottom: pos.y + 10, 636 | left: pos.x, 637 | right: pos.x, 638 | }; 639 | } else if (this.targetEl) { 640 | const relativePos = getRelativePos(this.targetEl, this.document.body); 641 | rect = { 642 | top: relativePos.top, 643 | bottom: relativePos.top + this.targetEl.offsetHeight, 644 | left: relativePos.left, 645 | right: relativePos.left + this.targetEl.offsetWidth, 646 | }; 647 | } else { 648 | rect = { 649 | top: 0, 650 | bottom: 0, 651 | left: 0, 652 | right: 0, 653 | }; 654 | } 655 | 656 | this.document.body.appendChild(this.hoverEl); 657 | positionEl(rect, this.hoverEl, { gap: 10 }, this.document); 658 | 659 | // custom hover editor logic 660 | if (pos) { 661 | // give positionEl a chance to adjust the position before we read the coords 662 | setTimeout(() => { 663 | const left = parseFloat(this.hoverEl.style.left); 664 | const top = parseFloat(this.hoverEl.style.top); 665 | this.hoverEl.setAttribute("data-x", String(left)); 666 | this.hoverEl.setAttribute("data-y", String(top)); 667 | }, 0); 668 | } 669 | } 670 | 671 | shouldShow() { 672 | return this.shouldShowSelf() || this.shouldShowChild(); 673 | } 674 | 675 | shouldShowChild(): boolean { 676 | return HoverEditor.activePopovers().some(popover => { 677 | if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) { 678 | return popover.shouldShow(); 679 | } 680 | return false; 681 | }); 682 | } 683 | 684 | shouldShowSelf() { 685 | // Don't let obsidian show() us if we've already started closing 686 | // return !this.detaching && (this.onTarget || this.onHover); 687 | return ( 688 | !this.detaching && 689 | !!( 690 | this.onTarget || 691 | this.onHover || 692 | (this.state == PopoverState.Shown && this.isPinned) || 693 | this.document.querySelector(`body>.modal-container, body > #he${this.id} ~ .menu, body > #he${this.id} ~ .suggestion-container`) 694 | ) 695 | ); 696 | } 697 | 698 | calculateMinSize() { 699 | return { width: 40, height: this.headerHeight }; 700 | } 701 | 702 | calculateBoundaries(x: number, y: number, interaction: Interaction) { 703 | const bodyEl = interaction.element.closest("body"); 704 | const boundaryEl = bodyEl?.querySelector(".workspace") || bodyEl?.querySelector(".workspace-window"); 705 | return boundaryEl?.getBoundingClientRect(); 706 | } 707 | 708 | calculateMaxSize(x: number, y: number, interaction: Interaction) { 709 | return { width: this.document.body.offsetWidth, height: this.document.body.offsetHeight }; 710 | } 711 | 712 | toggleConstrainAspectRatio(value?: boolean, ratio?: number) { 713 | const aspectRatioMod = this.resizeModifiers.find(mod => mod.name == "aspectRatio"); 714 | if (!aspectRatioMod) return; 715 | if (value === undefined) value = !aspectRatioMod.options.enabled; 716 | if (value) { 717 | aspectRatioMod.enable(); 718 | this.constrainAspectRatio = true; 719 | if (ratio !== undefined && aspectRatioMod.options.ratio !== ratio) { 720 | aspectRatioMod.options.ratio = ratio; 721 | } 722 | } else { 723 | aspectRatioMod.disable(); 724 | this.constrainAspectRatio = false; 725 | } 726 | } 727 | 728 | registerInteract() { 729 | const viewPortBounds: HTMLElement = this.document.querySelector("div.app-container, div.workspace-split")!; 730 | const self = this; 731 | const calculateBoundaryRestriction = function ( 732 | eventX: number, 733 | eventY: number, 734 | interaction: Interaction, 735 | ) { 736 | const { top, right, bottom, left, x, y, width, height } = viewPortBounds.getBoundingClientRect(); 737 | const boundingRect = { top, right, bottom, left, x, y, width, height }; 738 | if (interaction.pointerType === "reflow") { 739 | // if we're reflowing, we want to keep the window fully inside the viewport 740 | self.dragElementRect.bottom = 1; 741 | } else { 742 | self.dragElementRect.bottom = 0; 743 | } 744 | if (self.plugin.settings.snapToEdges) { 745 | boundingRect.top = top - 30; 746 | boundingRect.bottom = bottom - self.headerHeight; 747 | } else { 748 | boundingRect.bottom = bottom - self.headerHeight; 749 | } 750 | return boundingRect; 751 | }; 752 | let firstMovement = true; 753 | let windowChromeHeight: number; 754 | const imgRatio = this.hoverEl.dataset?.imgRatio ? parseFloat(this.hoverEl.dataset?.imgRatio) : undefined; 755 | this.resizeModifiers = [ 756 | this.interactStatic.modifiers.restrictEdges({ 757 | outer: self.calculateBoundaries.bind(this), 758 | }), 759 | this.interactStatic.modifiers.restrictSize({ 760 | min: self.calculateMinSize.bind(this), 761 | max: self.calculateMaxSize.bind(this), 762 | }), 763 | this.interactStatic.modifiers.aspectRatio({ 764 | ratio: imgRatio || "preserve", 765 | enabled: false, 766 | }), 767 | ]; 768 | this.dragElementRect = { top: 0, left: 1, bottom: 0, right: 0 }; 769 | const dragModifiers = [ 770 | this.interactStatic.modifiers.restrict({ 771 | restriction: calculateBoundaryRestriction, 772 | offset: { top: 0, left: 40, bottom: 0, right: 40 }, 773 | elementRect: this.dragElementRect, 774 | endOnly: false, 775 | }), 776 | ]; 777 | if (this.constrainAspectRatio && imgRatio !== undefined) { 778 | this.toggleConstrainAspectRatio(true, imgRatio); 779 | } 780 | const i = this.interactStatic(this.hoverEl) 781 | .preventDefault("always") 782 | 783 | .on("doubletap", this.onDoubleTap.bind(this)) 784 | 785 | .draggable({ 786 | // inertiajs has a core lib memory leak currently. leave disabled 787 | // inertia: false, 788 | modifiers: dragModifiers, 789 | allowFrom: ".popover-titlebar", 790 | 791 | listeners: { 792 | start(event: DragEvent) { 793 | // only auto pin if the drag with user initiated 794 | // this avoids a reflow causing an auto pin 795 | if (event.buttons) self.togglePin(true); 796 | if (event.buttons && isA(event.target, HTMLElement)) { 797 | event.target.addClass("is-dragging"); 798 | self.activate(); 799 | } 800 | }, 801 | end(event: DragEvent) { 802 | if (isA(event.target, HTMLElement)) { 803 | event.target.removeClass("is-dragging"); 804 | } 805 | }, 806 | move: dragMoveListener.bind(self), 807 | }, 808 | }) 809 | .resizable({ 810 | edges: { 811 | top: ".top-left, .top-right, .top", 812 | left: ".top-left, .bottom-left, .left", 813 | bottom: ".bottom-left, .bottom-right, .bottom", 814 | right: ".top-right, .bottom-right, .right", 815 | }, 816 | modifiers: this.resizeModifiers, 817 | listeners: { 818 | start(event: ResizeEvent) { 819 | const viewEl = event.target as HTMLElement; 820 | self.activate(); 821 | viewEl.style.removeProperty("max-height"); 822 | const viewHeaderHeight = (self.hoverEl.querySelector(".view-header") as HTMLElement)?.offsetHeight; 823 | const titlebarHeight = self.titleEl.offsetHeight; 824 | 825 | windowChromeHeight = titlebarHeight + viewHeaderHeight; 826 | firstMovement = true; 827 | // only auto pin if the drag with user initiated 828 | // this avoids a reflow causing an auto pin 829 | if (event.buttons) self.togglePin(true); 830 | }, 831 | move: function (event: ResizeEvent) { 832 | if (!event?.deltaRect || !event.edges) return; 833 | const { target } = event; 834 | let { x, y } = target.dataset; 835 | 836 | let height = event.rect.height; 837 | let width = event.rect.width; 838 | 839 | x = x ? x : target.style.left; 840 | y = y ? y : target.style.top; 841 | 842 | x = String((parseFloat(x) || 0) + event.deltaRect?.left); 843 | y = String((parseFloat(y) || 0) + event.deltaRect?.top); 844 | 845 | if (self.constrainAspectRatio && imgRatio && event.buttons !== undefined) { 846 | // don't run if this was an automated resize (ie. reflow) 847 | if (firstMovement) { 848 | // adjustments to compensate for the titlebar height 849 | if (event.edges.top && (event.edges.right || event.edges.left)) { 850 | y = String(parseFloat(y) - windowChromeHeight); 851 | } else if (event.edges.top) { 852 | x = String(parseFloat(x) + windowChromeHeight * imgRatio); 853 | } else if (event.edges.left && !(event.edges.top || event.edges.bottom)) { 854 | y = String(parseFloat(y) - windowChromeHeight); 855 | } 856 | } 857 | 858 | firstMovement = false; 859 | 860 | if (event.edges.top && !(event.edges.right || event.edges.left)) { 861 | height = height - windowChromeHeight; 862 | width = width - windowChromeHeight * imgRatio; 863 | } else if (event.edges.bottom && !(event.edges.right || event.edges.left)) { 864 | height = height - windowChromeHeight; 865 | width = width - windowChromeHeight * imgRatio; 866 | } 867 | 868 | height = height + windowChromeHeight; 869 | 870 | if (target.hasClass("snap-to-left") || target.hasClass("snap-to-right")) { 871 | y = String(parseFloat(target.style.top)); 872 | x = String(parseFloat(target.style.left)); 873 | } 874 | } else { 875 | if (imgRatio && height > this?.document?.body.offsetHeight) { 876 | height = height / 1.5; 877 | width = height * imgRatio; 878 | } 879 | } 880 | 881 | Object.assign(target.style, { 882 | width: `${width}px`, 883 | height: `${height}px`, 884 | top: `${y}px`, 885 | left: x === "NaN" ? "unset" : `${x}px`, 886 | }); 887 | 888 | Object.assign(target.dataset, { x, y }); 889 | }, 890 | end: function (event: ResizeEvent) { 891 | if (event.rect.height > self.headerHeight) { 892 | event.target.removeAttribute("data-restore-height"); 893 | } 894 | i.reflow({ name: "drag", axis: "xy" }); 895 | }, 896 | }, 897 | }); 898 | this.interact = i; 899 | } 900 | 901 | createResizeHandles() { 902 | this.hoverEl.createDiv("resize-handle bottom-left"); 903 | this.hoverEl.createDiv("resize-handle bottom-right"); 904 | this.hoverEl.createDiv("resize-handle top-left"); 905 | this.hoverEl.createDiv("resize-handle top-right"); 906 | this.hoverEl.createDiv("resize-handle right"); 907 | this.hoverEl.createDiv("resize-handle left"); 908 | this.hoverEl.createDiv("resize-handle bottom"); 909 | this.hoverEl.createDiv("resize-handle top"); 910 | } 911 | 912 | onDoubleTap(event: InteractEvent) { 913 | if (event.target.tagName === "DIV" && event.target.closest(".popover-titlebar")) { 914 | event.preventDefault(); 915 | this.togglePin(true); 916 | this.toggleMinimized(); 917 | } 918 | } 919 | 920 | show() { 921 | // native obsidian logic start 922 | if (!this.targetEl || this.document.body.contains(this.targetEl)) { 923 | this.state = PopoverState.Shown; 924 | this.timer = 0; 925 | this.shownPos = mouseCoords; 926 | this.position(mouseCoords); 927 | this.document.removeEventListener("mousemove", setMouseCoords); 928 | this.onShow(); 929 | app.workspace.onLayoutChange(); 930 | // initializingHoverPopovers.remove(this); 931 | // activeHoverPopovers.push(this); 932 | // initializePopoverChecker(); 933 | this.load(); 934 | } else { 935 | this.hide(); 936 | } 937 | // native obsidian logic end 938 | 939 | // if this is an image view, set the dimensions to the natural dimensions of the image 940 | // an interactjs reflow will be triggered to constrain the image to the viewport if it's 941 | // too large 942 | if (this.hoverEl.dataset.imgHeight && this.hoverEl.dataset.imgWidth) { 943 | this.hoverEl.style.height = parseFloat(this.hoverEl.dataset.imgHeight) + this.titleEl.offsetHeight + "px"; 944 | this.hoverEl.style.width = parseFloat(this.hoverEl.dataset.imgWidth) + "px"; 945 | } 946 | this.registerInteract(); 947 | this.interact?.reflow({ 948 | name: "resize", 949 | edges: { right: true, bottom: true }, 950 | }); 951 | this.interact?.reflow({ name: "drag", axis: "xy" }); 952 | } 953 | 954 | onHide() { 955 | this.oldPopover = null; 956 | if (this.parent?.hoverPopover === this) { 957 | this.parent.hoverPopover = null; 958 | } 959 | } 960 | 961 | hide() { 962 | this.onTarget = this.onHover = false; 963 | this.isPinned = false; 964 | this.detaching = true; 965 | // Once we reach this point, we're committed to closing 966 | layers.get(this.document.win)?.delete(this); 967 | 968 | // in case we didn't ever call show() 969 | this.document.removeEventListener("mousemove", setMouseCoords); 970 | 971 | // A timer might be pending to call show() for the first time, make sure 972 | // it doesn't bring us back up after we close 973 | if (this.timer) { 974 | clearTimeout(this.timer); 975 | this.timer = 0; 976 | } 977 | 978 | // Hide our HTML element immediately, even if our leaves might not be 979 | // detachable yet. This makes things more responsive and improves the 980 | // odds of not showing an empty popup that's just going to disappear 981 | // momentarily. 982 | this.hoverEl.hide(); 983 | 984 | // If a file load is in progress, we need to wait until it's finished before 985 | // detaching leaves. Because we set .detaching, The in-progress openFile() 986 | // will call us again when it finishes. 987 | if (this.opening) return; 988 | 989 | const leaves = this.leaves(); 990 | if (leaves.length) { 991 | // Detach all leaves before we unload the popover and remove it from the DOM. 992 | // Each leaf.detach() will trigger layout-changed and the updateLeaves() 993 | // method will then call hide() again when the last one is gone. 994 | leaves.forEach(leaf => { 995 | // Prevent saving uninitialized fold info 996 | if (leaf.view instanceof MarkdownView && !this._loaded) leaf.view.onMarkdownFold = () => null; 997 | leaf.detach(); 998 | // Newer obsidians don't switch the active leaf until layout processing :( 999 | if (leaf === app.workspace.activeLeaf) app.workspace.activeLeaf = null; 1000 | }); 1001 | } else { 1002 | this.parent = null; 1003 | if (this.interact?.unset) this.interact.unset(); 1004 | this.abortController?.unload(); 1005 | this.abortController = undefined; 1006 | this.interact = undefined; 1007 | return this.nativeHide(); 1008 | } 1009 | } 1010 | 1011 | nativeHide() { 1012 | const { hoverEl, targetEl } = this; 1013 | 1014 | this.state = PopoverState.Hidden; 1015 | 1016 | hoverEl.detach(); 1017 | 1018 | if (targetEl) { 1019 | const parent = targetEl.matchParent(".hover-popover"); 1020 | if (parent) popovers.get(parent)?.transition(); 1021 | targetEl.removeEventListener("mouseover", this.onMouseIn); 1022 | targetEl.removeEventListener("mouseout", this.onMouseOut); 1023 | } 1024 | 1025 | this.onHide(); 1026 | this.unload(); 1027 | } 1028 | 1029 | resolveLink(linkText: string, sourcePath: string): TFile | null { 1030 | const link = parseLinktext(linkText); 1031 | const tFile = link ? this.plugin.app.metadataCache.getFirstLinkpathDest(link.path, sourcePath) : null; 1032 | return tFile; 1033 | } 1034 | 1035 | async openLink(linkText: string, sourcePath: string, eState?: EphemeralState, createInLeaf?: WorkspaceLeaf) { 1036 | let file = this.resolveLink(linkText, sourcePath); 1037 | const link = parseLinktext(linkText); 1038 | if (!file && createInLeaf) { 1039 | const folder = this.plugin.app.fileManager.getNewFileParent(sourcePath); 1040 | file = await this.plugin.app.fileManager.createNewMarkdownFile(folder, link.path); 1041 | } 1042 | if (!file) { 1043 | this.displayCreateFileAction(linkText, sourcePath, eState); 1044 | return; 1045 | } 1046 | const { viewRegistry } = this.plugin.app; 1047 | const viewType = viewRegistry.typeByExtension[file.extension]; 1048 | if (!viewType || !viewRegistry.viewByType[viewType]) { 1049 | this.displayOpenFileAction(file); 1050 | return; 1051 | } 1052 | eState = Object.assign(this.buildEphemeralState(file, link), eState); 1053 | const parentMode = this.getDefaultMode(); 1054 | const state = this.buildState(parentMode, eState); 1055 | const leaf = await this.openFile(file, state, createInLeaf); 1056 | const leafViewType = leaf?.view?.getViewType(); 1057 | if (leafViewType === "image") { 1058 | // TODO: temporary workaround to prevent image popover from disappearing immediately when using live preview 1059 | if ( 1060 | this.plugin.settings.autoFocus && 1061 | this.parent?.hasOwnProperty("editorEl") && 1062 | (this.parent as unknown as MarkdownEditView).editorEl!.hasClass("is-live-preview") 1063 | ) { 1064 | this.waitTime = 3000; 1065 | } 1066 | this.constrainAspectRatio = true; 1067 | const img = leaf!.view.contentEl.querySelector("img")!; 1068 | this.hoverEl.dataset.imgHeight = String(img.naturalHeight); 1069 | this.hoverEl.dataset.imgWidth = String(img.naturalWidth); 1070 | this.hoverEl.dataset.imgRatio = String(img.naturalWidth / img.naturalHeight); 1071 | } else if (leafViewType === "pdf") { 1072 | this.hoverEl.style.height = "800px"; 1073 | this.hoverEl.style.width = "600px"; 1074 | } 1075 | if (state.state?.mode === "source") this.whenShown(() => { 1076 | // Not sure why this is needed, but without it we get issue #186 1077 | if (requireApiVersion("1.0"))(leaf?.view as any)?.editMode?.reinit?.(); 1078 | leaf?.view?.setEphemeralState(state.eState); 1079 | }); 1080 | } 1081 | 1082 | displayOpenFileAction(file: TFile) { 1083 | const leaf = this.attachLeaf(); 1084 | const view = leaf.view! as EmptyView; 1085 | view.emptyTitleEl.hide(); 1086 | view.actionListEl.empty(); 1087 | const { actionListEl } = view; 1088 | actionListEl.createDiv({ cls: "file-embed-title" }, div => { 1089 | div.createSpan({ cls: "file-embed-icon" }, span => setIcon(span, "document")); 1090 | div.appendText(" " + file.name); 1091 | }); 1092 | actionListEl.addEventListener("click", () => this.plugin.app.openWithDefaultApp(file.path)); 1093 | actionListEl.setAttribute("aria-label", i18next.t("interface.embed-open-in-default-app-tooltip")); 1094 | } 1095 | 1096 | displayCreateFileAction(linkText: string, sourcePath: string, eState?: EphemeralState) { 1097 | const leaf = this.attachLeaf(); 1098 | const view = leaf.view as EmptyView; 1099 | if (view) { 1100 | view.emptyTitleEl?.hide(); 1101 | view.actionListEl?.empty(); 1102 | const createEl = view.actionListEl?.createEl("button", "empty-state-action"); 1103 | if (!createEl) return; 1104 | createEl.textContent = `${linkText} is not yet created. Click to create.`; 1105 | if (this.plugin.settings.autoFocus) { 1106 | setTimeout(() => { 1107 | createEl?.focus(); 1108 | }, 200); 1109 | } 1110 | createEl.addEventListener( 1111 | "click", 1112 | async () => { 1113 | this.togglePin(true); 1114 | await this.openLink(linkText, sourcePath, eState, leaf); 1115 | }, 1116 | { once: true }, 1117 | ); 1118 | } 1119 | } 1120 | 1121 | whenShown(callback: () => any) { 1122 | // invoke callback once the popover is visible 1123 | if (this.detaching) return; 1124 | const existingCallback = this.onShowCallback; 1125 | this.onShowCallback = () => { 1126 | if (this.detaching) return; 1127 | callback(); 1128 | if (typeof existingCallback === "function") existingCallback(); 1129 | }; 1130 | if (this.state === PopoverState.Shown) { 1131 | this.onShowCallback(); 1132 | this.onShowCallback = undefined; 1133 | } 1134 | } 1135 | 1136 | async openFile(file: TFile, openState?: OpenViewState, useLeaf?: WorkspaceLeaf) { 1137 | if (this.detaching) return; 1138 | const leaf = useLeaf ?? this.attachLeaf(); 1139 | this.opening = true; 1140 | try { 1141 | await leaf.openFile(file, openState); 1142 | if (this.plugin.settings.autoFocus && !this.detaching) { 1143 | this.whenShown(() => { 1144 | // Don't set focus so as not to activate the Obsidian window during unfocused mouseover 1145 | app.workspace.setActiveLeaf(leaf, false, false); 1146 | // Set only the leaf focus, rather than global focus 1147 | if (app.workspace.activeLeaf === leaf) leaf.setEphemeralState({focus: true}); 1148 | // Prevent this leaf's file from registering as a recent file 1149 | // (for the quick switcher or Recent Files plugin) for the next 1150 | // 1ms. (They're both triggered by a file-open event that happens 1151 | // in a timeout 0ms after setActiveLeaf, so we register now and 1152 | // uninstall later to ensure our uninstalls happen after the event.) 1153 | setTimeout( 1154 | around(Workspace.prototype, { 1155 | recordMostRecentOpenedFile(old) { 1156 | return function (_file: TFile) { 1157 | // Don't update the quick switcher's recent list 1158 | if (_file !== file) { 1159 | return old.call(this, _file); 1160 | } 1161 | }; 1162 | }, 1163 | }), 1164 | 1, 1165 | ); 1166 | const recentFiles = this.plugin.app.plugins.plugins["recent-files-obsidian"]; 1167 | if (recentFiles) 1168 | setTimeout( 1169 | around(recentFiles, { 1170 | shouldAddFile(old) { 1171 | return function (_file: TFile) { 1172 | // Don't update the Recent Files plugin 1173 | return _file !== file && old.call(this, _file); 1174 | }; 1175 | }, 1176 | update(old) { 1177 | // Newer versions of the plugin need this instead 1178 | return function (_file) { 1179 | return old.call(this, _file === file ? null : _file); 1180 | } 1181 | } 1182 | }), 1183 | 1, 1184 | ); 1185 | }); 1186 | } else if (!this.plugin.settings.autoFocus && !this.detaching) { 1187 | const titleEl = this.hoverEl.querySelector(".popover-title"); 1188 | if (!titleEl) return; 1189 | titleEl.textContent = leaf.view?.getDisplayText(); 1190 | titleEl.setAttribute("data-path", leaf.view?.file?.path); 1191 | } 1192 | } catch (e) { 1193 | console.error(e); 1194 | } finally { 1195 | this.opening = false; 1196 | if (this.detaching) this.hide(); 1197 | } 1198 | return leaf; 1199 | } 1200 | 1201 | buildState(parentMode: string, eState?: EphemeralState) { 1202 | const defaultMode = this.plugin.settings.defaultMode; 1203 | const mode = defaultMode === "match" ? parentMode : this.plugin.settings.defaultMode; 1204 | return { 1205 | active: false, // Don't let Obsidian force focus if we have autofocus off 1206 | state: { mode: mode }, 1207 | eState: eState, 1208 | }; 1209 | } 1210 | 1211 | buildEphemeralState( 1212 | file: TFile, 1213 | link?: { 1214 | path: string; 1215 | subpath: string; 1216 | }, 1217 | ) { 1218 | const cache = this.plugin.app.metadataCache.getFileCache(file); 1219 | const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined; 1220 | const eState: EphemeralState = { subpath: link?.subpath }; 1221 | if (subpath) { 1222 | eState.line = subpath.start.line; 1223 | eState.startLoc = subpath.start; 1224 | eState.endLoc = subpath.end || undefined; 1225 | } 1226 | return eState; 1227 | } 1228 | } 1229 | 1230 | export function isHoverLeaf(leaf: WorkspaceLeaf) { 1231 | // Work around missing enhance.js API by checking match condition instead of looking up parent 1232 | return leaf.containerEl.matches(".popover.hover-popover.hover-editor .workspace-leaf"); 1233 | } 1234 | 1235 | /** 1236 | * It positions an element relative to a rectangle, taking into account the boundaries of the element's 1237 | * offset parent 1238 | * @param rect - The rectangle of the element you want to position the popup relative to. 1239 | * @param {HTMLElement} el - The element to position 1240 | * @param [options] - { 1241 | * @returns An object with the top, left, and vresult properties. 1242 | */ 1243 | export function positionEl( 1244 | rect: { top: number; bottom: number; left: number; right: number }, 1245 | el: HTMLElement, 1246 | options: { gap?: number; preference?: string; offsetParent?: HTMLElement; horizontalAlignment?: string }, 1247 | document: Document, 1248 | ) { 1249 | options = options || {}; 1250 | el.show(); 1251 | const gap = options.gap || 0; 1252 | const verticalPref = options.preference || "bottom"; 1253 | const parentEl = options.offsetParent || el.offsetParent || document.documentElement; 1254 | const horizontalAlignment = options.horizontalAlignment || "left"; 1255 | const parentTop = parentEl.scrollTop + 10; 1256 | const parentBottom = parentEl.scrollTop + parentEl.clientHeight - 10; 1257 | const top = Math.min(rect.top, parentBottom); 1258 | const bottom = Math.max(rect.bottom, parentTop); 1259 | const elHeight = el.offsetHeight; 1260 | const fitsAbove = rect.top - parentTop >= elHeight + gap; 1261 | const fitsBelow = parentBottom - rect.bottom >= elHeight + gap; 1262 | let topResult = 0; 1263 | let vresult = ""; // vertical result 1264 | 1265 | if (!fitsAbove || ("top" !== verticalPref && fitsBelow)) { 1266 | if (!fitsBelow || ("bottom" !== verticalPref && fitsAbove)) { 1267 | if (parentEl.clientHeight < elHeight + gap) { 1268 | topResult = parentTop; 1269 | vresult = "overlap"; 1270 | } else { 1271 | if ("top" === verticalPref) { 1272 | topResult = parentTop + gap; 1273 | vresult = "overlap"; 1274 | } else { 1275 | topResult = parentBottom - elHeight; 1276 | vresult = "overlap"; 1277 | } 1278 | } 1279 | } else { 1280 | topResult = bottom + gap; 1281 | vresult = "bottom"; 1282 | } 1283 | } else { 1284 | topResult = top - gap - elHeight; 1285 | vresult = "top"; 1286 | } 1287 | 1288 | const leftBoundary = parentEl.scrollLeft + 10; 1289 | const rightBoundary = parentEl.scrollLeft + parentEl.clientWidth - 10; 1290 | const elWidth = el.offsetWidth; 1291 | let leftResult = "left" === horizontalAlignment ? rect.left : rect.right - elWidth; 1292 | 1293 | if (leftResult < leftBoundary) { 1294 | leftResult = leftBoundary; 1295 | } else { 1296 | if (leftResult > rightBoundary - elWidth) { 1297 | leftResult = rightBoundary - elWidth; 1298 | } 1299 | } 1300 | 1301 | el.style.top = "".concat(topResult.toString(), "px"); 1302 | el.style.left = "".concat(leftResult.toString(), "px"); 1303 | 1304 | return { 1305 | top: topResult, 1306 | left: leftResult, 1307 | vresult: vresult, 1308 | }; 1309 | } 1310 | 1311 | /** 1312 | * "Get the position of an element relative to a parent element." 1313 | * 1314 | * The function takes two arguments: 1315 | * 1316 | * el: The element whose position we want to get. 1317 | * parentEl: The parent element to which we want to get the relative position. 1318 | * The function returns an object with two properties: 1319 | * 1320 | * top: The top position of the element relative to the parent element. 1321 | * left: The left position of the element relative to the parent element. 1322 | * 1323 | * The function works by looping through the offsetParent chain of the element and subtracting the 1324 | * scrollTop and scrollLeft values of the parent elements 1325 | * @param {HTMLElement | null} el - The element you want to get the relative position of. 1326 | * @param {HTMLElement | null} parentEl - The parent element that you want to get the relative position 1327 | * of. 1328 | * @returns An object with two properties, top and left. 1329 | */ 1330 | function getRelativePos(el: HTMLElement | null, parentEl: HTMLElement | null) { 1331 | let top = 0, 1332 | left = 0; 1333 | for (let nextParentEl = parentEl ? parentEl.offsetParent : null; el && el !== parentEl && el !== nextParentEl; ) { 1334 | top += el.offsetTop; 1335 | left += el.offsetLeft; 1336 | const offsetParent = el.offsetParent as HTMLElement | null; 1337 | 1338 | for (let parent = el.parentElement; parent && parent !== offsetParent; ) { 1339 | top -= parent.scrollTop; 1340 | left -= parent.scrollLeft; 1341 | parent = parent.parentElement; 1342 | } 1343 | 1344 | if (offsetParent && offsetParent !== parentEl && offsetParent !== nextParentEl) { 1345 | top -= offsetParent.scrollTop; 1346 | left -= offsetParent.scrollLeft; 1347 | } 1348 | 1349 | el = offsetParent; 1350 | } 1351 | 1352 | return { 1353 | top, 1354 | left, 1355 | }; 1356 | } 1357 | 1358 | export function setMouseCoords(event: MouseEvent) { 1359 | mouseCoords = { 1360 | x: event.clientX, 1361 | y: event.clientY, 1362 | }; 1363 | } 1364 | 1365 | function mouseIsOffTarget(event: MouseEvent, el: Element) { 1366 | const relatedTarget = event.relatedTarget; 1367 | return !(isA(relatedTarget, Node) && el.contains(relatedTarget)); 1368 | } 1369 | 1370 | function overlaps(rect1?: DOMRect, rect2?: DOMRect) { 1371 | return !!( 1372 | rect1 && 1373 | rect2 && 1374 | rect1.right > rect2.left && 1375 | rect1.left < rect2.right && 1376 | rect1.bottom > rect2.top && 1377 | rect1.top < rect2.bottom 1378 | ); 1379 | } 1380 | -------------------------------------------------------------------------------- /src/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting, requireApiVersion } from "obsidian"; 2 | 3 | import HoverEditorPlugin from "../main"; 4 | import { parseCssUnitValue } from "../utils/misc"; 5 | 6 | export interface HoverEditorSettings { 7 | defaultMode: string; 8 | autoPin: string; 9 | triggerDelay: number; 10 | closeDelay: number; 11 | autoFocus: boolean; 12 | rollDown: boolean; 13 | snapToEdges: boolean; 14 | initialHeight: string; 15 | initialWidth: string; 16 | showViewHeader: boolean; 17 | imageZoom: boolean; 18 | hoverEmbeds: boolean; 19 | footnotes: "always" | "never"; 20 | headings: "always" | "never"; 21 | blocks: "always" | "never"; 22 | } 23 | 24 | export const DEFAULT_SETTINGS: HoverEditorSettings = { 25 | defaultMode: "preview", 26 | autoPin: "onMove", 27 | triggerDelay: 300, 28 | closeDelay: 600, 29 | autoFocus: true, 30 | rollDown: false, 31 | snapToEdges: false, 32 | initialHeight: "340px", 33 | initialWidth: "400px", 34 | showViewHeader: false, 35 | imageZoom: true, 36 | hoverEmbeds: false, 37 | footnotes: requireApiVersion("1.6") ? "never" : "always", 38 | headings: "always", 39 | blocks: requireApiVersion("1.6") ? "never" : "always", 40 | }; 41 | 42 | export const modeOptions = { 43 | preview: "Reading view", 44 | source: "Editing view", 45 | match: "Match current view", 46 | }; 47 | 48 | export const pinOptions = { 49 | onMove: "On drag or resize", 50 | always: "Always", 51 | }; 52 | 53 | export class SettingTab extends PluginSettingTab { 54 | plugin: HoverEditorPlugin; 55 | 56 | constructor(app: App, plugin: HoverEditorPlugin) { 57 | super(app, plugin); 58 | this.plugin = plugin; 59 | } 60 | 61 | hide() {} 62 | 63 | display(): void { 64 | const { containerEl } = this; 65 | 66 | containerEl.empty(); 67 | 68 | new Setting(containerEl).setName("Default Mode").addDropdown(cb => { 69 | cb.addOptions(modeOptions); 70 | cb.setValue(this.plugin.settings.defaultMode); 71 | cb.onChange(async value => { 72 | this.plugin.settings.defaultMode = value; 73 | await this.plugin.saveSettings(); 74 | }); 75 | }); 76 | 77 | new Setting(containerEl).setName("Auto Pin").addDropdown(cb => { 78 | cb.addOptions(pinOptions); 79 | cb.setValue(this.plugin.settings.autoPin); 80 | cb.onChange(async value => { 81 | this.plugin.settings.autoPin = value; 82 | await this.plugin.saveSettings(); 83 | }); 84 | }); 85 | 86 | new Setting(containerEl) 87 | .setName("Trigger hover preview on embeds") 88 | .setDesc( 89 | "Allow hover preview to trigger when hovering over any type of rendered embed such as images or block references", 90 | ) 91 | .addToggle(toggle => 92 | toggle.setValue(this.plugin.settings.hoverEmbeds).onChange(value => { 93 | this.plugin.settings.hoverEmbeds = value; 94 | this.plugin.saveSettings(); 95 | }), 96 | ); 97 | 98 | new Setting(containerEl) 99 | .setName("Trigger hover preview on sub-heading links") 100 | .setDesc( 101 | "Use hover editor for links to subheadings, instead of the built-in preview/editor", 102 | ) 103 | .addToggle(toggle => 104 | toggle.setValue(this.plugin.settings.headings === "always").onChange(value => { 105 | this.plugin.settings.headings = value ? "always" : "never"; 106 | this.plugin.saveSettings(); 107 | }), 108 | ); 109 | 110 | new Setting(containerEl) 111 | .setName("Trigger hover preview on block links") 112 | .setDesc( 113 | "Use hover editor for links to blocks, instead of the built-in preview/editor", 114 | ) 115 | .addToggle(toggle => 116 | toggle.setValue(this.plugin.settings.blocks === "always").onChange(value => { 117 | this.plugin.settings.blocks = value ? "always" : "never"; 118 | this.plugin.saveSettings(); 119 | }), 120 | ); 121 | 122 | new Setting(containerEl) 123 | .setName("Trigger hover preview on footnotes") 124 | .setDesc( 125 | "Use hover editor for footnotes, instead of the built-in preview/editor", 126 | ) 127 | .addToggle(toggle => 128 | toggle.setValue(this.plugin.settings.footnotes === "always").onChange(value => { 129 | this.plugin.settings.footnotes = value ? "always" : "never"; 130 | this.plugin.saveSettings(); 131 | }), 132 | ); 133 | 134 | new Setting(containerEl) 135 | .setName("Auto Focus") 136 | .setDesc("Set the hover editor as the active pane when opened") 137 | .addToggle(toggle => 138 | toggle.setValue(this.plugin.settings.autoFocus).onChange(value => { 139 | this.plugin.settings.autoFocus = value; 140 | this.plugin.saveSettings(); 141 | }), 142 | ); 143 | 144 | new Setting(containerEl) 145 | .setName("Minimize downwards") 146 | .setDesc("When double clicking to minimize, the window will roll down instead of rolling up") 147 | .addToggle(toggle => 148 | toggle.setValue(this.plugin.settings.rollDown).onChange(value => { 149 | this.plugin.settings.rollDown = value; 150 | this.plugin.saveSettings(); 151 | }), 152 | ); 153 | 154 | new Setting(containerEl) 155 | .setName("Snap to edges") 156 | .setDesc( 157 | `Quickly arrange popovers by dragging them to the edges of the screen. The left and right edges 158 | will maximize the popover vertically. The top edge will maximize the popover to fill the entire 159 | screen. Dragging the popovers away from the edges will restore the popver to its original size.`, 160 | ) 161 | .addToggle(toggle => 162 | toggle.setValue(this.plugin.settings.snapToEdges).onChange(value => { 163 | this.plugin.settings.snapToEdges = value; 164 | this.plugin.saveSettings(); 165 | }), 166 | ); 167 | 168 | new Setting(containerEl) 169 | .setName("Show view header by default") 170 | .setDesc( 171 | `Show the view header by default when triggering a hover editor. 172 | When disabled, view headers will only show if you click the view header icon to the left of the minimize button.`, 173 | ) 174 | .addToggle(toggle => 175 | toggle.setValue(this.plugin.settings.showViewHeader).onChange(value => { 176 | this.plugin.settings.showViewHeader = value; 177 | this.plugin.saveSettings(); 178 | }), 179 | ); 180 | 181 | new Setting(containerEl) 182 | .setName("Click to zoom image") 183 | .setDesc( 184 | `Click and hold an image within a hover editor to temporarily maximize the popover and image to fill the entire viewport. 185 | On mouse up, the hover editor will restore to its original size.`, 186 | ) 187 | .addToggle(toggle => 188 | toggle.setValue(this.plugin.settings.imageZoom).onChange(value => { 189 | this.plugin.settings.imageZoom = value; 190 | this.plugin.saveSettings(); 191 | }), 192 | ); 193 | 194 | new Setting(containerEl) 195 | .setName("Initial popover width") 196 | .setDesc("Enter any valid CSS unit") 197 | .addText(textfield => { 198 | textfield.setPlaceholder(this.plugin.settings.initialWidth); 199 | textfield.inputEl.type = "text"; 200 | textfield.setValue(this.plugin.settings.initialWidth); 201 | textfield.onChange(async value => { 202 | value = parseCssUnitValue(value); 203 | if (!value) value = DEFAULT_SETTINGS.initialWidth; 204 | this.plugin.settings.initialWidth = value; 205 | this.plugin.saveSettings(); 206 | }); 207 | }); 208 | 209 | new Setting(containerEl) 210 | .setName("Initial popover height") 211 | .setDesc("Enter any valid CSS unit") 212 | .addText(textfield => { 213 | textfield.setPlaceholder(String(this.plugin.settings.initialHeight)); 214 | textfield.inputEl.type = "text"; 215 | textfield.setValue(String(this.plugin.settings.initialHeight)); 216 | textfield.onChange(async value => { 217 | value = parseCssUnitValue(value); 218 | if (!value) value = DEFAULT_SETTINGS.initialHeight; 219 | this.plugin.settings.initialHeight = value; 220 | this.plugin.saveSettings(); 221 | }); 222 | }); 223 | 224 | new Setting(containerEl) 225 | .setName("Hover Trigger Delay (ms)") 226 | .setDesc("How long to wait before showing a Hover Editor when hovering over a link") 227 | .addText(textfield => { 228 | textfield.setPlaceholder(String(this.plugin.settings.triggerDelay)); 229 | textfield.inputEl.type = "number"; 230 | textfield.setValue(String(this.plugin.settings.triggerDelay)); 231 | textfield.onChange(async value => { 232 | this.plugin.settings.triggerDelay = Number(value); 233 | this.plugin.saveSettings(); 234 | }); 235 | }); 236 | 237 | new Setting(containerEl) 238 | .setName("Hover Close Delay (ms)") 239 | .setDesc("How long to wait before closing a Hover Editor once the mouse leaves") 240 | .addText(textfield => { 241 | textfield.setPlaceholder(String(this.plugin.settings.closeDelay)); 242 | textfield.inputEl.type = "number"; 243 | textfield.setValue(String(this.plugin.settings.closeDelay)); 244 | textfield.onChange(async value => { 245 | this.plugin.settings.closeDelay = Number(value); 246 | this.plugin.saveSettings(); 247 | }); 248 | }); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/types/obsidian.d.ts: -------------------------------------------------------------------------------- 1 | import type { EditorView } from "@codemirror/view"; 2 | import { Plugin, SuggestModal, TFile, View, WorkspaceLeaf } from "obsidian"; 3 | import { HoverEditorParent } from "src/popover"; 4 | 5 | interface InternalPlugins { 6 | switcher: QuickSwitcherPlugin; 7 | "page-preview": InternalPlugin; 8 | graph: GraphPlugin; 9 | } 10 | declare class QuickSwitcherModal extends SuggestModal { 11 | getSuggestions(query: string): TFile[] | Promise; 12 | renderSuggestion(value: TFile, el: HTMLElement): unknown; 13 | onChooseSuggestion(item: TFile, evt: MouseEvent | KeyboardEvent): unknown; 14 | instructionsEl?: HTMLElement; 15 | } 16 | interface InternalPlugin { 17 | disable(): void; 18 | enable(): void; 19 | enabled: boolean; 20 | _loaded: boolean; 21 | instance: { name: string; id: string }; 22 | } 23 | interface GraphPlugin extends InternalPlugin { 24 | views: { localgraph: (leaf: WorkspaceLeaf) => GraphView }; 25 | } 26 | 27 | interface GraphView extends View { 28 | engine: typeof Object; 29 | renderer: { worker: { terminate(): void } }; 30 | } 31 | interface QuickSwitcherPlugin extends InternalPlugin { 32 | instance: { 33 | name: string; 34 | id: string; 35 | QuickSwitcherModal: typeof QuickSwitcherModal; 36 | }; 37 | } 38 | 39 | declare global { 40 | interface Window { 41 | activeWindow: Window; 42 | activeDocument: Document; 43 | } 44 | } 45 | 46 | declare module "obsidian" { 47 | interface App { 48 | internalPlugins: { 49 | plugins: InternalPlugins; 50 | getPluginById(id: T): InternalPlugins[T]; 51 | }; 52 | plugins: { 53 | manifests: Record; 54 | plugins: Record & { 55 | ["recent-files-obsidian"]: Plugin & { 56 | shouldAddFile(file: TFile): boolean; 57 | update(file: TFile): Promise; 58 | }; 59 | }; 60 | getPlugin(id: string): Plugin; 61 | getPlugin(id: "calendar"): CalendarPlugin; 62 | }; 63 | dom: { appContainerEl: HTMLElement }; 64 | viewRegistry: ViewRegistry; 65 | openWithDefaultApp(path: string): void; 66 | } 67 | interface ViewRegistry { 68 | typeByExtension: Record; // file extensions to view types 69 | viewByType: Record View>; // file extensions to view types 70 | } 71 | interface CalendarPlugin { 72 | view: View; 73 | } 74 | interface WorkspaceParent { 75 | insertChild(index: number, child: WorkspaceItem, resize?: boolean): void; 76 | replaceChild(index: number, child: WorkspaceItem, resize?: boolean): void; 77 | removeChild(leaf: WorkspaceLeaf, resize?: boolean): void; 78 | containerEl: HTMLElement; 79 | } 80 | interface MarkdownView { 81 | editMode: { cm: EditorView }; 82 | onMarkdownFold(): void; 83 | } 84 | 85 | interface MarkdownPreviewView { 86 | onResize(): void 87 | } 88 | 89 | interface MarkdownEditView { 90 | editorEl: HTMLElement; 91 | } 92 | class MarkdownPreviewRendererStatic extends MarkdownPreviewRenderer { 93 | static registerDomEvents(el: HTMLElement, handlerInstance: unknown, cb: (el: HTMLElement) => unknown): void; 94 | } 95 | 96 | interface WorkspaceLeaf { 97 | openLinkText(linkText: string, path: string, state?: unknown): Promise; 98 | updateHeader(): void; 99 | containerEl: HTMLDivElement; 100 | working: boolean; 101 | parentSplit: WorkspaceParent; 102 | activeTime: number; 103 | } 104 | interface Workspace { 105 | recordHistory(leaf: WorkspaceLeaf, pushHistory: boolean): void; 106 | iterateLeaves(callback: (item: WorkspaceLeaf) => boolean | void, item: WorkspaceItem | WorkspaceItem[]): boolean; 107 | iterateLeaves(item: WorkspaceItem | WorkspaceItem[], callback: (item: WorkspaceLeaf) => boolean | void): boolean; 108 | getDropLocation(event: MouseEvent): { 109 | target: WorkspaceItem; 110 | sidedock: boolean; 111 | }; 112 | recursiveGetTarget(event: MouseEvent, parent: WorkspaceParent): WorkspaceItem; 113 | recordMostRecentOpenedFile(file: TFile): void; 114 | onDragLeaf(event: MouseEvent, leaf: WorkspaceLeaf): void; 115 | onLayoutChange(): void // tell Obsidian leaves have been added/removed/etc. 116 | activeEditor: MarkdownFileInfo | null; 117 | } 118 | interface Editor { 119 | getClickableTokenAt(pos: EditorPosition): { 120 | text: string; 121 | type: string; 122 | start: EditorPosition; 123 | end: EditorPosition; 124 | }; 125 | } 126 | interface View { 127 | iconEl: HTMLElement; 128 | file: TFile; 129 | setMode(mode: MarkdownSubView): Promise; 130 | followLinkUnderCursor(newLeaf: boolean): void; 131 | modes: Record; 132 | getMode(): string; 133 | headerEl: HTMLElement; 134 | contentEl: HTMLElement; 135 | } 136 | 137 | interface EmptyView extends View { 138 | actionListEl: HTMLElement; 139 | emptyTitleEl: HTMLElement; 140 | } 141 | 142 | interface FileManager { 143 | createNewMarkdownFile(folder: TFolder, fileName: string): Promise; 144 | } 145 | enum PopoverState { 146 | Showing, 147 | Shown, 148 | Hiding, 149 | Hidden, 150 | } 151 | interface Menu { 152 | items: MenuItem[]; 153 | dom: HTMLElement; 154 | hideCallback: () => unknown; 155 | } 156 | interface MenuItem { 157 | iconEl: HTMLElement; 158 | dom: HTMLElement; 159 | } 160 | interface EphemeralState { 161 | focus?: boolean; 162 | subpath?: string; 163 | line?: number; 164 | startLoc?: Loc; 165 | endLoc?: Loc; 166 | scroll?: number; 167 | } 168 | interface HoverParent { 169 | type?: string; 170 | } 171 | interface HoverPopover { 172 | parent: HoverEditorParent | null; 173 | targetEl: HTMLElement; 174 | hoverEl: HTMLElement; 175 | position(pos?: MousePos): void; 176 | hide(): void; 177 | show(): void; 178 | shouldShowSelf(): boolean; 179 | timer: number; 180 | waitTime: number; 181 | shouldShow(): boolean; 182 | transition(): void; 183 | } 184 | interface MousePos { 185 | x: number; 186 | y: number; 187 | } 188 | export function setTooltip(el: HTMLElement, tooltip: string, options?: TooltipOptions): void; 189 | } 190 | -------------------------------------------------------------------------------- /src/utils/measure.ts: -------------------------------------------------------------------------------- 1 | import { InteractEvent } from "@interactjs/types"; 2 | import { HoverEditor } from "src/popover"; 3 | import { isA } from "./misc"; 4 | 5 | const SNAP_DISTANCE = 10; 6 | const UNSNAP_THRESHOLD = 60; 7 | 8 | export function calculateOffsets(document: Document) { 9 | const appContainerEl = document.body.querySelector(".app-container, .workspace-split") as HTMLElement; 10 | const leftRibbonEl = document.body.querySelector(".mod-left.workspace-ribbon") as HTMLElement; 11 | const titlebarHeight = appContainerEl.offsetTop; 12 | const ribbonWidth = document.body.hasClass("hider-ribbon") ? 0 : leftRibbonEl ? leftRibbonEl.offsetWidth : 0; 13 | return { top: titlebarHeight, left: ribbonWidth }; 14 | } 15 | 16 | export function getOrigDimensions(el: HTMLElement) { 17 | const height = el.getAttribute("data-orig-height"); 18 | const width = el.getAttribute("data-orig-width"); 19 | const left = parseFloat(el.getAttribute("data-orig-pos-left") || "0"); 20 | let top = parseFloat(el.getAttribute("data-orig-pos-top") || "0"); 21 | const titlebarHeight = calculateOffsets(el.ownerDocument).top; 22 | if (top < titlebarHeight) top = titlebarHeight; 23 | return { height, width, top, left }; 24 | } 25 | 26 | export function restoreDimentions(el: HTMLElement, retain?: boolean) { 27 | const { height, width, top, left } = getOrigDimensions(el); 28 | if (!retain) { 29 | el.removeAttribute("data-orig-width"); 30 | el.removeAttribute("data-orig-height"); 31 | el.removeAttribute("data-orig-pos-left"); 32 | el.removeAttribute("data-orig-pos-top"); 33 | } 34 | if (width) { 35 | el.style.width = width + "px"; 36 | } 37 | if (height) { 38 | el.style.height = height + "px"; 39 | } 40 | if (top) { 41 | el.style.top = top + "px"; 42 | el.setAttribute("data-y", String(top)); 43 | } 44 | if (left) { 45 | el.style.left = left + "px"; 46 | } 47 | } 48 | 49 | export function restorePopover(el: HTMLElement) { 50 | if (el.hasClass("snap-to-viewport")) { 51 | el.removeClass("snap-to-viewport"); 52 | restoreDimentions(el); 53 | return; 54 | } 55 | } 56 | 57 | export function expandContract(el: HTMLElement, expand: boolean) { 58 | let contentHeight = (el.querySelector(".view-content") as HTMLElement).offsetHeight; 59 | contentHeight = expand ? -contentHeight : contentHeight; 60 | const y = parseFloat(el.getAttribute("data-y") || "0") + contentHeight; 61 | el.style.top = y + "px"; 62 | el.setAttribute("data-y", String(y)); 63 | } 64 | 65 | export function storeDimensions(el: HTMLElement) { 66 | if (!el.hasAttribute("data-orig-width")) { 67 | el.setAttribute("data-orig-width", String(el.offsetWidth)); 68 | } 69 | if (!el.hasAttribute("data-orig-height")) { 70 | el.setAttribute("data-orig-height", String(el.offsetHeight)); 71 | } 72 | if (!el.hasAttribute("data-orig-pos-left")) { 73 | el.setAttribute("data-orig-pos-left", String(parseFloat(el.style.left))); 74 | } 75 | if (!el.hasAttribute("data-orig-pos-top")) { 76 | el.setAttribute("data-orig-pos-top", String(parseFloat(el.style.top))); 77 | } 78 | } 79 | 80 | export function hasStoredDimensions(el: HTMLElement) { 81 | return ( 82 | el.hasAttribute("data-orig-width") && 83 | el.hasAttribute("data-orig-height") && 84 | el.hasAttribute("data-orig-pos-left") && 85 | el.hasAttribute("data-orig-pos-top") 86 | ); 87 | } 88 | 89 | function calculatePointerPosition(event: InteractEvent) { 90 | const target = event.target as HTMLElement; 91 | 92 | const pointerOffset = event.client.x - event.rect.left; 93 | const maximizedWidth = event.rect.width; 94 | 95 | const pointerOffsetPercentage = pointerOffset / maximizedWidth; 96 | const restoredWidth = target.offsetWidth; 97 | 98 | const x = String(event.client.x - pointerOffsetPercentage * restoredWidth); 99 | const y = String(event.client.y); 100 | 101 | target.setAttribute("data-x", String(x)); 102 | target.setAttribute("data-y", String(y)); 103 | } 104 | 105 | export function snapToEdge(el: HTMLElement, edge: string, offset: { top: number; left: number }) { 106 | el.addClass(`snap-to-${edge}`); 107 | el.style.top = offset.top + "px"; 108 | el.style.height = `calc(100vh - ${offset.top}px)`; 109 | el.style.left = edge === "right" ? "unset" : offset.left + "px"; 110 | if (edge === "viewport") { 111 | el.style.width = `calc(100vw - ${offset.left}px)`; 112 | } 113 | } 114 | 115 | export function dragMoveListener(event: InteractEvent) { 116 | const target = event.target as HTMLElement; 117 | 118 | let { x, y } = target.dataset; 119 | 120 | x = x ? x : target.style.left; 121 | y = y ? y : target.style.top; 122 | 123 | x = String((parseFloat(x) || 0) + event.dx); 124 | y = String((parseFloat(y) || 0) + event.dy); 125 | 126 | if (this.plugin.settings.snapToEdges) { 127 | let offset: { top: number; left: number }; 128 | const document = target.ownerDocument; 129 | 130 | const insideLeftSnapTarget = event.client.x < SNAP_DISTANCE; 131 | const insideRightSnapTarget = event.client.x > document.body.offsetWidth - SNAP_DISTANCE; 132 | const insideTopSnapTarget = event.client.y < 30; 133 | 134 | if (insideLeftSnapTarget || insideRightSnapTarget || insideTopSnapTarget) { 135 | offset = calculateOffsets(document); 136 | storeDimensions(target); 137 | } 138 | 139 | if (insideLeftSnapTarget && event.buttons) { 140 | // if we're inside of a snap zone 141 | snapToEdge(target, "left", offset!); 142 | return; 143 | } else if (insideRightSnapTarget && event.buttons) { 144 | snapToEdge(target, "right", offset!); 145 | return; 146 | } else if (insideTopSnapTarget && event.buttons) { 147 | snapToEdge(target, "viewport", offset!); 148 | return; 149 | } else { 150 | // if we're outside of a snap zone 151 | if (target.hasClass("snap-to-viewport")) { 152 | if (event.client.y < UNSNAP_THRESHOLD) return; 153 | target.removeClass("snap-to-viewport"); 154 | restoreDimentions(target); 155 | calculatePointerPosition(event); 156 | return; 157 | } else if (target.hasClass("snap-to-left")) { 158 | if (event.client.y < UNSNAP_THRESHOLD) return; 159 | target.removeClass("snap-to-left"); 160 | restoreDimentions(target); 161 | calculatePointerPosition(event); 162 | return; 163 | } else if (target.hasClass("snap-to-right")) { 164 | if (event.client.y < UNSNAP_THRESHOLD) return; 165 | target.removeClass("snap-to-right"); 166 | restoreDimentions(target); 167 | calculatePointerPosition(event); 168 | return; 169 | } 170 | } 171 | } 172 | 173 | // if snapping disabled or if no snapping action has just occurred 174 | 175 | target.style.top = y ? y + "px" : target.style.top; 176 | target.style.left = x ? x + "px" : target.style.left; 177 | 178 | target.setAttribute("data-x", String(x)); 179 | target.setAttribute("data-y", String(y)); 180 | } 181 | 182 | export const snapDirections = ["left", "right", "viewport"]; 183 | 184 | export const snapActivePopover = (direction: string, checking?: boolean) => { 185 | const popover = HoverEditor.activePopover?.hoverEl; 186 | if (popover && isA(popover, HTMLElement)) { 187 | if (!checking) { 188 | if (!hasStoredDimensions(popover)) { 189 | storeDimensions(popover); 190 | } else { 191 | restoreDimentions(popover, true); 192 | } 193 | popover.removeClasses(["snap-to-left", "snap-to-right", "snap-to-viewport"]); 194 | const offset = calculateOffsets(popover.ownerDocument); 195 | snapToEdge(popover, direction, offset); 196 | } 197 | return true; 198 | } 199 | return false; 200 | }; 201 | 202 | export const restoreActivePopover = (checking?: boolean) => { 203 | const popover = HoverEditor.activePopover?.hoverEl; 204 | if (popover && isA(popover, HTMLElement)) { 205 | if (!checking) { 206 | if (hasStoredDimensions(popover)) { 207 | popover.removeClasses(["snap-to-left", "snap-to-right", "snap-to-viewport"]); 208 | restoreDimentions(popover); 209 | } 210 | } 211 | return true; 212 | } 213 | return false; 214 | }; 215 | 216 | export const minimizeActivePopover = (checking?: boolean) => { 217 | const popover = HoverEditor.activePopover?.hoverEl; 218 | const he = HoverEditor.activePopovers().find(he => he.hoverEl === popover); 219 | if (he) { 220 | if (!checking) { 221 | he.toggleMinimized(); 222 | } 223 | return true; 224 | } 225 | return false; 226 | }; 227 | -------------------------------------------------------------------------------- /src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | export function parseCssUnitValue(value: string) { 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const parseUnit = require("parse-unit"); 4 | // eslint-disable-next-line prefer-const 5 | let [num, unit] = parseUnit(value); 6 | if (!num) { 7 | return false; 8 | } 9 | if (!unit) { 10 | unit = "px"; 11 | } 12 | const unitTypes = ["em", "ex", "ch", "rem", "vw", "vh", "vmin", "vmax", "%", "cm", "mm", "in", "px", "pt", "pc"]; 13 | 14 | if (unitTypes.contains(unit)) { 15 | return num + unit; 16 | } else { 17 | return undefined; 18 | } 19 | } 20 | 21 | /** 22 | * Window-safe 'instanceof' replacement for Event and DOM class checks 23 | * (Compatibility wrapper for Obsidian 0.14.x: can be replaced with plain `.instanceOf()` later) 24 | */ 25 | export function isA(el: unknown, cls: new (...args: unknown[]) => T): el is T { 26 | return el instanceof cls || (el as { instanceOf(cls: new () => T): boolean })?.instanceOf?.(cls); 27 | } 28 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* @settings 2 | 3 | name: Hover Editor 4 | id: hover-editor 5 | settings: 6 | - 7 | id: titlebar-heading 8 | title: Title bar 9 | type: heading 10 | level: 1 11 | collapsed: true 12 | - 13 | id: titlebar-heading 14 | title: Title bar background/foreground 15 | type: heading 16 | level: 2 17 | collapsed: true 18 | - 19 | id: he-title-bar-active-bg 20 | title: Active unpinned title bar background color 21 | type: variable-themed-color 22 | format: hex 23 | default-light: '#' 24 | default-dark: '#' 25 | - 26 | id: he-title-bar-inactive-bg 27 | title: Inactive unpinned title bar background color 28 | type: variable-themed-color 29 | format: hex 30 | default-light: '#' 31 | default-dark: '#' 32 | - 33 | id: he-title-bar-active-pinned-bg 34 | title: Active pinned title bar background color 35 | type: variable-themed-color 36 | format: hex 37 | default-light: '#' 38 | default-dark: '#' 39 | - 40 | id: he-title-bar-inactive-pinned-bg 41 | title: Inactive pinned title bar background color 42 | type: variable-themed-color 43 | format: hex 44 | default-light: '#' 45 | default-dark: '#' 46 | - 47 | id: he-title-bar-active-fg 48 | title: Active title bar foreground color 49 | type: variable-themed-color 50 | format: hex 51 | default-light: '#' 52 | default-dark: '#' 53 | - 54 | id: he-title-bar-inactive-fg 55 | title: Inactive title bar foreground color 56 | type: variable-themed-color 57 | format: hex 58 | default-light: '#' 59 | default-dark: '#' 60 | - 61 | id: titlebar-action-heading 62 | title: Title bar icons 63 | type: heading 64 | level: 2 65 | collapsed: true 66 | - 67 | id: he-title-bar-inactive-action 68 | title: Title bar inactive icon color 69 | type: variable-themed-color 70 | format: hex 71 | default-light: '#' 72 | default-dark: '#' 73 | - 74 | id: he-title-bar-active-action 75 | title: Titlebar active icon color 76 | type: variable-themed-color 77 | format: hex 78 | default-light: '#' 79 | default-dark: '#' 80 | - 81 | id: titlebar-text-heading 82 | title: Title bar text 83 | type: heading 84 | level: 2 85 | collapsed: true 86 | - 87 | id: he-title-bar-font-size 88 | title: Title bar Font size 89 | type: variable-text 90 | description: Accepts any CSS font-size value 91 | default: 15px 92 | - 93 | id: titlebar-height-heading 94 | title: Title bar height 95 | type: heading 96 | level: 2 97 | collapsed: true 98 | - 99 | id: he-title-bar-height 100 | title: Title bar height 101 | type: variable-text 102 | description: Accepts any CSS font-size value 103 | default: 28px 104 | */ 105 | 106 | :root { 107 | /* general styling */ 108 | --he-popover-opacity-while-dragging: 0.8; 109 | --he-popover-border-radius: 6px; 110 | --he-popover-header-transition-speed: 0.3s; 111 | --he-popover-snap-to-edge-transition-speed: 0.3s; 112 | /* resize handle sizing */ 113 | --he-resize-handle-side-size: 12px; 114 | --he-resize-handle-corner-size: 18px; 115 | /* view header height */ 116 | --he-view-header-height: 36px; 117 | } 118 | 119 | body { 120 | --he-text-on-accent-inactive: var(--text-on-accent); /* couldn't find a good variable that worked across themes */ 121 | --he-text-on-accent-active: #fff; 122 | /* z-index layer settings, probably not a good idea to mess with these */ 123 | --he-popover-layer-inactive: calc(var(--layer-slides) - 4); 124 | --he-popover-layer-active: calc(var(--he-popover-layer-inactive) + 1); 125 | --he-popover-layer-new: calc(var(--he-popover-layer-inactive) + 2); 126 | --he-leaf-drag-overlay: calc(var(--he-popover-layer-inactive) + 3); 127 | /* calculated variables, do not modify */ 128 | --he-resize-handle-side-offset: calc((var(--he-resize-handle-side-size) - 3px) * -1); 129 | --he-resize-handle-corner-offset: calc((var(--he-resize-handle-corner-size) / 2) * -1); 130 | --he-resize-handle-side-length: calc(100% - var(--he-resize-handle-corner-size)); 131 | /* title bar colors */ 132 | --he-title-bar-active-bg: var(--interactive-accent); 133 | --he-title-bar-inactive-bg: #777777; 134 | --he-title-bar-inactive-pinned-bg: #777777; 135 | --he-title-bar-active-pinned-bg: var(--interactive-accent); 136 | 137 | --he-title-bar-active-fg: var(--he-text-on-accent-active); 138 | --he-title-bar-inactive-fg: var(--he-text-on-accent-inactive); 139 | /* title bar action/icon colors */ 140 | --he-title-bar-inactive-action: var(--he-text-on-accent-inactive); 141 | --he-title-bar-active-action: var(--he-text-on-accent-active); 142 | /* titlebar sizing */ 143 | --he-title-bar-height: 28px; 144 | --he-title-bar-font-size: 15px; 145 | } 146 | 147 | .popover.hover-editor .workspace-leaf, 148 | .popover.hover-editor .workspace-split { 149 | height: 100%; 150 | width: 100%; 151 | } 152 | 153 | /* 154 | Obsidian 1.6 sets a different background for non-root splits, 155 | then uses primary as an override at root. Since hover editors 156 | don't live in a root split, we have to copy the override: 157 | */ 158 | .popover.hover-editor .workspace-split .view-content { 159 | background-color: var(--background-primary); 160 | } 161 | 162 | .popover.hover-editor { 163 | min-height: unset; 164 | max-height: unset; 165 | /* touch action none fixes dragging and resizing on mobile */ 166 | touch-action: none; 167 | /* this is set to allow the drag/resize handles to overflow the popover frame */ 168 | overflow: visible; 169 | border: none; 170 | padding: 0; 171 | z-index: var(--he-popover-layer-inactive); 172 | border-radius: var(--he-popover-border-radius); 173 | 174 | /* Prevent snagging on titlebar */ 175 | -webkit-app-region: no-drag; 176 | } 177 | 178 | .popover.hover-editor .markdown-preview-view { 179 | font-size: inherit; 180 | } 181 | 182 | .popover.hover-editor.is-active { 183 | z-index: var(--he-popover-layer-active); 184 | } 185 | 186 | .popover.hover-editor.is-new { 187 | z-index: var(--he-popover-layer-new); 188 | } 189 | 190 | /* Drag/link overlay needs to overlay popups */ 191 | .workspace-fake-target-overlay, 192 | .workspace-drop-overlay { 193 | z-index: var(--he-leaf-drag-overlay); 194 | } 195 | 196 | .popover.hover-editor .resize-handle { 197 | position: absolute; 198 | touch-action: none; 199 | } 200 | 201 | .popover.hover-editor .resize-handle.top { 202 | top: var(--he-resize-handle-side-offset); 203 | height: var(--he-resize-handle-side-size); 204 | left: calc(var(--he-resize-handle-corner-offset) * -1); 205 | width: var(--he-resize-handle-side-length); 206 | } 207 | 208 | .popover.hover-editor .resize-handle.left { 209 | height: var(--he-resize-handle-side-length); 210 | left: var(--he-resize-handle-side-offset); 211 | top: calc(var(--he-resize-handle-corner-offset) * -1); 212 | width: var(--he-resize-handle-side-size); 213 | } 214 | 215 | .popover.hover-editor .resize-handle.right { 216 | height: var(--he-resize-handle-side-length); 217 | right: var(--he-resize-handle-side-offset); 218 | top: calc(var(--he-resize-handle-corner-offset) * -1); 219 | width: var(--he-resize-handle-side-size); 220 | } 221 | 222 | .popover.hover-editor .resize-handle.bottom { 223 | bottom: var(--he-resize-handle-side-offset); 224 | height: var(--he-resize-handle-side-size); 225 | left: calc(var(--he-resize-handle-corner-offset) * -1); 226 | width: var(--he-resize-handle-side-length); 227 | } 228 | 229 | .popover.hover-editor .resize-handle.bottom-left { 230 | bottom: var(--he-resize-handle-corner-offset); 231 | height: var(--he-resize-handle-corner-size); 232 | left: var(--he-resize-handle-corner-offset); 233 | width: var(--he-resize-handle-corner-size); 234 | } 235 | 236 | .popover.hover-editor .resize-handle.bottom-right { 237 | bottom: var(--he-resize-handle-corner-offset); 238 | height: var(--he-resize-handle-corner-size); 239 | right: var(--he-resize-handle-corner-offset); 240 | width: var(--he-resize-handle-corner-size); 241 | } 242 | 243 | .popover.hover-editor .resize-handle.top-left { 244 | top: var(--he-resize-handle-corner-offset); 245 | height: var(--he-resize-handle-corner-size); 246 | left: var(--he-resize-handle-corner-offset); 247 | width: var(--he-resize-handle-corner-size); 248 | } 249 | 250 | .popover.hover-editor .resize-handle.top-right { 251 | top: var(--he-resize-handle-corner-offset); 252 | height: var(--he-resize-handle-corner-size); 253 | right: var(--he-resize-handle-corner-offset); 254 | width: var(--he-resize-handle-corner-size); 255 | } 256 | 257 | /* body.is-dragging-popover .tooltip { 258 | opacity: 0; 259 | } */ 260 | 261 | .popover-header-icon { 262 | width: fit-content; 263 | } 264 | 265 | .mod-pin-popover > svg { 266 | transform: rotate(45deg); 267 | } 268 | 269 | .mod-pin-popover.is-active > svg { 270 | transform: rotate(0deg); 271 | } 272 | 273 | .popover-action, 274 | .popover-header-icon { 275 | margin: 0 8px; 276 | cursor: pointer; 277 | color: var(--he-title-bar-inactive-action); 278 | position: relative; 279 | display: flex; 280 | align-items: center; 281 | } 282 | 283 | .popover-action.is-active, 284 | .mod-pin-popover.is-active { 285 | color: var(--he-title-bar-active-action); 286 | } 287 | 288 | .popover-action:hover, 289 | .popover-header-icon:hover { 290 | color: var(--he-title-bar-active-action); 291 | } 292 | 293 | .popover-action.is-active svg, 294 | .mod-pin-popover.is-active svg { 295 | } 296 | 297 | .mod-pin-popover.is-active > svg { 298 | transform: unset; 299 | } 300 | 301 | .popover.hover-editor .workspace-leaf-content[data-type="empty"] .view-header { 302 | /* ensures that minimal theme doesn't hide the popover header */ 303 | display: flex; 304 | } 305 | 306 | .popover.hover-editor .workspace-split > .workspace-leaf:last-child > .workspace-leaf-resize-handle { 307 | /* this hides the leaf resize handles that touch the edge of the popover */ 308 | /* without this the leaf resize handles conflict with the popover resize handles */ 309 | display: none; 310 | } 311 | 312 | .popover.hover-editor.is-dragging { 313 | opacity: var(--he-popover-opacity-while-dragging); 314 | } 315 | 316 | .popover.hover-editor:is(.snap-to-viewport, .snap-to-left, .snap-to-right) .resize-handle { 317 | display: none; 318 | } 319 | 320 | .popover.hover-editor.snap-to-right .resize-handle.left, 321 | .popover.hover-editor.snap-to-left .resize-handle.right { 322 | display: block; 323 | } 324 | 325 | .popover.hover-editor.is-dragging.snap-to-left, 326 | .popover.hover-editor.is-dragging.snap-to-right, 327 | .popover.hover-editor.is-dragging.snap-to-viewport { 328 | transition: width var(--he-popover-snap-to-edge-transition-speed), 329 | height var(--he-popover-snap-to-edge-transition-speed), top var(--he-popover-snap-to-edge-transition-speed), 330 | left var(--he-popover-snap-to-edge-transition-speed); 331 | } 332 | 333 | .hover-popover.is-dragging.snap-to-left::after, 334 | .hover-popover.is-dragging.snap-to-right::after, 335 | .hover-popover.is-dragging.snap-to-viewport::after { 336 | position: absolute; 337 | content: ""; 338 | width: 100%; 339 | height: 100%; 340 | top: 0; 341 | border-radius: var(--he-popover-border-radius); 342 | box-shadow: inset 0px 0px 0px 4px var(--interactive-accent); 343 | pointer-events: none; 344 | } 345 | 346 | .popover.hover-editor.snap-to-left { 347 | max-height: unset !important; 348 | } 349 | 350 | .popover.hover-editor.snap-to-right { 351 | right: 0 !important; 352 | max-height: unset !important; 353 | } 354 | 355 | .popover.hover-editor.snap-to-viewport { 356 | max-height: unset !important; 357 | max-width: unset !important; 358 | } 359 | 360 | .popover.hover-editor .popover-titlebar { 361 | display: flex; 362 | height: var(--he-title-bar-height); 363 | width: 100%; 364 | background-color: var(--he-title-bar-inactive-bg); 365 | } 366 | 367 | .popover.hover-editor.is-active .popover-titlebar { 368 | background-color: var(--he-title-bar-active-bg); 369 | } 370 | 371 | .popover.hover-editor.is-pinned.is-pinned .popover-titlebar { 372 | background-color: var(--he-title-bar-inactive-pinned-bg); 373 | } 374 | 375 | .popover.hover-editor.is-pinned.is-pinned.is-active .popover-titlebar { 376 | background-color: var(--he-title-bar-active-pinned-bg); 377 | } 378 | 379 | .popover.hover-editor .popover-titlebar .popover-actions { 380 | display: flex; 381 | justify-content: flex-end; 382 | } 383 | 384 | .popover.hover-editor > .popover-content { 385 | margin: 0; 386 | border-radius: var(--he-popover-border-radius); 387 | overflow: hidden; 388 | height: 100%; 389 | width: 100%; 390 | } 391 | 392 | .popover.hover-popover.hover-editor .pdf-toolbar:not(.pdf-findbar.mod-hidden) { 393 | /* Show PDF toolbar in hover editor */ 394 | display: flex; 395 | } 396 | 397 | .popover.hover-editor .popover-titlebar .popover-title { 398 | display: block; 399 | flex-grow: 1; 400 | transition: all 0.3s; 401 | align-self: center; 402 | font-size: var(--he-title-bar-font-size); 403 | font-weight: 500; 404 | white-space: pre; 405 | word-wrap: normal; 406 | color: var(--he-title-bar-inactive-fg); 407 | overflow: hidden; 408 | position: relative; 409 | } 410 | 411 | .popover.hover-editor.is-active .popover-title { 412 | color: var(--he-title-bar-active-fg); 413 | } 414 | 415 | .popover.hover-editor.is-active .popover-title:after { 416 | background: linear-gradient(to right, transparent, var(--he-title-bar-active-bg)); 417 | } 418 | 419 | .popover.hover-editor.is-pinned.is-pinned.is-active .popover-title:after { 420 | background: linear-gradient(to right, transparent, var(--he-title-bar-active-pinned-bg)); 421 | } 422 | 423 | .popover.hover-editor.is-pinned.is-pinned .popover-title:after { 424 | background: linear-gradient(to right, transparent, var(--he-title-bar-inactive-pinned-bg)); 425 | } 426 | 427 | .popover.hover-editor .popover-title:after { 428 | content: " "; 429 | position: absolute; 430 | top: 0; 431 | right: 0; 432 | width: 30px; 433 | height: 100%; 434 | background: linear-gradient(to right, transparent, var(--he-title-bar-inactive-bg)); 435 | } 436 | 437 | .popover.hover-editor .mod-show-navbar svg { 438 | transform: rotate(90deg); 439 | } 440 | 441 | .popover.hover-editor > .popover-content > .workspace-split { 442 | height: calc(100% - var(--he-title-bar-height)); 443 | } 444 | 445 | .popover.hover-editor .view-header { 446 | border-top: none; 447 | transition: all var(--he-popover-header-transition-speed); 448 | display: flex; 449 | } 450 | 451 | /* Restore 1.5.x view header icons */ 452 | .view-header .view-header-icon { 453 | display: none; 454 | padding: var(--size-2-2); 455 | margin-right: var(--size-2-3); 456 | color: var(--text-muted); 457 | align-self: center; 458 | cursor: grab; 459 | } 460 | .view-header .view-header-icon:active { 461 | cursor: grabbing; 462 | } 463 | 464 | .popover.hover-editor .view-header .view-header-icon { 465 | display: flex; 466 | } 467 | 468 | .popover.hover-editor.show-navbar:not(.is-minimized) .popover-title { 469 | opacity: 0; 470 | } 471 | 472 | .popover.hover-editor:not(.show-navbar) .view-header { 473 | height: 0px; 474 | overflow: hidden; 475 | } 476 | 477 | .popover.hover-editor.show-navbar .view-header { 478 | /* theme devs: if you want to change the header height, you must do so by setting the --he-view-header-height variable */ 479 | /* if you don't use the variable, you will break internal measurement logic */ 480 | height: var(--he-view-header-height); 481 | overflow: unset; 482 | } 483 | 484 | .popover.hover-editor:not(.show-navbar) .view-content { 485 | height: 100%; 486 | } 487 | 488 | .popover.hover-editor .workspace-leaf-content[data-type="image"] .view-content { 489 | padding: 0; 490 | position: relative; 491 | overflow: hidden; 492 | } 493 | 494 | .popover.hover-editor .workspace-leaf-content[data-type="image"] img { 495 | display: block; 496 | position: relative; 497 | height: 100%; 498 | width: 100%; 499 | max-width: unset; 500 | border-radius: 0; 501 | } 502 | 503 | body .popover.hover-editor .view-content { 504 | /* theme devs: if you want to change the header height, you must do so by setting the --he-view-header-height variable */ 505 | /* if you don't use the variable, you will break internal measurement logic */ 506 | height: calc(100% - var(--he-view-header-height)); 507 | } 508 | 509 | /* start: zoomable images feature */ 510 | 511 | .popover.hover-editor.image-zoom .view-content .image-embed:active { 512 | aspect-ratio: unset; 513 | cursor: zoom-out; 514 | display: block; 515 | z-index: 200; 516 | position: fixed; 517 | max-height: calc(100% + 1px); 518 | max-width: 100%; 519 | height: calc(100% + 1px); 520 | width: 100%; 521 | object-fit: contain; 522 | margin: -0.5px auto 0; 523 | text-align: center; 524 | padding: 0; 525 | left: 0; 526 | right: 0; 527 | bottom: 0; 528 | max-width: unset; 529 | } 530 | 531 | /* extra specificity to override some community theme styles that cause issues */ 532 | .popover.hover-editor.image-zoom .view-content .image-embed img:active { 533 | top: 50%; 534 | z-index: 99; 535 | transform: translateY(-50%); 536 | padding: 0; 537 | margin: 0 auto; 538 | width: calc(100% - 20px); 539 | height: unset; 540 | max-height: 95vh; 541 | object-fit: contain; 542 | left: 0; 543 | right: 0; 544 | bottom: 0; 545 | position: absolute; 546 | opacity: 1; 547 | max-width: unset; 548 | max-height: 100%; 549 | } 550 | 551 | .popover.hover-editor.image-zoom .view-content .image-embed:active:after { 552 | background-color: var(--background-primary); 553 | opacity: 0.9; 554 | content: " "; 555 | height: calc(100% + 1px); 556 | width: 100%; 557 | position: fixed; 558 | left: 0; 559 | right: 1px; 560 | z-index: 0; 561 | } 562 | 563 | .popover.hover-editor.image-zoom .view-content img { 564 | cursor: zoom-in; 565 | } 566 | 567 | /* extra specificity to override some community theme styles that cause issues */ 568 | .popover.hover-editor.image-zoom .workspace-leaf-content[data-type="image"] img { 569 | cursor: zoom-in; 570 | top: 50%; 571 | transform: translateY(-50%); 572 | object-fit: contain; 573 | height: unset; 574 | left: 0; 575 | right: 0; 576 | bottom: 0; 577 | position: absolute; 578 | opacity: 1; 579 | max-height: 100%; 580 | } 581 | 582 | /* end: zoomable images feature */ 583 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES2018", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "lib": [ 14 | "DOM", 15 | "ES5", 16 | "ES6", 17 | "ES7", 18 | "ES2017", 19 | "ES2018" 20 | ] 21 | }, 22 | "include": [ 23 | "**/*.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.11.26": "1.5.8", 3 | "0.11.17": "1.4.16", 4 | "0.11.15": "1.3.5", 5 | "0.11.12": "0.15.9", 6 | "0.11.4": "0.14.5", 7 | "0.10.7": "0.14.5", 8 | "0.9.2": "0.12.5", 9 | "0.0.1": "0.12.5" 10 | } 11 | --------------------------------------------------------------------------------