├── .editorconfig ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── Actions │ ├── Actions.md │ ├── Bookmarks.md │ ├── Canvas.md │ ├── Commands.md │ ├── Frontmatter.md │ ├── Miscellaneous.md │ ├── Navigation.md │ ├── Search.md │ ├── Settings navigation.md │ └── Writing.md ├── Concepts │ ├── Encoding.md │ ├── File identifiers.md │ ├── Navigation Parameters.md │ └── Schema.md ├── External │ └── Hook.md ├── Getting started.md ├── Home.md ├── Installing.md ├── Tips │ ├── From within Obsidian.md │ ├── Helper Commands.md │ └── Tips and Tricks.md └── i18n │ └── zh-CN │ ├── actions │ ├── bookmarks.md │ ├── commands.md │ ├── frontmatter.md │ ├── index.md │ ├── miscellaneous.md │ ├── navigation.md │ ├── search.md │ ├── settings_navigation.md │ └── writing.md │ ├── concepts │ ├── encoding.md │ ├── file_identifiers.md │ ├── navigation_parameters.md │ └── schema.md │ ├── external │ └── hook.md │ ├── getting_started.md │ ├── index.md │ ├── installing.md │ └── tips │ ├── helper_commands.md │ └── index.md ├── esbuild.config.mjs ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── block_utils.ts ├── constants.ts ├── daily_note_utils.ts ├── handlers.ts ├── main.ts ├── modals │ ├── command_modal.ts │ ├── enter_data_modal.ts │ ├── file_modal.ts │ ├── replace_modal.ts │ ├── search_modal.ts │ └── workspace_modal.ts ├── settings.ts ├── tools.ts ├── types.ts └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | tab_width = 4 -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian Plugin 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 13 | - name: Use Node.js 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: '16.x' 17 | - name: Get Version 18 | id: version 19 | run: | 20 | echo "::set-output name=tag::$(git describe --abbrev=0)" 21 | # Build the plugin 22 | - name: Build 23 | id: build 24 | run: | 25 | npm install 26 | npm run build --if-present 27 | # Package the required files into a zip 28 | - name: Package 29 | run: | 30 | mkdir ${{ github.event.repository.name }} 31 | cp main.js manifest.json README.md ${{ github.event.repository.name }} 32 | zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }} 33 | # Create the release on github 34 | - name: Create Release 35 | id: create_release 36 | uses: actions/create-release@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | VERSION: ${{ github.ref }} 40 | with: 41 | tag_name: ${{ github.ref }} 42 | release_name: ${{ github.ref }} 43 | draft: false 44 | prerelease: false 45 | # Upload the packaged release file 46 | - name: Upload zip file 47 | id: upload-zip 48 | uses: actions/upload-release-asset@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | upload_url: ${{ steps.create_release.outputs.upload_url }} 53 | asset_path: ./${{ github.event.repository.name }}.zip 54 | asset_name: ${{ github.event.repository.name }}-${{ steps.version.outputs.tag }}.zip 55 | asset_content_type: application/zip 56 | # Upload the main.js 57 | - name: Upload main.js 58 | id: upload-main 59 | uses: actions/upload-release-asset@v1 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | with: 63 | upload_url: ${{ steps.create_release.outputs.upload_url }} 64 | asset_path: ./main.js 65 | asset_name: main.js 66 | asset_content_type: text/javascript 67 | # Upload the manifest.json 68 | - name: Upload manifest.json 69 | id: upload-manifest 70 | uses: actions/upload-release-asset@v1 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | with: 74 | upload_url: ${{ steps.create_release.outputs.upload_url }} 75 | asset_path: ./manifest.json 76 | asset_name: manifest.json 77 | asset_content_type: application/json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | main.js 8 | 9 | # build 10 | *.js.map 11 | 12 | .prettierignore 13 | /data.json 14 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.44.3](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.44.2...1.44.3) (2024-12-31) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * handle blocks case insensitive ([f2ae357](https://github.com/Vinzent03/obsidian-advanced-uri/commit/f2ae357b4ebacdc85a60c1a3fea526927c2e7051)), closes [#192](https://github.com/Vinzent03/obsidian-advanced-uri/issues/192) 11 | 12 | ### [1.44.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.44.1...1.44.2) (2024-11-07) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * enable plugin ([d9b581d](https://github.com/Vinzent03/obsidian-advanced-uri/commit/d9b581dddbfb98c023994c7a640d21638497ecd6)) 18 | 19 | ### [1.44.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.44.0...1.44.1) (2024-08-30) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * encode vault parameter ([26376d7](https://github.com/Vinzent03/obsidian-advanced-uri/commit/26376d77c1aeb11bc0693f96a11229fbd760b51a)), closes [#186](https://github.com/Vinzent03/obsidian-advanced-uri/issues/186) 25 | 26 | ## [1.44.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.43.0...1.44.0) (2024-08-28) 27 | 28 | 29 | ### Features 30 | 31 | * new shorter action string without doubled encoding ([026d8e2](https://github.com/Vinzent03/obsidian-advanced-uri/commit/026d8e2736b3b19b76bfdd57b0fbaa4f956fac4f)) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * open without viewmode parameter ([8fdb789](https://github.com/Vinzent03/obsidian-advanced-uri/commit/8fdb7890ab2119632567f987bcf15e8241a4fdd0)) 37 | 38 | ## [1.43.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.42.0...1.43.0) (2024-08-25) 39 | 40 | 41 | ### Features 42 | 43 | * add confirm parameter to command uris ([147be01](https://github.com/Vinzent03/obsidian-advanced-uri/commit/147be01e2dafa51867a66c7854d32eb2634cdfa4)), closes [#174](https://github.com/Vinzent03/obsidian-advanced-uri/issues/174) 44 | * center cursor in line ([e941beb](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e941beb8bd968f8f41c0ce37e176379b9c4c22ef)), closes [#183](https://github.com/Vinzent03/obsidian-advanced-uri/issues/183) 45 | * copy URI for current workspace ([58f92ea](https://github.com/Vinzent03/obsidian-advanced-uri/commit/58f92eaafa12999c8df08196d4802a860503a207)), closes [#42](https://github.com/Vinzent03/obsidian-advanced-uri/issues/42) 46 | * set cursor at offset ([0d450ca](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0d450ca3270753ad6114f07146a73fe401ce3add)), closes [#181](https://github.com/Vinzent03/obsidian-advanced-uri/issues/181) 47 | * support openmode for command without file ([42df2cc](https://github.com/Vinzent03/obsidian-advanced-uri/commit/42df2cc85ec9048908f61b8cf4afe16e286cf917)), closes [#163](https://github.com/Vinzent03/obsidian-advanced-uri/issues/163) 48 | 49 | ## [1.42.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.41.0...1.42.0) (2024-08-01) 50 | 51 | 52 | ### Features 53 | 54 | * canvas support ([6bf24ea](https://github.com/Vinzent03/obsidian-advanced-uri/commit/6bf24eae66e3dce9a41129c76354eaec23ff8ced)), closes [#177](https://github.com/Vinzent03/obsidian-advanced-uri/issues/177) 55 | * set cursor to column in line ([414277d](https://github.com/Vinzent03/obsidian-advanced-uri/commit/414277d0edef26348aaed6998e879519fca055c9)), closes [#173](https://github.com/Vinzent03/obsidian-advanced-uri/issues/173) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * ignore window openmode on mobile ([03e7005](https://github.com/Vinzent03/obsidian-advanced-uri/commit/03e7005fa35e3aae21c9e66edb90a7f956384633)), closes [#169](https://github.com/Vinzent03/obsidian-advanced-uri/issues/169) 61 | * remove useless console.log ([4db3143](https://github.com/Vinzent03/obsidian-advanced-uri/commit/4db314317af3a13a16cf043636c4561e1c2117d3)) 62 | 63 | ## [1.41.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.40.1...1.41.0) (2024-07-07) 64 | 65 | 66 | ### Features 67 | 68 | * add command to copy workspace URI ([31a61b9](https://github.com/Vinzent03/obsidian-advanced-uri/commit/31a61b91d650a2cee99e4f4e359f16ffc762e50e)), closes [#42](https://github.com/Vinzent03/obsidian-advanced-uri/issues/42) 69 | * allow appending / prepending at line ([a564d0b](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a564d0ba237c0d5613a0ab1d3ff048c596b11c02)) 70 | * improve `updateplugins` ([#166](https://github.com/Vinzent03/obsidian-advanced-uri/issues/166)) ([697b0ba](https://github.com/Vinzent03/obsidian-advanced-uri/commit/697b0baa9e5d0d3d4fd300aeffd9e2bcaec7157d)) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * append after empty line and fix prepend with frontmatter ([45f29f1](https://github.com/Vinzent03/obsidian-advanced-uri/commit/45f29f1a711bcb2c6dec6a64aa60ebae5399fe98)) 76 | * change window openmode on mobile to true ([204be8a](https://github.com/Vinzent03/obsidian-advanced-uri/commit/204be8a94299d3739737ef5803990b386233f99c)), closes [#169](https://github.com/Vinzent03/obsidian-advanced-uri/issues/169) 77 | * detect sections correctly ([#175](https://github.com/Vinzent03/obsidian-advanced-uri/issues/175)) ([a869f27](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a869f27af99274e6b2040d9df6cbad9f59bddb42)) 78 | * Missing capitalization of command names ([6d20433](https://github.com/Vinzent03/obsidian-advanced-uri/commit/6d204335008640cb5357bb363c18b7cbc74d3fbd)) 79 | 80 | ### [1.40.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.40.0...1.40.1) (2024-04-09) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * prepend text to empty file ([b4a3f3a](https://github.com/Vinzent03/obsidian-advanced-uri/commit/b4a3f3a65fac2994d1c5db4f7268fecc51c4779f)) 86 | 87 | ## [1.40.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.39.1...1.40.0) (2024-02-03) 88 | 89 | 90 | ### Features 91 | 92 | * write to specific frontmatter fields ([73e6388](https://github.com/Vinzent03/obsidian-advanced-uri/commit/73e6388716c6b02ae8db7a87adca5c73df5ef9fa)) 93 | 94 | ### [1.39.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.39.0...1.39.1) (2024-02-01) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * prepend with frontmatter ([fb39d06](https://github.com/Vinzent03/obsidian-advanced-uri/commit/fb39d0655041da6a14a9c8fa37259d91d7b75e3d)), closes [#148](https://github.com/Vinzent03/obsidian-advanced-uri/issues/148) 100 | * support enable and disable core plugin ([c9b05af](https://github.com/Vinzent03/obsidian-advanced-uri/commit/c9b05af3b7bdbcd9ae8e3a61cee3d856c1b836c8)) 101 | 102 | ## [1.39.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.38.1...1.39.0) (2024-02-01) 103 | 104 | 105 | ### Features 106 | 107 | * add toggle to exclude vault param, and specify name or vault ID ([#153](https://github.com/Vinzent03/obsidian-advanced-uri/issues/153)) ([209160e](https://github.com/Vinzent03/obsidian-advanced-uri/commit/209160efec46235f8317a9f0c8e7a241a4a5261d)) 108 | 109 | ### [1.38.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.38.0...1.38.1) (2023-08-30) 110 | 111 | 112 | ### Bug Fixes 113 | 114 | * use existing empty frontmatter key for uid ([f07c035](https://github.com/Vinzent03/obsidian-advanced-uri/commit/f07c035e3f6d007bb005056390c1b010836564c0)), closes [#144](https://github.com/Vinzent03/obsidian-advanced-uri/issues/144) 115 | 116 | ## [1.38.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.37.0...1.38.0) (2023-08-24) 117 | 118 | 119 | ### Features 120 | 121 | * search whole vault for block id ([91108e6](https://github.com/Vinzent03/obsidian-advanced-uri/commit/91108e685f18a723a0c6837ce97313923396c408)), closes [#141](https://github.com/Vinzent03/obsidian-advanced-uri/issues/141) 122 | 123 | ## [1.37.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.5...1.37.0) (2023-08-07) 124 | 125 | 126 | ### Features 127 | 128 | * use block/heading nav and search in one ([7581613](https://github.com/Vinzent03/obsidian-advanced-uri/commit/7581613e921a268ca514014a89cd05f9bc500f42)), closes [#135](https://github.com/Vinzent03/obsidian-advanced-uri/issues/135) 129 | 130 | ### [1.36.5](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.4...1.36.5) (2023-07-30) 131 | 132 | 133 | ### Bug Fixes 134 | 135 | * allow block and heading in popover openmode ([40227a3](https://github.com/Vinzent03/obsidian-advanced-uri/commit/40227a36e7cb306607619c4868fa2efff5cbe942)), closes [#137](https://github.com/Vinzent03/obsidian-advanced-uri/issues/137) 136 | 137 | ### [1.36.4](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.3...1.36.4) (2023-07-17) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * wait for metadata indexing when getting uid ([fd2a223](https://github.com/Vinzent03/obsidian-advanced-uri/commit/fd2a22340a4958677b0ede78c19c067c2394f75f)) 143 | 144 | ### [1.36.3](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.2...1.36.3) (2023-06-27) 145 | 146 | 147 | ### Bug Fixes 148 | 149 | * only add frontmatter to canvas files ([9d6672a](https://github.com/Vinzent03/obsidian-advanced-uri/commit/9d6672a3ba755ce22b30cc1356f2c205c34f962c)), closes [#131](https://github.com/Vinzent03/obsidian-advanced-uri/issues/131) 150 | 151 | ### [1.36.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.1...1.36.2) (2023-06-03) 152 | 153 | 154 | ### Bug Fixes 155 | 156 | * support numbers as frontmatter uid ([0a0c409](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0a0c4096fc00f4ab5fa63c915c04d56e14a8e542)) 157 | 158 | ### [1.36.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.36.0...1.36.1) (2023-06-02) 159 | 160 | 161 | ### Bug Fixes 162 | 163 | * open file with uid ([4ff8b93](https://github.com/Vinzent03/obsidian-advanced-uri/commit/4ff8b93b606f320ac6a45e455643efc64f30e674)) 164 | 165 | ## [1.36.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.35.0...1.36.0) (2023-05-31) 166 | 167 | 168 | ### Features 169 | 170 | * support list of uids fin frontmatter ([e564f2f](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e564f2f712209ebedd5020d1a880263136c256dd)), closes [#127](https://github.com/Vinzent03/obsidian-advanced-uri/issues/127) 171 | 172 | ## [1.35.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.34.0...1.35.0) (2023-04-15) 173 | 174 | 175 | ### Features 176 | 177 | * bookmark support ([32fd56b](https://github.com/Vinzent03/obsidian-advanced-uri/commit/32fd56b6bd83b021d1c37db75040c7f46b87710e)), closes [#113](https://github.com/Vinzent03/obsidian-advanced-uri/issues/113) 178 | 179 | ## [1.34.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.33.3...1.34.0) (2023-03-30) 180 | 181 | 182 | ### Features 183 | 184 | * set line and switch to reading/preview mode ([917a4eb](https://github.com/Vinzent03/obsidian-advanced-uri/commit/917a4eb5d193e721817bfa770a0d126824b4ad98)), closes [#91](https://github.com/Vinzent03/obsidian-advanced-uri/issues/91) 185 | 186 | ### [1.33.3](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.33.2...1.33.3) (2023-03-18) 187 | 188 | 189 | ### Bug Fixes 190 | 191 | * focus heading in current file ([2adddab](https://github.com/Vinzent03/obsidian-advanced-uri/commit/2adddab38ce95e6ee23a8081d8b8c0460e3dda21)) 192 | 193 | ### [1.33.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.33.1...1.33.2) (2023-03-17) 194 | 195 | 196 | ### Bug Fixes 197 | 198 | * better use for 'true' openmode ([d4d802f](https://github.com/Vinzent03/obsidian-advanced-uri/commit/d4d802f8936f90724d3eea8a688a80ea38ca2c26)) 199 | 200 | ### [1.33.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.33.0...1.33.1) (2023-03-17) 201 | 202 | 203 | ### Bug Fixes 204 | 205 | * use openmode correctly ([fd7dde8](https://github.com/Vinzent03/obsidian-advanced-uri/commit/fd7dde8754c9ff6d318bd614fe05341971a5c9e8)), closes [#118](https://github.com/Vinzent03/obsidian-advanced-uri/issues/118) 206 | 207 | ## [1.33.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.32.0...1.33.0) (2023-02-20) 208 | 209 | 210 | ### Features 211 | 212 | * eval parameter ([025bb92](https://github.com/Vinzent03/obsidian-advanced-uri/commit/025bb9201ef7ca840ef5d4efea7fd8aefc960bea)), closes [#114](https://github.com/Vinzent03/obsidian-advanced-uri/issues/114) 213 | 214 | ## [1.32.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.31.3...1.32.0) (2023-02-10) 215 | 216 | 217 | ### Features 218 | 219 | * navigate to setting section ([e2dd57d](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e2dd57d8380a4d79f5c56c88cdd1746be83696ea)), closes [#110](https://github.com/Vinzent03/obsidian-advanced-uri/issues/110) 220 | 221 | ### [1.31.3](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.31.2...1.31.3) (2023-02-05) 222 | 223 | 224 | ### Bug Fixes 225 | 226 | * don't focus existing pane on silent mode ([d6edfbd](https://github.com/Vinzent03/obsidian-advanced-uri/commit/d6edfbdac3be412b3c21f68ce3414046505edd02)), closes [#106](https://github.com/Vinzent03/obsidian-advanced-uri/issues/106) 227 | * open existing tab if possible ([e98b03d](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e98b03def89cb09ff96ce011b91940676a63318e)), closes [#98](https://github.com/Vinzent03/obsidian-advanced-uri/issues/98) 228 | * show copy advanced uri in file options ([e38c8f4](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e38c8f4a64ec2f0dfc0e96238ee3f2c4b16125fe)) 229 | 230 | ### [1.31.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.31.1...1.31.2) (2022-12-17) 231 | 232 | 233 | ### Bug Fixes 234 | 235 | * copy file uri in other view types ([0303ded](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0303dedf1f3ce032c7e05b2f2a447e35a24fb1d1)), closes [#97](https://github.com/Vinzent03/obsidian-advanced-uri/issues/97) 236 | 237 | ### [1.31.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.31.0...1.31.1) (2022-11-26) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * block ref creation for list items ([49d2714](https://github.com/Vinzent03/obsidian-advanced-uri/commit/49d2714e8023e9bb6a7c0fa7fe3c62c16c0eefa6)), closes [#89](https://github.com/Vinzent03/obsidian-advanced-uri/issues/89) 243 | 244 | ## [1.31.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.30.0...1.31.0) (2022-11-25) 245 | 246 | 247 | ### Features 248 | 249 | * search ([52a2f4c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/52a2f4c6b9ba382b54a8b272c39aa217c275e56b)), closes [#90](https://github.com/Vinzent03/obsidian-advanced-uri/issues/90) 250 | 251 | 252 | ### Bug Fixes 253 | 254 | * set source mode line navigation ([e1079a3](https://github.com/Vinzent03/obsidian-advanced-uri/commit/e1079a395ce5e296b7d2a71118a209041fcdb95f)), closes [#91](https://github.com/Vinzent03/obsidian-advanced-uri/issues/91) 255 | 256 | ## [1.30.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.29.2...1.30.0) (2022-11-23) 257 | 258 | 259 | ### Features 260 | 261 | * add filepath when using uid ([eca15a2](https://github.com/Vinzent03/obsidian-advanced-uri/commit/eca15a2efa3a83c64583e849fef43998dcf632c3)), closes [#86](https://github.com/Vinzent03/obsidian-advanced-uri/issues/86) 262 | * create block reference via command ([f7f37cb](https://github.com/Vinzent03/obsidian-advanced-uri/commit/f7f37cb64ff2f1e379ff1f64e0c19b1e990e181b)), closes [#89](https://github.com/Vinzent03/obsidian-advanced-uri/issues/89) 263 | 264 | ### [1.29.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.29.1...1.29.2) (2022-11-10) 265 | 266 | 267 | ### Bug Fixes 268 | 269 | * openmode popover ([7a69e31](https://github.com/Vinzent03/obsidian-advanced-uri/commit/7a69e313cade68b733a1037b99c493bfde5c99cc)) 270 | 271 | ### [1.29.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.29.0...1.29.1) (2022-11-08) 272 | 273 | 274 | ### Bug Fixes 275 | 276 | * open file when writing to existing file ([4bf9e97](https://github.com/Vinzent03/obsidian-advanced-uri/commit/4bf9e972b350f5a0413cbbb7c29d3f2e00922c58)), closes [#88](https://github.com/Vinzent03/obsidian-advanced-uri/issues/88) 277 | 278 | ## [1.29.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.28.2...1.29.0) (2022-11-08) 279 | 280 | 281 | ### Features 282 | 283 | * specify openmode ([dfc8f6b](https://github.com/Vinzent03/obsidian-advanced-uri/commit/dfc8f6bebe5a6de244e6b1f98cc4febf9f84b800)), closes [#79](https://github.com/Vinzent03/obsidian-advanced-uri/issues/79) 284 | 285 | ### [1.28.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.28.1...1.28.2) (2022-10-14) 286 | 287 | 288 | ### Bug Fixes 289 | 290 | * encode vault parameter properly ([cf53f48](https://github.com/Vinzent03/obsidian-advanced-uri/commit/cf53f4850d11a0807b0a22f9100d73172d603b53)), closes [#83](https://github.com/Vinzent03/obsidian-advanced-uri/issues/83) 291 | 292 | ### [1.28.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.28.0...1.28.1) (2022-10-13) 293 | 294 | 295 | ### Bug Fixes 296 | 297 | * encode vault parameter ([a683686](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a6836868b20f78ce65ffbac6b7f17e9ff445b2f0)), closes [#62](https://github.com/Vinzent03/obsidian-advanced-uri/issues/62) 298 | 299 | ## [1.28.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.27.2...1.28.0) (2022-09-30) 300 | 301 | 302 | ### Features 303 | 304 | * cache last uri parameters ([825150c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/825150c2fd018265f400fa712b1a933303b3557f)), closes [#77](https://github.com/Vinzent03/obsidian-advanced-uri/issues/77) 305 | 306 | ### [1.27.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.27.1...1.27.2) (2022-09-28) 307 | 308 | 309 | ### Bug Fixes 310 | 311 | * set cursor in already opened pane ([c51d3ef](https://github.com/Vinzent03/obsidian-advanced-uri/commit/c51d3ef03c67e4aa8e0f656c92043506b10ec12d)), closes [#81](https://github.com/Vinzent03/obsidian-advanced-uri/issues/81) 312 | 313 | ### [1.27.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.27.0...1.27.1) (2022-09-26) 314 | 315 | 316 | ### Bug Fixes 317 | 318 | * support mode=new on ([14736c1](https://github.com/Vinzent03/obsidian-advanced-uri/commit/14736c12dd4cfde084a0acb4cecccd4519278c84)), closes [#71](https://github.com/Vinzent03/obsidian-advanced-uri/issues/71) 319 | 320 | ## [1.27.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.26.1...1.27.0) (2022-09-15) 321 | 322 | 323 | ### Features 324 | 325 | * set cursor in command call ([3a544ee](https://github.com/Vinzent03/obsidian-advanced-uri/commit/3a544eedaf468dd685bc93db6b95f8c9a8396bae)), closes [#76](https://github.com/Vinzent03/obsidian-advanced-uri/issues/76) 326 | 327 | ### [1.26.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.26.0...1.26.1) (2022-09-06) 328 | 329 | ## [1.26.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.25.1...1.26.0) (2022-09-06) 330 | 331 | 332 | ### Features 333 | 334 | * copy frontmatter value to clipboard ([64b0596](https://github.com/Vinzent03/obsidian-advanced-uri/commit/64b0596b866b05e21a9f5cf2638682a7cb20c602)), closes [#68](https://github.com/Vinzent03/obsidian-advanced-uri/issues/68) 335 | 336 | ### [1.25.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.25.0...1.25.1) (2022-09-06) 337 | 338 | ## [1.25.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.24.0...1.25.0) (2022-09-06) 339 | 340 | 341 | ### Features 342 | 343 | * donate button in settings ([4352657](https://github.com/Vinzent03/obsidian-advanced-uri/commit/43526578acb3d2aaa5d98b48e80c630146910a50)) 344 | * enable and disable plugin ([35f685b](https://github.com/Vinzent03/obsidian-advanced-uri/commit/35f685b1000ef3cda6f9b56f40de64f94ef31846)) 345 | 346 | 347 | ### Bug Fixes 348 | 349 | * copy uri in file menu obs 0.16 ([d766f05](https://github.com/Vinzent03/obsidian-advanced-uri/commit/d766f05eb49f915b9e75f7d869266c735362eb21)), closes [#72](https://github.com/Vinzent03/obsidian-advanced-uri/issues/72) 350 | 351 | ## [1.24.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.23.0...1.24.0) (2022-07-21) 352 | 353 | 354 | ### Features 355 | 356 | * write content from clipboard ([07ed921](https://github.com/Vinzent03/obsidian-advanced-uri/commit/07ed92195c06c6c3e6acf381360fb08fc06792dd)) 357 | 358 | ## [1.23.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.22.0...1.23.0) (2022-06-09) 359 | 360 | 361 | ### Features 362 | 363 | * skip file selection in simple uri generation ([de2e904](https://github.com/Vinzent03/obsidian-advanced-uri/commit/de2e9041f5938214e22a6dfa2054a20eb3b50963)) 364 | 365 | ## [1.22.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.21.1...1.22.0) (2022-05-26) 366 | 367 | 368 | ### Features 369 | 370 | * add simpler copy uri for file command ([1a84aca](https://github.com/Vinzent03/obsidian-advanced-uri/commit/1a84aca18d33977c40096948756cc800d8785d38)), closes [#57](https://github.com/Vinzent03/obsidian-advanced-uri/issues/57) 371 | 372 | ### [1.21.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.21.0...1.21.1) (2022-03-24) 373 | 374 | 375 | ### Bug Fixes 376 | 377 | * race condition for frontmatter parsing ([5dbd063](https://github.com/Vinzent03/obsidian-advanced-uri/commit/5dbd063cac7900fea62808bdacd6e8d16910a437)) 378 | 379 | ## [1.21.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.20.1...1.21.0) (2022-03-24) 380 | 381 | 382 | ### Features 383 | 384 | * add newpane parameter ([cf2dd3c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/cf2dd3c29aebb0925e28861b7a35c684e3e81a66)), closes [#50](https://github.com/Vinzent03/obsidian-advanced-uri/issues/50) 385 | * file creation with filename follows user preferences ([b4682f7](https://github.com/Vinzent03/obsidian-advanced-uri/commit/b4682f7e1dc9e96764a81eb73761905b1b43f21e)) 386 | 387 | 388 | ### Bug Fixes 389 | 390 | * write file in non existent sub folder ([f75713d](https://github.com/Vinzent03/obsidian-advanced-uri/commit/f75713dbbd9e027b7e144b48df2d96aed7e21b3a)) 391 | 392 | ### [1.20.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.20.0...1.20.1) (2022-03-09) 393 | 394 | 395 | ### Bug Fixes 396 | 397 | * get uid from frontmatter ([12f8076](https://github.com/Vinzent03/obsidian-advanced-uri/commit/12f80761585d7c9bf71f4e3bfb438e739bba5b1a)) 398 | 399 | ## [1.20.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.19.0...1.20.0) (2022-03-03) 400 | 401 | 402 | ### Features 403 | 404 | * create file with uid when file doesn't exists ([367775a](https://github.com/Vinzent03/obsidian-advanced-uri/commit/367775ac7cee2c576d2f986d9d4e61815e9602bc)), closes [#49](https://github.com/Vinzent03/obsidian-advanced-uri/issues/49) 405 | * return file uri on hook action ([a7378a1](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a7378a1d6f137a77a20ac2bfad234e3ae319e728)) 406 | 407 | ## [1.19.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.18.0...1.19.0) (2022-03-02) 408 | 409 | 410 | ### Features 411 | 412 | * Hook to new support ([b2b7108](https://github.com/Vinzent03/obsidian-advanced-uri/commit/b2b71084c36fe0d7970cd781fbef269621a7f2c7)) 413 | 414 | 415 | ### Bug Fixes 416 | 417 | * append to heading of daily note ([015bd7c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/015bd7c35315f24f5cdac79adf98d875dbb17731)), closes [#48](https://github.com/Vinzent03/obsidian-advanced-uri/issues/48) 418 | 419 | ## [1.18.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.17.0...1.18.0) (2022-02-28) 420 | 421 | 422 | ### Features 423 | 424 | * add Hook support ([daf9d56](https://github.com/Vinzent03/obsidian-advanced-uri/commit/daf9d56fe6317f3e1e6e5bea3b74abd69d5ef793)), closes [#46](https://github.com/Vinzent03/obsidian-advanced-uri/issues/46) 425 | 426 | ## [1.17.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.16.1...1.17.0) (2022-01-04) 427 | 428 | 429 | ### Features 430 | 431 | * support live preview as view mode ([786fcff](https://github.com/Vinzent03/obsidian-advanced-uri/commit/786fcff1d45a171adf39589c1f215409f993ed90)) 432 | 433 | 434 | ### Bug Fixes 435 | 436 | * call checkCallback correctly ([9cb2c21](https://github.com/Vinzent03/obsidian-advanced-uri/commit/9cb2c2152daef069d2f25a2ea863f41f1757c5d0)) 437 | * support line parameter for writing too ([b5f6508](https://github.com/Vinzent03/obsidian-advanced-uri/commit/b5f65089ecc172c9017f2b40cf0df7e2411c0666)), closes [#39](https://github.com/Vinzent03/obsidian-advanced-uri/issues/39) 438 | 439 | ### [1.16.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.16.0...1.16.1) (2021-12-08) 440 | 441 | 442 | ### Bug Fixes 443 | 444 | * prepand after frontmatter ([6fd80dc](https://github.com/Vinzent03/obsidian-advanced-uri/commit/6fd80dc84c2a2433777c4bade39aeb7feb8d9318)), closes [#36](https://github.com/Vinzent03/obsidian-advanced-uri/issues/36) 445 | 446 | ## [1.16.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.15.2...1.16.0) (2021-11-19) 447 | 448 | 449 | ### Features 450 | 451 | * set cursor to specific line ([35b8455](https://github.com/Vinzent03/obsidian-advanced-uri/commit/35b84557e27a8a252454a95ca5115d951d6e34e9)) 452 | 453 | 454 | ### Bug Fixes 455 | 456 | * place cursor below heading and in block ([7bc7471](https://github.com/Vinzent03/obsidian-advanced-uri/commit/7bc7471dcb2a5ca370b1d0022108a1275ee98fd2)) 457 | 458 | ### [1.15.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.15.1...1.15.2) (2021-11-17) 459 | 460 | 461 | ### Bug Fixes 462 | 463 | * workspace documentation and add notice on workspace save ([a8d0c16](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a8d0c165a6807497db968b31d48bb0e3d51333a8)) 464 | 465 | ### [1.15.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.15.0...1.15.1) (2021-11-16) 466 | 467 | 468 | ### Bug Fixes 469 | 470 | * update plugins works now ([eb833dd](https://github.com/Vinzent03/obsidian-advanced-uri/commit/eb833dd2b9871ce558e4d9b936068e5267c8ca04)) 471 | 472 | ## [1.15.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.14.0...1.15.0) (2021-11-16) 473 | 474 | 475 | ### Features 476 | 477 | * add x-success and x-error param ([5bda636](https://github.com/Vinzent03/obsidian-advanced-uri/commit/5bda63667326f2ad5eac884e84384aa27052b63a)), closes [#30](https://github.com/Vinzent03/obsidian-advanced-uri/issues/30) 478 | * save current workspace ([717fcfe](https://github.com/Vinzent03/obsidian-advanced-uri/commit/717fcfe57e1ba996b8ef054a8383ce15f8b52d98)), closes [#33](https://github.com/Vinzent03/obsidian-advanced-uri/issues/33) 479 | * update plugins, open theme/plugin browser ([a310d80](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a310d80cdf91ad962c60d4fd820f2ae8936eeeba)), closes [#32](https://github.com/Vinzent03/obsidian-advanced-uri/issues/32) 480 | 481 | ## [1.14.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.13.0...1.14.0) (2021-11-01) 482 | 483 | 484 | ### Features 485 | 486 | * add navigation to any settings tab ([702f30a](https://github.com/Vinzent03/obsidian-advanced-uri/commit/702f30a930967b16efec4d5b14561e372ff36cfa)), closes [#29](https://github.com/Vinzent03/obsidian-advanced-uri/issues/29) 487 | 488 | ## [1.13.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.12.0...1.13.0) (2021-10-21) 489 | 490 | 491 | ### Features 492 | 493 | * support daily note for all actions ([8b78394](https://github.com/Vinzent03/obsidian-advanced-uri/commit/8b783940a0e8596dcfec8e5af199d92dfb1e517f)) 494 | 495 | ## [1.12.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.11.2...1.12.0) (2021-10-09) 496 | 497 | 498 | ### Features 499 | 500 | * support setting viewmode ([9ebc0d4](https://github.com/Vinzent03/obsidian-advanced-uri/commit/9ebc0d452812df7fbad416ad7d638464a69f772a)), closes [#27](https://github.com/Vinzent03/obsidian-advanced-uri/issues/27) 501 | 502 | ### [1.11.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.11.1...1.11.2) (2021-10-05) 503 | 504 | 505 | ### Bug Fixes 506 | 507 | * focus existing pane instead of open new one ([a6020c7](https://github.com/Vinzent03/obsidian-advanced-uri/commit/a6020c7c826fb3764afaa5eb34431e781f0efa4d)), closes [#26](https://github.com/Vinzent03/obsidian-advanced-uri/issues/26) 508 | 509 | ### [1.11.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.11.0...1.11.1) (2021-09-06) 510 | 511 | ## [1.11.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.10.0...1.11.0) (2021-09-06) 512 | 513 | 514 | ### Features 515 | 516 | * add support to check for file existence ([27ad1a1](https://github.com/Vinzent03/obsidian-advanced-uri/commit/27ad1a13b01d3a1f8557520d255c2081caddc64c)), closes [#20](https://github.com/Vinzent03/obsidian-advanced-uri/issues/20) 517 | 518 | ## [1.10.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.9.0...1.10.0) (2021-08-04) 519 | 520 | 521 | ### Features 522 | 523 | * support heading and block for daily notes ([fe78a27](https://github.com/Vinzent03/obsidian-advanced-uri/commit/fe78a27510d8432bda6532b903e7f41e56a9df51)), closes [#19](https://github.com/Vinzent03/obsidian-advanced-uri/issues/19) 524 | 525 | ## [1.9.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.8.1...1.9.0) (2021-07-28) 526 | 527 | 528 | ### Features 529 | 530 | * add file name support ([4816800](https://github.com/Vinzent03/obsidian-advanced-uri/commit/4816800282d0b9a5b59865ab8c295a8f0b67ee9b)), closes [#17](https://github.com/Vinzent03/obsidian-advanced-uri/issues/17) 531 | 532 | ### [1.8.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.8.0...1.8.1) (2021-07-23) 533 | 534 | 535 | ### Bug Fixes 536 | 537 | * support replacing with empty text ([0468ac2](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0468ac24b79b4787a84ca7768968ca5219a12073)) 538 | 539 | ## [1.8.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.7.1...1.8.0) (2021-07-13) 540 | 541 | 542 | ### Features 543 | 544 | * add support to navigate by UUID ([c1f509c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/c1f509c2b2ed6d75d3688df4b0d2e98c9629c32e)), closes [#13](https://github.com/Vinzent03/obsidian-advanced-uri/issues/13) 545 | 546 | ### [1.7.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.7.0...1.7.1) (2021-05-15) 547 | 548 | 549 | ### Bug Fixes 550 | 551 | * copying on mobile didn't work ([693e6a0](https://github.com/Vinzent03/obsidian-advanced-uri/commit/693e6a0e07746a7633cb3840f018d21cb63756a6)), closes [#12](https://github.com/Vinzent03/obsidian-advanced-uri/issues/12) 552 | 553 | ## [1.7.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.6.0...1.7.0) (2021-05-13) 554 | 555 | 556 | ### Features 557 | 558 | * improve file picker for uri generation ([bfc6005](https://github.com/Vinzent03/obsidian-advanced-uri/commit/bfc6005a8d676d8785281ce9c7577f2798b47f18)) 559 | * support heading for append and prepend mode ([53936cb](https://github.com/Vinzent03/obsidian-advanced-uri/commit/53936cbe6613722f89088e4db30d2e1e012d5bda)), closes [#11](https://github.com/Vinzent03/obsidian-advanced-uri/issues/11) 560 | 561 | ## [1.6.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.5.0...1.6.0) (2021-05-02) 562 | 563 | 564 | ### Features 565 | 566 | * add search and replace ([cd50799](https://github.com/Vinzent03/obsidian-advanced-uri/commit/cd507993530737dd12fc5cf0c2c4e3eae79fdeeb)) 567 | 568 | ## [1.5.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.4.1...1.5.0) (2021-04-30) 569 | 570 | 571 | ### Features 572 | 573 | * support open with modes ([46f734f](https://github.com/Vinzent03/obsidian-advanced-uri/commit/46f734f23568af7fa2bfe05b06dc73c76387ea1d)), closes [#9](https://github.com/Vinzent03/obsidian-advanced-uri/issues/9) 574 | 575 | ### [1.4.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.4.0...1.4.1) (2021-04-25) 576 | 577 | 578 | ### Bug Fixes 579 | 580 | * prepend/append mode needed an existing file ([894760c](https://github.com/Vinzent03/obsidian-advanced-uri/commit/894760ca7bc79c9ed0b0aa89111bf36e12266283)) 581 | 582 | ## [1.4.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.3.0...1.4.0) (2021-04-24) 583 | 584 | 585 | ### Features 586 | 587 | * add mode support for command execution ([2bc4af1](https://github.com/Vinzent03/obsidian-advanced-uri/commit/2bc4af14d207e4eaa53800f2195c2917ffca9204)), closes [#8](https://github.com/Vinzent03/obsidian-advanced-uri/issues/8) 588 | 589 | ## [1.3.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.2.1...1.3.0) (2021-04-08) 590 | 591 | 592 | ### Features 593 | 594 | * add better settings ([96f67cd](https://github.com/Vinzent03/obsidian-advanced-uri/commit/96f67cdcfbb51d66800db7c0e6ed853c56c6ed91)), closes [#6](https://github.com/Vinzent03/obsidian-advanced-uri/issues/6) 595 | * add command URI builder ([0daf996](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0daf9961fe8d4b953d9478f55445e721e13feeed)) 596 | * add daily notes URI builder ([cb0ebb6](https://github.com/Vinzent03/obsidian-advanced-uri/commit/cb0ebb68d2755a89265b7c26ec5c7781f77d0fa9)), closes [#7](https://github.com/Vinzent03/obsidian-advanced-uri/issues/7) 597 | * encode and decode everything ([8a35341](https://github.com/Vinzent03/obsidian-advanced-uri/commit/8a35341d6ab3883d829e3dce8a4284d90859e909)) 598 | 599 | ### [1.2.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.2.0...1.2.1) (2021-04-07) 600 | 601 | 602 | ### Bug Fixes 603 | 604 | * data was changed although no data was not set ([8622476](https://github.com/Vinzent03/obsidian-advanced-uri/commit/8622476fb5698c0c1efd33b023f849e070694bc1)) 605 | 606 | ## [1.2.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.1.0...1.2.0) (2021-04-07) 607 | 608 | 609 | ### Features 610 | 611 | * encode file data properly ([70ef2ca](https://github.com/Vinzent03/obsidian-advanced-uri/commit/70ef2ca03934f61374530cd2510fdc2edfaf24cb)) 612 | * execute command in given file ([ae2d088](https://github.com/Vinzent03/obsidian-advanced-uri/commit/ae2d088b4ae2c2c0857d709a6f701d1f90460b0d)), closes [#5](https://github.com/Vinzent03/obsidian-advanced-uri/issues/5) 613 | 614 | 615 | ### Bug Fixes 616 | 617 | * commands with checkCallback didn't work ([46d2478](https://github.com/Vinzent03/obsidian-advanced-uri/commit/46d24787f42ecf59e5da1aabb4d272e421d949a9)) 618 | 619 | ## [1.1.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/1.0.0...1.1.0) (2021-04-06) 620 | 621 | 622 | ### Features 623 | 624 | * add copy URI command ([f790d90](https://github.com/Vinzent03/obsidian-advanced-uri/commit/f790d904110129aad5cdf07d5c4d54fb459d0b7e)) 625 | 626 | ## [1.0.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/0.2.2...1.0.0) (2021-04-06) 627 | 628 | 629 | ### Features 630 | 631 | * execute commands ([0a5f47f](https://github.com/Vinzent03/obsidian-advanced-uri/commit/0a5f47f8b86cb74ac5a5adb9e908bfc80f2f5e1b)) 632 | * open daily note without writing ([3d52fc9](https://github.com/Vinzent03/obsidian-advanced-uri/commit/3d52fc911d24a3d83322667450a1f40f5e8601c8)) 633 | 634 | ### [0.2.2](https://github.com/Vinzent03/obsidian-advanced-uri/compare/0.2.1...0.2.2) (2021-04-02) 635 | 636 | 637 | ### Features 638 | 639 | * copy Advanced Obsidian URI ([43983ad](https://github.com/Vinzent03/obsidian-advanced-uri/commit/43983ade0e2e4204fd0939edeab2b6a6d19ca62e)), closes [#4](https://github.com/Vinzent03/obsidian-advanced-uri/issues/4) 640 | 641 | ### [0.2.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/0.2.0...0.2.1) (2021-04-02) 642 | 643 | 644 | ### Features 645 | 646 | * add daily notes support ([c2333fe](https://github.com/Vinzent03/obsidian-advanced-uri/commit/c2333fe2ea930f4056b9365a68effc7fb6f08a9f)), closes [#2](https://github.com/Vinzent03/obsidian-advanced-uri/issues/2) 647 | 648 | ## [0.2.0](https://github.com/Vinzent03/obsidian-advanced-uri/compare/0.1.1...0.2.0) (2021-04-01) 649 | 650 | 651 | ### ⚠ BREAKING CHANGES 652 | 653 | * rename override to overwrite 654 | 655 | * rename override to overwrite ([4dfc301](https://github.com/Vinzent03/obsidian-advanced-uri/commit/4dfc3019e877e8cb6c5ea769325edca2fe42f162)) 656 | 657 | ### [0.1.1](https://github.com/Vinzent03/obsidian-advanced-uri/compare/0.1.0...0.1.1) (2021-04-01) 658 | 659 | 660 | ### Features 661 | 662 | * add prepend mode ([3040b8e](https://github.com/Vinzent03/obsidian-advanced-uri/commit/3040b8eaeb1afb3a178237df7e5b94eb7b9d9310)), closes [#1](https://github.com/Vinzent03/obsidian-advanced-uri/issues/1) 663 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vinzent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced URI 2 | 3 | A plugin for [Obsidian](https://obsidian.md) 4 | 5 | [Documentation](https://publish.obsidian.md/advanced-uri-doc) 6 | 7 | ## Overview 8 | 9 | [Advanced URI](https://github.com/Vinzent03/obsidian-advanced-uri) allows you to control many different features in Obsidian just by opening some URIs. Because they are just text and don't require any mouse clicks or keyboard inputs, they are perfect to automate your Obsidian workflow. 10 | 11 | You can for example 12 | - [open files](https://publish.obsidian.md/advanced-uri-doc/Actions/Navigation) 13 | - [edit files](https://publish.obsidian.md/advanced-uri-doc/Actions/Writing) 14 | - [create files](https://publish.obsidian.md/advanced-uri-doc/Actions/Writing) 15 | - [open workspaces](https://publish.obsidian.md/advanced-uri-doc/Actions/Navigation) 16 | - [open bookmarks](https://publish.obsidian.md/advanced-uri-doc/Actions/Bookmarks) 17 | - [navigate to headings/blocks](https://publish.obsidian.md/advanced-uri-doc/Actions/Navigation) 18 | - [automated search and replace in a file](https://publish.obsidian.md/advanced-uri-doc/Actions/Search) 19 | - [call commands](https://publish.obsidian.md/advanced-uri-doc/Actions/Commands) 20 | - [edit and read from frontmatter](https://publish.obsidian.md/advanced-uri-doc/actions/frontmatter) 21 | - [canvas movement](https://publish.obsidian.md/advanced-uri-doc/actions/canvas) 22 | - and much more 23 | 24 | Please read the [documentation](https://publish.obsidian.md/advanced-uri-doc) for a detailed explanation. 25 | 26 | ## Examples 27 | 28 | ### Append content from the clipboard to today's daily note 29 | ```uri 30 | obsidian://adv-uri?vault=&daily=true&clipboard=true&mode=append 31 | ``` 32 | 33 | ### Export a file to PDF by calling the command "Export to PDF" via its command ID 34 | ```uri 35 | obsidian://adv-uri?vault=&filepath=&commandid=workspace%3Aexport-pdf 36 | ``` 37 | 38 | ### Open heading in a file 39 | ```uri 40 | obsidian://adv-uri?vault=&filepath=my-file&heading=Goal 41 | ``` 42 | 43 | If you find this plugin useful and would like to support its development, you can support me on [Ko-fi](https://Ko-fi.com/Vinzent). 44 | 45 | [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F195IQ5) 46 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .obsidian 2 | -------------------------------------------------------------------------------- /docs/Actions/Actions.md: -------------------------------------------------------------------------------- 1 | Actions are combinations of different parameters. 2 | For example if you pass a file path and content, it writes the content to the file. 3 | 4 | > [!note] 5 | > The `` key should be replaced by your chosen identification as described in [File Identifiers](File%20identifiers.md) -------------------------------------------------------------------------------- /docs/Actions/Bookmarks.md: -------------------------------------------------------------------------------- 1 | Open any bookmarked search, folder or file. Anything you can bookmark in Obsidian via an URI. 2 | 3 | | / | parameters | explanation | 4 | | ------------- | ---------------------- | ----------------------------- | 5 | | Open bookmark | bookmark | Opens bookmark with title `bookmark` in current tab | 6 | | Open bookmark | bookmark, openmode=tab | Opens bookmark with title `bookmark` in a new tab | 7 | 8 | For more openmodes, see [open mode](Navigation%20Parameters.md#open-mode). 9 | 10 | 11 | > [!example] 12 | > ```uri 13 | > obsidian://adv-uri?vault=&bookmark=&openmode=tab 14 | > ``` 15 | -------------------------------------------------------------------------------- /docs/Actions/Canvas.md: -------------------------------------------------------------------------------- 1 | 2 | The file identification is optional. If left out, the currently opened canvas is used. 3 | 4 | | / | parameters | explanation | 5 | | --------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | 6 | | Focus nodes | canvasnodes, | Zooms to and selects a list of canvas nodes. `canvasnodes` is a list of nodes separated by `,` | 7 | | Set viewport | canvasviewport, | Sets the x,y and zoom of the canvas view. `canvasviewport` contains the x, y, zoom values separated by `,`. A value can be `-` to leave unchanged. | 8 | | Change viewport | canvasviewport, | Increase/decrease x,y and/or zoom. Prefix a value with `++`/`--` to add/subtract a value. `canvasviewport` is still the x, y, zoom list separated by `,` | 9 | 10 | > [!tip] 11 | > Use the [[Helper Commands]] to obtain the correct node ids and current viewport values. 12 | 13 | > [!example] 14 | > 15 | > Focus the nodes `abc` and `xyz` 16 | > ```uri 17 | > obsidian://adv-uri?vault=&canvasnodes=abc%2Cxyz 18 | > ``` 19 | > Set x=100, y=-300, zoom=0.5 20 | > 21 | > ```uri 22 | > obsidian://adv-uri?vault=&canvasviewport=100%2C-300%2C0.5 23 | > ``` 24 | > 25 | > Only set y=-300 and leave other values untouched 26 | > 27 | > ```uri 28 | > obsidian://adv-uri?vault=&canvasviewport=-%2C-300%2C- 29 | > ``` 30 | > 31 | > Increase x by 100, subtract y by 300, increase zoom by 0.5 32 | > ```uri 33 | > obsidian://adv-uri?vault=&canvasviewport=++100%2C--300%2C++0.5 34 | > ``` 35 | -------------------------------------------------------------------------------- /docs/Actions/Commands.md: -------------------------------------------------------------------------------- 1 | There are two ways to identify a command. 2 | 3 | - `commandname` That's the one you see when searching in Obsidian's command palette 4 | - `commandid` That's invisible to the user, but can be read from the plugin's source code 5 | 6 | > [!note] 7 | > Using the command's ID is strongly recommended, because it's not likely to change. Using [Helper Commands](Helper%20Commands.md) the ID is automatically obtained. 8 | 9 | Each command URI supports the `confirm` parameter, which when beeing truthy (not empty and not `false`) finds the first main button and clicks it. May be used for the pdf export modal to automatically press the "Export to pdf" button. 10 | 11 | In the following `` can be replaced with either `commandname` or `commandid`. 12 | 13 | | parameters | explanation | 14 | | --------------------------------------------- | ----------------------------------------------------------------------------------------------- | 15 | | | Executes command by its name | 16 | | , | Opens file and then executes command by its name | 17 | | , , line=myline | Opens file, sets the curosor to myline and then executes command by its name | 18 | | , , mode=append | Opens file, adds empty line at the end and sets cursor, then executes command by its name | 19 | | , , mode=prepend | Opens file, adds empty line at the beginning and sets cursor, then executes command by its name | 20 | | , , mode=overwrite | Opens file, clears the file, then executes command by its name | 21 | 22 | > [!example] 23 | > 24 | > Close specific tab by its filepath: 25 | > 26 | > ```uri 27 | > obsidian://adv-uri?vault=&filepath=&commandid=workspace%3Aclose 28 | > ``` 29 | > 30 | > To explain this example: It first switches to the tab specified by `filepath` and then executes the command `Close current tab` by its ID. Resulting in the ability to close any tab by its filepath. 31 | -------------------------------------------------------------------------------- /docs/Actions/Frontmatter.md: -------------------------------------------------------------------------------- 1 | The frontmatter of a file is the metadata at the beginning of a file. It is usually written in YAML format and is enclosed by `---`. 2 | 3 | ```yaml 4 | --- 5 | title: My Title 6 | tags: [tag1, tag2] 7 | --- 8 | ``` 9 | 10 | Actions on the frontmatter are done using the `frontmatterkey` parameter. 11 | 12 | ## `frontmatterkey` parameter structure 13 | 14 | ### Simple Structure 15 | ```yaml 16 | my_item: my_value 17 | ``` 18 | 19 | To select the field `my_value` set the parameter `frontmatterkey=my_item`. 20 | 21 | ### Complex Structure 22 | ```yaml 23 | my_item: 24 | second_item: my_value 25 | ``` 26 | To select the field `my_value` use `frontmatterkey=[my_item,second_item]`. The value of `frontmatterkey` is the ordered list of keys to access your value to copy. Each key needs to be separated via `,`. 27 | 28 | ```yaml 29 | my_item: 30 | second_item: 31 | - A 32 | - B 33 | ``` 34 | To select `B` use `frontmatterkey=[my_item,second_item,1]`, because `B` is at index `1` in the list. 35 | 36 | 37 | ## Read Frontmatter 38 | 39 | You can copy values of your frontmatter to the clipboard using a [file identifier](File%20identifiers.md) and the `frontmatterkey` paramteter. 40 | 41 | **Complete example:** 42 | ``` 43 | obsidian://adv-uri?vault=&filepath=MyFile&frontmatterkey=[my_item,second_item,1] 44 | ``` 45 | 46 | ## Write Frontmatter 47 | 48 | You can also write to your frontmatter using the `frontmatterkey` and `data` parameter. If the key does not exist, it will be created. 49 | 50 | The `data` parameter is the value you want to write to the frontmatter field. It can be a string, a number, a boolean, a list, or any other JSON object. 51 | 52 | ### Simple Structure 53 | 54 | **Complete example:** 55 | 56 | Before: 57 | 58 | ```yaml 59 | my_item: 60 | second_item: 61 | - A 62 | - B 63 | ``` 64 | 65 | After: 66 | 67 | ```yaml 68 | my_item: 69 | second_item: 70 | - A 71 | - newValue 72 | ``` 73 | 74 | ``` 75 | obsidian://adv-uri?vault=&filepath=MyFile&frontmatterkey=[my_item,second_item,1]&data=NewValue 76 | ``` 77 | 78 | ### Complex Structure 79 | 80 | **Complete example:** 81 | 82 | Before: 83 | 84 | ```yaml 85 | my_item: 86 | second_item: 87 | - A 88 | - B 89 | ``` 90 | 91 | After: 92 | 93 | ```yaml 94 | my_item: 95 | second_item: 96 | - A 97 | - data: 98 | - 2 99 | - 3 100 | ``` 101 | 102 | ``` 103 | obsidian://adv-uri?vault=&filepath=MyFile&frontmatterkey=[my_item,second_item,1]&data={%22data%22:[2,3]} 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /docs/Actions/Miscellaneous.md: -------------------------------------------------------------------------------- 1 | 2 | | / | parameters | explanation | 3 | | ---------------------- | ------------------------------ | -------------------------------------------------------------------------- | 4 | | Exists | , exists=true | Copies `1` to clipboard if file exists, `0` if not. | 5 | | Update plugins | updateplugins=true | Updates all community-plugins | 6 | | Enable custom plugin | enable-plugin | Enable `enable-plugin` plugin | 7 | | Disable custom plugins | disable-plugin | Disable `disable-plugin` plugin | 8 | | Execute arbitrary code | eval | Execute any javascript code via eval. Requires extra setting to be enabled | 9 | 10 | -------------------------------------------------------------------------------- /docs/Actions/Navigation.md: -------------------------------------------------------------------------------- 1 | > [!tip] 2 | > Use the [view mode](Navigation%20Parameters.md#view-mode) parameter to e.g. switch between reading and live preview mode. 3 | 4 | > [!tip] 5 | > Use the [open mode](Navigation%20Parameters.md#open-mode) parameter to open the file always in a new tab or in a new window. 6 | 7 | | / | parameters | explanation | | 8 | | ------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --- | 9 | | load workspace | workspace | Opens the workspace called `workspace` | | 10 | | copy URI for current workspace | workspace, clipboard=true | Set any non-empty value to `workspace` and `clipboard=true` to copy the URI for the current workspace | | 11 | | save current workspace | saveworkspace=true | Saves the current workspace. (Can be combined with `workspace` to open a new workspace afterwards) | | 12 | | file | | Opens file | | 13 | | line and/or column in file | , line, column | Opens `column` in `line` in file (1 indexed) | | 14 | | offset in file | , offset | Sets the cursor at `offset` in file. Offset is the character count from the start | 15 | | heading in file | , heading | Opens the `heading` in file | | 16 | | block reference in file | , block | Opens the `block` in file | | 17 | | global block reference | block | Searches the whole vault for that block id and uses that file for | | 18 | | settings tab | settingid | Opens a settings tab by id, all plugins are supported. See [here](Settings%20navigation.md) for a list of all available options | | 19 | 20 | > [!example] 21 | > 22 | > Open **workspace** "main": 23 | > 24 | > ```uri 25 | > obsidian://adv-uri?vault=&workspace=main 26 | > ``` 27 | > 28 | > Open **heading** "Goal" in "my-file.md" (**Important:** Without syntax, only `Goal`): 29 | > 30 | > ```uri 31 | > obsidian://adv-uri?vault=&filepath=my-file&heading=Goal 32 | > ``` 33 | > 34 | > Open **block**-id "12345" in "my-file.md" (**Important:** Without syntax, only `12345`): 35 | > 36 | > ```uri 37 | > obsidian://adv-uri?vault=&filepath=my-file&block=12345 38 | > ``` 39 | > 40 | > Searches whole vault for **block**-id "12345". Ideally that block id is unique. (**Important:** Without syntax, only `12345`): 41 | > 42 | > ```uri 43 | > obsidian://adv-uri?vault=&block=12345 44 | > ``` 45 | -------------------------------------------------------------------------------- /docs/Actions/Search.md: -------------------------------------------------------------------------------- 1 | ## Per File Search 2 | 3 | | / | parameters | explanation | 4 | | --------------------- | ------------------------- | ----------------------------------------- | 5 | | Normal | search | Searches for `search` in the current file | 6 | | Normal with open file | search, | Opens specific file and searches `search` | 7 | 8 | ## Per File Search and Replace 9 | 10 | | / | parameters | explanation | 11 | | ------ | --------------------------------------- | ---------------------------------------------------------------------------- | 12 | | Normal | search, replace | Replaces every occurrence of `search` with `replace` in the current file | 13 | | Normal | search, replace, | Replaces every occurrence of `search` with `replace` in file | 14 | | RegEx | searchregex, replace | Uses `searchregex` to replace every match with `replace` in the current file | 15 | | RegEx | searchregex, replace, | Uses `searchregex` to replace every match with `replace` in file | 16 | -------------------------------------------------------------------------------- /docs/Actions/Settings navigation.md: -------------------------------------------------------------------------------- 1 | > [!note] 2 | > The settings tab of every community plugin can be opened by the plugin's id. The id can be found in `/.obsidian/plugins//manifest.json`. 3 | > 4 | 5 | ## Obsidian settings 6 | 7 | | id | Meaning | 8 | | ----------------- | ----------------- | 9 | | editor | Editor | 10 | | file | File & Links | 11 | | appearance | Appearance | 12 | | hotkeys | Hotkeys | 13 | | about | About | 14 | | account | Account | 15 | | core-plugins | Core plugins | 16 | | community-plugins | Community plugins | 17 | 18 | ## Obsidian stores 19 | 20 | | id | Meaning | 21 | | -------------- | -------------- | 22 | | theme-browser | Theme browser | 23 | | plugin-browser | Plugin browser | 24 | 25 | 26 | ## Core plugin settings 27 | 28 | | id | Meaning | 29 | | --------------- | --------------- | 30 | | note-composer | Note composer | 31 | | backlink | Backlink | 32 | | switcher | Quick Switcher | 33 | | command-palette | Command palette | 34 | | daily-notes | Daily notes | 35 | | file-recovery | File recovery | 36 | | page-preview | Page Preview | 37 | 38 | ## Setting Sections 39 | 40 | In addition to navigating to a specific setting, you can also navigate to a specific section of a setting. This is useful if you want to open a specific setting and have it scrolled into view. Use the additional `settingsection` parameter for this purpose. The parameter's value is the exact case-sensitive section name. 41 | 42 | 43 | > [!example] Example for Obsidian Settings 44 | > ```uri 45 | > obsidian://adv-uri?vault=&settingid=editor 46 | > ``` 47 | > ```uri 48 | > obsidian://adv-uri?vault=&settingid=editor&settingsection=Behavior 49 | > ``` 50 | 51 | > [!example] Example for Community Plugins Options Page 52 | > ```uri 53 | > obsidian://adv-uri?vault=Manifold-Grace&settingid= 54 | > ``` 55 | 56 | > [!note] Source 57 | > Thanks to [hyaray](https://github.com/hyaray) for collecting all setting ids on the [Obsidian forum](https://forum-zh.obsidian.md/t/topic/7365) 58 | -------------------------------------------------------------------------------- /docs/Actions/Writing.md: -------------------------------------------------------------------------------- 1 | > [!caution] 2 | > Make sure your values are properly [encoded](Concepts/Encoding.md) 3 | > 4 | 5 | > [!info] 6 | > The `data` parameter can be replaced with `clipboard=true` to get the content from the clipboard. 7 | > 8 | 9 | | / | parameters | explanation | 10 | | --------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- | 11 | | write | , data | Only writes `data` to the file if the file is not already present | 12 | | overwrite | , data, mode=overwrite | Writes `data` to `filepath` even if the file already exists | 13 | | append | , data, mode=append | Only appends `data` to the file | 14 | | prepend | , data, mode=prepend | Only prepends `data` to the file | 15 | | new | filepath, data, mode=new | Definitely creates a new file. If `filepath` already exists, an incrementing number is appended | 16 | 17 | > [!example] 18 | > **Write** "Hello World" to "my-file.md": 19 | > ```uri 20 | > obsidian://adv-uri?vault=&filepath=my-file&data=Hello%20World 21 | > ``` 22 | > 23 | > **Overwrite** "This text is overwritten" to "my-file.md": 24 | > ```uri 25 | > obsidian://adv-uri?vault=&filepath=my-file&data=This%20text%20is%20overwritten&mode=overwrite 26 | > ``` 27 | > 28 | > **Append** "Hello World" to today's **daily note**: 29 | > ```uri 30 | > obsidian://adv-uri?vault=&daily=true&data=Hello%20World&mode=append 31 | > ``` 32 | > 33 | > **Append** content from the **clipboard** to today's **daily note**: 34 | > ```uri 35 | > obsidian://adv-uri?vault=&daily=true&clipboard=true&mode=append 36 | > ``` 37 | 38 | > [!note] 39 | > When using `append` or `prepend` mode, the default separator between the existing and new content is a newline (`\n`). 40 | > 41 | > ``` 42 | > original_data 43 | > new_data 44 | > ``` 45 | > 46 | > You can customize this by using the `separator` parameter. Check out the example below. 47 | > 48 | > ```uri 49 | > obsidian://adv-uri?vault=&filepath=my-file&data=new_data&mode=append&separator=, 50 | > ``` 51 | > 52 | > In this example, the original content and the new data will be separated by a comma (`,`): 53 | > 54 | > ``` 55 | > original_data,new_data 56 | > ``` 57 | 58 | > [!note] 59 | You may use the `heading` or `line` parameter to append and prepend data to a heading or line. More information in [Navigation](Actions/Navigation) 60 | -------------------------------------------------------------------------------- /docs/Concepts/Encoding.md: -------------------------------------------------------------------------------- 1 | Special characters like `?` and spaces need to be encoded. There are many online encoders. An example is [this tool](https://www.urlencoder.io/). Simply enter the value of your parameters and use the encoded one. 2 | 3 | Some encoding examples: 4 | - space → `%20` 5 | - `/` → `%2F` 6 | - `%` → `%25` 7 | 8 | The key `myKey` and value `Hello World` need to be encoded like the following: 9 | 10 | ```uri 11 | obsidian://adv-uri?myKey=Hello%20World 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /docs/Concepts/File identifiers.md: -------------------------------------------------------------------------------- 1 | There are multiple ways to identify a file: 2 | 3 | - [File path](#file-path) 4 | - [File name](#file-name) 5 | - [Daily note](#daily-note) 6 | - [Key in frontmatter](#key-in-frontmatter) 7 | 8 | > [!caution] 9 | > Make sure your values are properly [encoded](Concepts/Encoding.md) 10 | > 11 | 12 | ## File path 13 | 14 | - Key: `filepath` 15 | - Value: Relative path to the vault 16 | - Example: `hobbies/soccer.md` / `hobbies/soccer` 17 | - Note: You can omit the file extension `.md`. 18 | 19 | ## File name 20 | 21 | - Key: `filename` 22 | - Value: Only the name of the file without the actual path 23 | - Example: `soccer` / `soccer.md` 24 | - Note: You can omit the file extension `.md`. It prefers just the file name, like when linking via `[[fileName]]`, causing aliases to be supported. 25 | 26 | 27 | ## Daily note 28 | 29 | - Key: `daily` 30 | - Value: `true` 31 | - Example: `daily=true` 32 | - Note: To use the current daily note simply set the key to `true`. If it doesn't exist already, it will be created. 33 | 34 | ## Key in frontmatter 35 | 36 | - Key: `uid` 37 | - Example: `uid=d43f7a17-058c-4aea-b8dc-515ea646825a` 38 | - Use case: Some users prefer navigating to specific notes per UUID instead of the file path to be able to rename these files, but to keep the link still working. 39 | - Note: By enabling that option in the setting, every generated command with the `filepath` parameter is replaced with the `uid` parameter. The uid is either read from the frontmatter or generated and then written to the frontmatter. 40 | 41 | > [!info] 42 | > [Navigation](Actions/Navigation.md) with `uid` is always supported and doesn't need the setting to be enabled. 43 | > 44 | 45 | > [!info] 46 | > By specifying `uid` and `filepath` it creates a new file, if no file with `uid` exists, at `filepath` and writes `uid` to the frontmatter. 47 | > 48 | 49 | ## Examples 50 | 1) 51 | `obsidian://adv-uri?&filepath=Inbox` 52 | This will open a note "Inbox" in your Obsidian vault even if there is no Inbox note present in your vault. 53 | 54 | 2) 55 | `obsidian://adv-uri?daily=true&heading=Inbox` 56 | This will open your daily note and place the cursor under Inbox heading if there is one. 57 | Sample image: [image](https://user-images.githubusercontent.com/95166364/205477904-dc974487-65e7-4480-a99b-d9ab0b1a2536.png) 58 | 59 | 3) 60 | Suppose you have a note called Inbox which has an alias "Brain Dumps". Now comes the use case for the `filename` parameter. 61 | Use this command 62 | `obsidian://adv-uri?filename=Brain%20Dumps` 63 | Example : [image](https://user-images.githubusercontent.com/95166364/205478454-b6949bf6-cf8c-4218-abaa-3d1bf22bfc1e.png) 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/Concepts/Navigation Parameters.md: -------------------------------------------------------------------------------- 1 | ## View mode 2 | 3 | Every action opening or focusing a pane supports the parameter `viewmode`. Accepted values: 4 | - `source`: Sets the editor to editing:source mode 5 | - `live`: Sets the editor to editing:live preview 6 | - `preview`: Sets the editor to reading mode 7 | 8 | ## Open mode 9 | 10 | Every action opening a pane supports the parameter `openmode`. Accepted values: 11 | - `true` opens file in new pane if not already opened 12 | - `false` opens file in current pane if not already opened 13 | - `window` 14 | - `split` 15 | - `tab` 16 | - `silent` doesn't open the file 17 | - `popover` which requires the [Hover Editor plugin](obsidian://show-plugin?id=obsidian-hover-editor) to be installed and enabled 18 | 19 | If the file is already opened in another pane, it gets focused. 20 | 21 | You can set a default value in the plugin's settings. The value from the setting gets overwritten by specifying it in the URI. -------------------------------------------------------------------------------- /docs/Concepts/Schema.md: -------------------------------------------------------------------------------- 1 | # Schema 2 | 3 | Passing values to the URI is handled like for every other URL. 4 | (Almost) every URI starts with `obsidian://adv-uri`. Values are set in key value pairs `key=value` separated from the start with `?`. The key value pairs itself are separated with `&`. 5 | 6 | An example URI looks like the following: 7 | 8 | ```url 9 | obsidian://adv-uri?key1=value1&key2=value2 10 | ``` 11 | 12 | Prior to version 1.44.0, the URI started with `obsidian://advanced-uri` and needed every value to be encoded twice. This is not necessary anymore. 13 | 14 | > [!caution] 15 | > Make sure your values are properly [encoded](Concepts/Encoding.md) 16 | > 17 | 18 | ## Vault parameter 19 | 20 | **Every** Obsidian URI supports the `vault` parameter to specify the vault in which to execute the URI. By leaving it empty, your last used vault is used. 21 | 22 | > [!example] 23 | > Specific vault: 24 | > ```uri 25 | > obsidian://adv-uri?vault=myVault&key1=value1 26 | > ``` 27 | > 28 | > Last used vault: 29 | > ```uri 30 | > obsidian://adv-uri?key1=value1 31 | > ``` 32 | -------------------------------------------------------------------------------- /docs/External/Hook.md: -------------------------------------------------------------------------------- 1 | 2 | [Hook](https://hookproductivity.com) manages links to various third party applications. Since version 3.4.3 they support [Advanced URI](https://github.com/Vinzent03/obsidian-advanced-uri) to get `file://` links, robust `obsidian://` links and create new files via URI. 3 | 4 | See [their documentation](https://hookproductivity.com/help/integration/using-hook-with-obsidian/#advanced) for a detailed explanation. 5 | 6 | ## Get `file://` and `obsidian://` URIs 7 | 8 | That as today, the only action not starting with `advanced-uri`. Instead it uses `hook-get-advanced-uri`. 9 | 10 | It requires the `x-success` parameter and optionally the `x-error` parameter. It appends an `obsidian://advanced-uri` URI via the parameter `advanceduri` and an `file://` URI via the `fileuri` parameter to the `x-success` uri and launches that URI. 11 | 12 | > [!info] 13 | > The benefit of this plugin over the default Obsidian URI is the feature of using robust links via frontmatter keys. By enabling `Use UID instead of file paths` in the plugin's settings, it creates those IDs automatically for `obsidian://advanced-uri` URIs. 14 | > -------------------------------------------------------------------------------- /docs/Getting started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | # Getting Started 5 | 6 | We are going through the whole workflow of creating and launching an URI to open a note. 7 | 8 | ## Create URI 9 | 10 | ### Collect parameters 11 | 12 | Let's say we want to open the file `Home Index/today.md`. By looking at the [navigation action](Actions/Navigation.md) the only parameter we need is a file [identification](File%20identifiers.md). Since we want to create a new file, we use the `filepath` parameter. 13 | 14 | As you can see, our file path includes a space and a slash. We have to [encode](Concepts/Encoding.md) special characters. By entering `Home Index/today` (You can omit the file extension) in an [online url encoder](https://www.urlencoder.io/) you get `Home%20Index%2Ftoday` as an output. Now we have the parameter key `filepath` and the parameter value `Home%20Index%2Ftoday`. 15 | 16 | ### Construct URI 17 | 18 | As stated in the [Schema](Concepts/Schema.md) every URI has to start with `obsidian://adv-uri`. Please refer to the [Schema](Concepts/Schema.md) for more detailed explanation. Our final URI is the following. 19 | 20 | ```uri 21 | obsidian://adv-uri?filepath=Home%20Index%2Ftoday 22 | ``` 23 | 24 | ## Launch URI 25 | 26 | There are **many** ways to launch an URI. I'm just listing the most common 27 | 28 | ### Browser 29 | 30 | You can simply enter the URI into the search bar. It will ask you to confirm the execution. 31 | 32 | ### Link in Obsidian 33 | 34 | You can launch an Obsidian URI from Obsidian itself. Because `obsidian://` is a more custom protocol, it doesn't recognize it as a link directly. To fix this, wrap it in a markdown link. 35 | 36 | ```md 37 | [This here is shown](obsidian://adv-uri?filepath=Home%20Index%2Ftoday) 38 | ``` 39 | 40 | ### Terminal 41 | 42 | #### Linux 43 | 44 | ```bash 45 | xdg-open "obsidian://adv-uri?filepath=Home%20Index%2Ftoday" 46 | ``` 47 | 48 | #### Mac 49 | Use the Mac shell command `open` to launch Obsidian, and with `--background` let Obsidian run in background. 50 | 51 | ```bash 52 | open --background "obsidian://adv-uri?vault=my-vault&filename=my-file&data=my-data" 53 | ``` 54 | 55 | #### Windows PowerShell 56 | Use the PowerShell command `Start-Process` to launch an Obsidian URI. 57 | 58 | ```bash 59 | Start-Process "obsidian://adv-uri?filepath=test" 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/Home.md: -------------------------------------------------------------------------------- 1 | # Advanced URI 2 | 3 | ## Overview 4 | 5 | [Advanced URI](https://github.com/Vinzent03/obsidian-advanced-uri) allows you to control many different features in Obsidian just by opening some URIs. Because they are just text and don't require any mouse clicks or keyboard inputs, they are perfect to automate your Obsidian workflow. 6 | 7 | You can for example 8 | - [open files](Actions/Navigation.md) 9 | - [edit files](Actions/Writing.md) 10 | - [create files](Actions/Writing.md) 11 | - [open workspaces](Actions/Navigation.md) 12 | - [navigate to headings/blocks](Actions/Navigation.md) 13 | - [automated search and replace in a file](Actions/Search.md) 14 | - [canvas movement](Actions/Canvas.md) 15 | 16 | ## Motivation 17 | 18 | I created this [Obsidian](https://obsidian.md) plugin initially to load workspaces per URI. 19 | I even created a [feature request](https://forum.obsidian.md/t/load-workspace-per-url-scheme/7120) on the forum to add that feature to Obsidian's own URI schema, but I ended up solving it myself with this plugin. 20 | 21 | Since then, **many** new features were added (most of them from feature requests on [GitHub](https://github.com/Vinzent03/obsidian-advanced-uri)). 22 | 23 | ## External resources 24 | 25 | - \[iOS] [Drafts like "just start writing" for your daily notes](https://forum.obsidian.md/t/journal-log-workflow-drafts-like-just-start-writing-for-your-daily-notes-ios/18382) 26 | - [Meta - URL Scheme Actions & Parameters](https://forum.obsidian.md/t/meta-url-scheme-actions-parameters/7035) 27 | - [Shimmering Obsidian](https://github.com/chrisgrieser/shimmering-obsidian) an Alfred workflow using many Advanced URI features under the hood 28 | 29 | > [!info] Supporting 30 | > If you find this plugin useful and would like to support its development, you can support me on [Ko-fi](https://Ko-fi.com/Vinzent). 31 | > 32 | > [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F195IQ5) 33 | -------------------------------------------------------------------------------- /docs/Installing.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | ### From Obsidian 4 | 1. Open settings -> Community plugins 5 | 2. Disable Restricted mode 6 | 3. Install [Advanced URI](obsidian://show-plugin?id=obsidian-advanced-uri) 7 | 8 | If the link does not work, click Browse community plugins and search for "Advanced URI". 9 | 4. Click Enable 10 | 11 | ### From GitHub 12 | 1. Download the [latest release](https://github.com/Vinzent03/obsidian-advanced-uri/releases/latest) 13 | 2. Move `manifest.json` and `main.js` to `/.obsidian/plugins/obsidian-advanced-uri` 14 | 3. Reload Obsidian 15 | 4. Go to settings and disable restricted mode 16 | 5. Enable `Advanced URI` 17 | -------------------------------------------------------------------------------- /docs/Tips/From within Obsidian.md: -------------------------------------------------------------------------------- 1 | You can access the query parameters of the last opened Advanced URI via the following: 2 | 3 | ```js 4 | const lastParameters = app.plugins.plugins["obsidian-advanced-uri"].lastParameters 5 | ``` 6 | 7 | This can be useful to continue processing the URI via the dataview or templater plugin. See [#77](https://github.com/Vinzent03/obsidian-advanced-uri/issues/77) for the initial request and use case. -------------------------------------------------------------------------------- /docs/Tips/Helper Commands.md: -------------------------------------------------------------------------------- 1 | There are multiple helper commands to generate the URI for you. 2 | 3 | - Copy URI for file 4 | - When you are in a heading or block with a reference, a URI to navigate to that heading/block is copied to your clipboard. Otherwise, a modal is opened, where you can type in your data that should be written to the current file. 5 | - Copy URI for daily note 6 | - Copy URI for search and replace 7 | - Copy URI for command 8 | - Copy URI for canvas viewport 9 | - Copy URI for selected canvas nodes -------------------------------------------------------------------------------- /docs/Tips/Tips and Tricks.md: -------------------------------------------------------------------------------- 1 | ## Insert templates ([Templater](https://github.com/SilentVoid13/Templater) supported) 2 | 3 | I suggest you using my [hotkeys for templates](https://github.com/Vinzent03/obsidian-hotkeys-for-templates) plugin. Just enable your templates and insert them by calling the command via URI. 4 | - Command id for core templates: `obsidian-hotkeys-for-templates:` 5 | - Command id for Templater templates: `obsidian-hotkeys-for-templates:templater:` 6 | 7 | ## Open starred notes 8 | 9 | I suggest you using my [Hotkeys for starred files](https://github.com/Vinzent03/obsidian-shortcuts-for-starred-files) plugin. 10 | 11 | Same as for the templates. Just call the commands. 12 | - Command id to open file in current pane: `obsidian-shortcuts-for-starred-files:open-file-` 13 | - Command id to open file in a new pane: `obsidian-shortcuts-for-starred-files:open-file-in-new-pane-` 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/bookmarks.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Bookmarks 6 | 7 | Open any bookmarked search, folder or file. Anything you can bookmark in Obsidian via an URI. 8 | 9 | | / | parameters | explanation | 10 | | ------------- | ---------------------- | ----------------------------- | 11 | | Open bookmark | bookmark | Opens bookmark with title `bookmark` in current tab | 12 | | Open bookmark | bookmark, openmode=tab | Opens bookmark with title `bookmark` in a new tab | 13 | 14 | For more openmodes, see [open mode](../concepts/navigation_parameters.md#open-mode). 15 | 16 | 17 | :::note Example 18 | ```uri 19 | obsidian://adv-uri?vault=&bookmark=&openmode=tab 20 | ``` 21 | ::: 22 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 命令 6 | 7 | 有两种方式来识别命令 8 | 9 | - `commandname` 你可以在Obsidian命令面板中看到这些值 10 | - `commandid` 虽然这对用户来说是不可见的,但你可以从插件的源码中获取该值 11 | 12 | :::info 13 | 强烈推荐使用命令 ID,因为它似乎不会改变 使用 [辅助命令](../tips/helper_commands.md) 将自动获取命令 ID. 14 | ::: 15 | 16 | 在下列中的 `` 可以被 `commandname` 或`commandid`取代。 17 | 18 | | 参数 | 介绍 | 19 | | --------------------------------------------- | ---------------------------------------------------- | 20 | | | 按名执行命令 | 21 | | , | 打开文件之后按名执行命令 | 22 | | , , line=myline | 打开文件,将光标定位到 myline 处而后按名执行命令 | 23 | | , , mode=append | 打开文件,在末尾添加空行并设置光标,之后按名执行命令 | 24 | | , , mode=prepend | 打开文件,在开始添加空行并设置光标,之后按名执行命令 | 25 | | , , mode=overwrite | 打开文件,清空文件之后按名执行命令 | 26 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/frontmatter.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vinzent03/obsidian-advanced-uri/46af7e448f9ecbb12d265d85c2cadc790bfdabf2/docs/i18n/zh-CN/actions/frontmatter.md -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/index.md: -------------------------------------------------------------------------------- 1 | # 动作 2 | 3 | 动作是不同参数的组合。 4 | 例如如果你键入了一个文件路径及内容,他将把内容写入到文件中 5 | 6 | :::info 7 | `` 键应被你按照 [文件识别符](../concepts/file_identifiers.md) 选择的识别符替换掉 8 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/miscellaneous.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # 杂类 6 | 7 | | / | parameters | explanation | 8 | | -------- | ------------------------------ | -------------------------------------------- | 9 | | 存在判断 | , exists=true | 如果文件存在则复制`1`到剪贴板,反之则返回`0` | 10 | | 插件升级 | updateplugins=true | 升级全部第三方插件 | 11 | | 启用插件 | enable-plugin | 启动 `enable-plugin` 插件 | 12 | | 禁用插件 | disable-plugin | 禁用 `disable-plugin` 插件 | 13 | 14 | ## 读取 Frontmatter 15 | 16 | 你可以使用 `frontmatterkey` 参数读取 frontmatter 的值。 17 | 18 | ### 简单结构 19 | 20 | ```yaml 21 | my_item: my_value 22 | ``` 23 | 24 | 设置`frontmatterkey=my_item`参数来复制`my_value`到剪贴板。 25 | 26 | ### 复合结构 27 | 28 | ```yaml 29 | my_item: 30 | second_item: my_value 31 | ``` 32 | 33 | 使用`frontmatterkey=[my_item,second_item]`来复制 `my_value`到剪贴板。如`frontmatterkey`复制出的的值是有序列表,每个值之间需要用`,`分割。 34 | 35 | ```yaml 36 | my_item: 37 | second_item: 38 | - A 39 | - B 40 | ``` 41 | 42 | 使用`frontmatterkey=[my_item,second_item,1]`来复制 `B`到剪贴板 , 因为`B` 在列表中的索引位为 `1` 。 43 | 44 | **完整示例:** 45 | 46 | ``` 47 | obsidian://adv-uri?vault=&filepath=MyFile&frontmatterkey=[my_item,second_item,1] 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/navigation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 导航 6 | 7 | | / | 参数 | explanation | 8 | | -------------- | -------------------------- | ------------------------------------------------------------------------------------------ | 9 | | 工作区 | workspace | 打开名为 `workspace` 的工作区 | 10 | | 保存当前工作区 | saveworkspace=true | 保存当前工作区(可以通过绑定 `workspace`在此后打开新的工作区) | 11 | | 文件 | | 打开文件 | 12 | | 文件中行 | , line | 打开指定文件中的 `line` 行 | 13 | | 标题 | , heading | 打开指定文件中的 `heading` | 14 | | 块引用 | , block | 打开指定文件中的 `block` | 15 | | 设置标签 | settingid | 使用 ID 打开设置页面,全部插件都支持,点击 [这里](settings_navigation.md) 查看可用设置列表 | 16 | 17 | :::note Example 18 | 19 | 打开“main”**工作区** : 20 | 21 | ```uri 22 | obsidian://adv-uri?vault=&workspace=main 23 | ``` 24 | 25 | 打开"my-file.md"中 **标题** "Goal" (**注意:** 仅 `Goal`即可,没有语法表示): 26 | 27 | ```uri 28 | obsidian://adv-uri?vault=&filepath=my-file&heading=Goal 29 | ``` 30 | 31 | 打开"my-file.md"中 id 为"12345" 的**块** (**注意:** 仅`12345`即可,没有语法标识): 32 | 33 | ```uri 34 | obsidian://adv-uri?vault=&filepath=my-file&block=12345 35 | ``` 36 | 37 | ::: 38 | 39 | :::tip 40 | 你可以指定自定义的 [视图模式](../concepts/navigation_parameters.md) 41 | ::: 42 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # 查找替换 6 | 7 | | / | 参数 | 意义 | 8 | | ---------- | --------------------------------------- | -------------------------------------------- | 9 | | 普通 | search, replace | 使用 `replace`替换当前文件中每个`search` | 10 | | 普通 | search, replace, | 使用 `replace`替换目标文件中每个`search` | 11 | | 正则表达式 | searchregex, replace | 使用 `searchregex`替换当前文件中每个`search` | 12 | | 正则表达式 | searchregex, replace, | 使用 `searchregex`替换当前文件中每个`search` | 13 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/settings_navigation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # 导航设置 6 | 7 | :::info 8 | 每个设置的选项卡都可以通过插件 ID 来打开。可以在`/.obsidian/plugins//manifest.json`找到对应的 ID 值。 9 | ::: 10 | 11 | ## Obsidian 设置 12 | 13 | | id | 含义 | 14 | | ----------------- | ---------- | 15 | | editor | 编辑器 | 16 | | file | 文件及链接 | 17 | | appearance | 外观 | 18 | | hotkeys | 快捷键 | 19 | | about | 关于 | 20 | | account | 账户 | 21 | | core-plugins | 核心插件 | 22 | | community-plugins | 第三方插件 | 23 | 24 | ## Obsidian 二级页面 25 | 26 | | id | 含义 | 27 | | -------------- | -------- | 28 | | theme-browser | 主题浏览 | 29 | | plugin-browser | 插件浏览 | 30 | 31 | ## 核心插件设置 32 | 33 | | id | 含义 | 34 | | --------------- | -------- | 35 | | note-composer | 笔记重组 | 36 | | backlink | 反链 | 37 | | switcher | 快速切换 | 38 | | command-palette | 命令面板 | 39 | | daily-notes | 日记 | 40 | | file-recovery | 文件恢复 | 41 | | page-preview | 页面预览 | 42 | 43 | :::note Example 44 | 45 | ```uri 46 | obsidian://adv-uri?vault=&settingid=editor 47 | ``` 48 | 49 | ::: 50 | 51 | :::note Source 52 | 感谢 [hyaray](https://github.com/hyaray) 收集了 [Obsidian forum](https://forum-zh.obsidian.md/t/topic/7365) 的全部插件 ID。 53 | ::: 54 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/actions/writing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 写入 6 | 7 | :::caution 8 | 确保你的值被完全 [编码](../concepts/encoding.md) 9 | ::: 10 | 11 | :::info 12 | `data` 参数可以通过设置 `clipboard=true` 来使用你剪贴板的值进行覆盖。 13 | ::: 14 | 15 | | / | 参数 | 解释 | 16 | | ---- | --------------------------------------- | ------------------------------------------------------------------ | 17 | | 写入 | , data | 仅当文件不存在时将`data`写入文件 | 18 | | 覆写 | , data, mode=overwrite | 文件存在也将 `data` 写入 `filepath` | 19 | | 追加 | , data, mode=append | 仅在文件后追加 `data` | 20 | | 前插 | , data, mode=prepend | 仅在文件前插入 `data` | 21 | | 新建 | filepath, data, mode=new | 创建一个文件。如果 `filepath` 文件存在则新建一个带有递增数字的文件 | 22 | 23 | :::note Example 24 | **写入** "Hello World" 到 "my-file.md": 25 | 26 | ```uri 27 | obsidian://adv-uri?vault=&filepath=my-file&data=Hello%20World 28 | ``` 29 | 30 | **覆写** "This text is overwritten" 到 "my-file.md": 31 | 32 | ```uri 33 | obsidian://adv-uri?vault=&filepath=my-file&data=This%20text%20is%20overwritten&mode=overwrite 34 | ``` 35 | 36 | **追加** "Hello World" 到今天的 **日记**: 37 | 38 | ```uri 39 | obsidian://adv-uri?vault=&daily=true&data=Hello%20World&mode=append 40 | ``` 41 | 42 | 从**剪贴板追加** 内容到 **daily note**: 43 | 44 | ```uri 45 | obsidian://adv-uri?vault=&daily=true&clipboard=true&mode=append 46 | ``` 47 | 48 | :::info 49 | 你可以使用 `heading` 参数来在标题进行追加或前插数据。更多信息见 [导航](i18n/zh-CN/docusaurus-plugin-content-docs/current/actions/navigation.md) 50 | ::: 51 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/concepts/encoding.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 编码 6 | 7 | 特殊符号如`?`和空格需要被编码。有一些在线编码网站例如 [此工具](https://www.urlencoder.io/)。只需简单地输入你得参数值再编码即可 8 | 9 | 一些编码示例: 10 | 11 | - 空格 → `%20` 12 | - `/` → `%2F` 13 | - `%` → `%25` 14 | 15 | 键值对`myKey=Hello World` 需要被编码如下: 16 | 17 | ```uri 18 | obsidian://adv-uri?myKey=Hello%20World 19 | ``` 20 | 21 | 如需使用`xdg-open`启动 URI,你需要将值编码两次 22 | 23 | ```uri 24 | obsidian://adv-uri?myKey=Hello%20World 25 | ``` 26 | 27 | 可以注意到,此处`%`被`%25`取代了 28 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/concepts/file_identifiers.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 文件识别符 6 | 7 | 有多种方式来指向一个文件: 8 | 9 | 1. [文件识别符](#文件识别符) 10 | 1. [文件路径](#文件路径) 11 | 2. [文件名](#文件名) 12 | 3. [Daily note](#daily-note) 13 | 4. [frontmatter 区键](#frontmatter-区键) 14 | 15 | :::caution 16 | 确保你的值被完全 [编码](i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/encoding.md) 17 | ::: 18 | 19 | ## 文件路径 20 | 21 | - 键:`filepath` 22 | - 值:基于库的路径 23 | - 示例:`hobbies/soccer.md` / `hobbies/soccer` 24 | - 提示:你可以忽略掉`.md`扩展名。 25 | 26 | ## 文件名 27 | 28 | - 键:`filename` 29 | - 值:仅文件名而不需实际路径 30 | - 示例:`soccer` / `soccer.md` 31 | - 提示:你可以忽略`.md`扩展名。他如`[[fileName]]`一样仅需要文件名,也支持别名。 32 | 33 | ## Daily note 34 | 35 | - 键:`daily` 36 | - 值:`true` 37 | - 示例:`daily=true` 38 | - 提示:使用当天的日记仅需要将值设置为`true`. 如不存在则将自动创建该文件。 39 | 40 | ## frontmatter 区键 41 | 42 | - 键:`uid` 43 | - 示例:`uid=d43f7a17-058c-4aea-b8dc-515ea646825a` 44 | - 使用场景:一些用户希望通过使用 UUID 来替代文件路径导航到特殊的笔记的方式来使他们重命名文件时链接依旧生效 45 | - 提示:通过在设置中进行设置将使每一次创建命令时使用`uid`参数来替代掉 `filepath`参数。该 uid 值将读取 frontmatter 中的 uid 或在 frontmatter 中创建该值。 46 | 47 | :::info 48 | 使用`uid`来进行 [导航](../actions/navigation.md) 是默认被设置为开启且支持的。 49 | ::: 50 | 51 | :::info 52 | By specifying `uid` and `filepath` it creates a new file, if no file with `uid` exists, at `filepath` and writes `uid` to the frontmatter. 53 | 通过特殊的`uid`和`filepath`创建文件时,如无文件具有该`uid` 则在该文件路径写入 `uid`。 54 | ::: 55 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/concepts/navigation_parameters.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # 导航参数 6 | 7 | ## 视图模式 8 | 9 | 每一个打开或聚焦到一个分栏的动作都支持`viewmode`参数。接受如下值: 10 | 11 | - `source`:设置编辑器为源码模式。 12 | - `preview`:设置编辑器为阅读模式。 13 | - `live`:设置编辑器为实时阅览模式。 14 | 15 | ## 新分栏 16 | 17 | 每一个打开分栏的动作都支持`newpane`参数。接受如下值: 18 | 19 | - `true` 如果未打开当前文件则在新分栏中打开。 20 | - `false` 如果未打开当前文件则在当前分栏中打开。 21 | - 如果文件已经在另一分栏中打开,则切换焦点到该分栏。 22 | 23 | 你可以在插件设置中设置默认值。当在 URI 中定义该值时将替代默认值 24 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/concepts/schema.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 框架 6 | 7 | 传递值给 URI 的方式与其他 URL 方式类似 8 | 9 | 几乎每一个 URI 都开始于`obsidian://adv-uri`。值被设置在`?`后的键值对`key=value`中。键值对本身由`&`进行分割 10 | 11 | 例如下面的示例 URI: 12 | 13 | ```url 14 | obsidian://adv-uri?key1=value1&key2=value2 15 | ``` 16 | 17 | :::caution 18 | 确保你的值被完整的 [编码](i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/encoding.md) 19 | ::: 20 | 21 | ## 库参数 22 | 23 | **每一个** Obsidian URI 都支持`vault`参数来定义执行 URI 的目标库。如果将其置空,则将使用你最近一次使用的库。 24 | 25 | :::note Example 26 | 特定库: 27 | 28 | ```uri 29 | obsidian://adv-uri?vault=myVault&key1=value1 30 | ``` 31 | 32 | 最近使用的库: 33 | 34 | ```uri 35 | obsidian://adv-uri?key1=value1 36 | ``` 37 | 38 | ::: 39 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/external/hook.md: -------------------------------------------------------------------------------- 1 | # Hook 2 | 3 | [Hook](https://hookproductivity.com) 管理指向各种第三方应用程序的链接。从 3.4.3 版本开始,它们支持 [Advanced URI](https://github.com/Vinzent03/obsidian-advanced-uri) 以通过 URI 获取`file:///`链接、强大的`obsidian://`链接以及创建新文件。 4 | 5 | 阅读 [他们的文档](https://hookproductivity.com/help/integration/using-hook-with-obsidian/#advanced) 来获得详细的说明。 6 | 7 | ## Get `file://` and `obsidian://` URIs 8 | 9 | 截止到现在,他们唯一的动作不是 `advanced-uri`。 而是使用 `hook-get-advanced-uri`。 10 | 11 | 需要设置`x-success` 及 `x-error` 参数。 他通过参数 `advanceduri`支持 `obsidian://advanced-uri` URI 通过`fileuri`参数支持 `file://` URI 传递给 `x-success` URI 并启动该 URI. 12 | 13 | :::info 14 | 这款插件与默认的 Obsidian URI 相比优势在于具有使用强大的基于 frontmatter 的链接功能。通过在插件设置中启用`Use UID instead of file paths`它可以自动为`obsidian://advanced-uri`创建 UID 15 | ::: 16 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/getting_started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 开始使用 6 | 7 | 这里,我们讲解通过创建并启动 URI 来打开笔记的完整的工作流。 8 | 9 | ## 创建 URI 10 | 11 | ### 收集参数 12 | 13 | 假设我们想要打开文件`Home Index/today.md`。通过查看 [操作导航](Actions/Navigation.md) 得知我们唯一需要的参数是一个文件的 [识别符](File%20identifiers.md)。因此我们我们可以使用`filepath`参数来创建一个新的文件。 14 | 15 | 如你所见,我们的文件地址包括了一个空格和一个斜线。因此,我们必须对特殊符号进行 [编码](Concepts/Encoding.md)。通过在 [在线 url 编码](https://www.urlencoder.io/) 输入`Home Index/today`(你可以忽略掉文件的拓展名),你获得了`Home%20Index%2Ftoday`的输出。现在我们有了参数键`filepath`和参数值`Home%20Index%2Ftoday`。 16 | 17 | ### 构建 URI 18 | 19 | 如 [架构](Concepts/Schema.md) 中所述,每个 URI 都必须以 `obsidian://advanced-uri`开头。有关更详细的说明,请参阅 [架构](Concepts/Schema.md)。我们最终得到的 URI 如下所示。 20 | 21 | ```uri 22 | obsidian://adv-uri?filepath=Home%20Index%2Ftoday 23 | ``` 24 | 25 | ## 启动 URI 26 | 27 | 有**很多**种启动 URI 的方式。我仅仅列出最常见的部分 28 | 29 | ### 浏览器 30 | 31 | 你可以简单的在搜索栏输入 URI。他将询问你是否拉起外部应用。 32 | 33 | ### Obsidian 内部链接 34 | 35 | 你可以在 Obsidian 内部启动一个 ObsidianURI. 因为`obsidian://`是一个自定义的连接方式,它不会被直接认为是一个链接。我们可以通过将他涵盖在一个 markdown 链接里来修复这一点。 36 | 37 | ```md 38 | [This here is shown](obsidian://adv-uri?filepath=Home%20Index%2Ftoday) 39 | ``` 40 | 41 | ### 终端 42 | 43 | #### Linux 44 | 45 | 对于`xdg-open`来说整个 URI 编码需要编译两次.查看[编码](Concepts/Encoding.md)来获得更多信息 46 | 47 | ```bash 48 | xdg-open "obsidian://adv-uri?filepath=Home%20Index%2Ftoday" 49 | ``` 50 | 51 | #### Mac 52 | 53 | 你可以使用 Mac 的 shell 命令`open`来启动 Obsidian,并使用`--background`参数来让 Obsidian 在后台运行。 54 | 55 | ```bash 56 | open --background "obsidian://adv-uri?vault=my-vault&filename=my-file&data=my-data" 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: / 4 | --- 5 | 6 | # Obsidian Advanced URI 7 | 8 | ## 概述 9 | 10 | [Obsidian Advanced URI](https://github.com/Vinzent03/obsidian-advanced-uri) 11 | 12 | 允许你控制许多 Obsidian 一些仅通过 URI 提供的不同的功能。 它们是不需要任何鼠标点击或键盘输入的纯文本,因而对于自动化你的 Obsidian 工作流来说它们是很棒的。 13 | 14 | 例如: 15 | 16 | - [打开文件](Actions/Navigation.md) 17 | - [编辑文件](Actions/Writing.md) 18 | - [创建文件](Actions/Writing.md) 19 | - [打开工作区](Actions/Navigation.md) 20 | - [定位到标题/区块](Actions/Navigation.md) 21 | - [文件内自动搜索替换](Actions/Search.md) 22 | 23 | ## 开发目的 24 | 25 | 起初,我创建这款 [Obsidian](https://obsidian.md) 插件是为了可以实现每个不同的 URI 加载不同的工作区。 26 | 27 | 我曾在论坛提出一个 [功能请求](https://forum.obsidian.md/t/load-workspace-per-url-scheme/7120) 希望可以添加 Obsidian 自己的 URI 方案,但最后我自己用这款插件解决了这个需求。 28 | 29 | 从那之后增添了许多新的功能(大部分都是来自于 [GitHub](https://github.com/Vinzent03/obsidian-advanced-uri) 的功能请求)。 30 | 31 | 时至今日,我甚至不再使用这款插件了,但我很开心许多人仍在使用甚至部分人每天都在使用。 32 | 33 | ## 外部链接 34 | 35 | - \[iOS] [Drafts like "just start writing" for your daily notes](https://forum.obsidian.md/t/journal-log-workflow-drafts-like-just-start-writing-for-your-daily-notes-ios/18382) 36 | - [Meta - URL Scheme Actions & Parameters](https://forum.obsidian.md/t/meta-url-scheme-actions-parameters/7035) 37 | - [Shimmering Obsidian](https://github.com/chrisgrieser/shimmering-obsidian) 一个在 hood 环境下使用许多 Advanced URL 功能的 Alfred 工作流 38 | 39 | :::info 支持我 40 | 如果你觉得这款插件很有用且希望支持它的开发,你可以通过 [Ko-fi](https://Ko-fi.com/Vinzent) 支持我。 41 | 42 | [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F195IQ5) 43 | ::: 44 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/installing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 安装 6 | 7 | ### 从 Obsidian 社区安装 8 | 9 | 1. 设置 -> 第三方插件 10 | 2. 关闭安全模式 11 | 3. 安装 [Advanced Obsidian URI](obsidian://show-plugin?id=obsidian-advanced-uri) 12 | 13 | 如果链接无法使用,点击 社区插件市场 -> 浏览,搜索“Advanced Obsidian URI”。 14 | 4. 在安装后选择启用 15 | 16 | ### 从 GitHub 安装 17 | 18 | 1. 下载 [latest release](https://github.com/Vinzent03/obsidian-advanced-uri/releases/latest) 19 | 2. 移动 `manifest.json` 、 `main.js` 到 `/.obsidian/plugins/obsidian-advanced-uri` 路径 (如不存在则手动新建此文件夹) 20 | 3. 重新加载 Obsidian (CTRL + R)/(CTRL + P -> 重新加载 Obsidian) 21 | 4. 设置 -> 第三方插件 -> 关闭安全模式 22 | 5. 启用 `Advanced Obsidian URI` 23 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/tips/helper_commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 辅助命令 6 | 7 | 这里是几个可以为您生成 URI 的辅助程序命令 8 | 9 | - `Copy URI for file`复制文件 URI 10 | - 当光标在标题或者引用块时,将复制一个指向当前标题/块的 URI 到你的剪贴板。此外,将打开输入窗口来进行输入 11 | - `Copy URI for daily note`复制日记笔记 URI 12 | - `Copy URI for search and replace`复制检索替换 URI 13 | - `Copy URI for command`复制命令 URI 14 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN/tips/index.md: -------------------------------------------------------------------------------- 1 | # 使用提示及技巧 2 | 3 | ## 支持插入 [Templater](https://github.com/SilentVoid13/Templater) 模板 4 | 5 | 建议使用我的 [快捷键模板](https://github.com/Vinzent03/obsidian-hotkeys-for-templates) 插件。它可以支持通过 URI 拉起并插入模板。 6 | 7 | - 核心插件命令 ID`obsidian-hotkeys-for-templates:` 8 | - Templater 模板命令 ID`obsidian-hotkeys-for-templates:templater:` 9 | 10 | ## 打开星标文档 11 | 12 | 建议使用我的 [星标文件快捷键](https://github.com/Vinzent03/obsidian-shortcuts-for-starred-files) 插件。 13 | 14 | 与模板相同,这里是拉起的命令 15 | 16 | - 在当前面板打开`obsidian-shortcuts-for-starred-files:open-file-` 17 | - 在新面板中打开`obsidian-shortcuts-for-starred-files:open-file-in-new-pane-` 18 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | 4 | const banner = `/* 5 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 6 | if you want to view the source visit the plugins github repository (https://github.com/Vinzent03/obsidian-advanced-uri) 7 | */ 8 | `; 9 | 10 | const prod = process.argv[2] === "production"; 11 | 12 | const context = await esbuild.context({ 13 | banner: { 14 | js: banner, 15 | }, 16 | entryPoints: ["src/main.ts"], 17 | bundle: true, 18 | external: ["obsidian"], 19 | format: "cjs", 20 | target: "es2018", 21 | logLevel: "info", 22 | sourcemap: prod ? false : "inline", 23 | treeShaking: true, 24 | platform: "browser", 25 | minify: prod, 26 | outfile: "main.js", 27 | }); 28 | 29 | if (prod) { 30 | await context.rebuild(); 31 | process.exit(0); 32 | } else { 33 | await context.watch(); 34 | } 35 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-advanced-uri", 3 | "name": "Advanced URI", 4 | "description": "Advanced modes for Obsidian URI", 5 | "isDesktopOnly": false, 6 | "js": "main.js", 7 | "fundingUrl": "https://ko-fi.com/vinzent", 8 | "version": "1.44.3", 9 | "author": "Vinzent", 10 | "authorUrl": "https://github.com/Vinzent03" 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-advanced-uri", 3 | "version": "1.44.3", 4 | "description": "Advanced modes for Obsidian URI", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs dev", 8 | "build": "node esbuild.config.mjs production", 9 | "release": "standard-version", 10 | "format": "prettier src --check" 11 | }, 12 | "keywords": [], 13 | "standard-version": { 14 | "t": "" 15 | }, 16 | "author": "Vinzent", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@types/uuid": "^8.3.4", 20 | "obsidian": "^1.1.1", 21 | "standard-version": "^9.5.0", 22 | "tslib": "^2.4.1", 23 | "typescript": "^5.1.6", 24 | "esbuild": "^0.18.10" 25 | }, 26 | "dependencies": { 27 | "obsidian-community-lib": "git://github.com/obsidian-community/obsidian-community-lib.git", 28 | "obsidian-daily-notes-interface": "^0.9.4", 29 | "uuid": "^8.3.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/block_utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | Editor, 4 | ListItemCache, 5 | MarkdownView, 6 | SectionCache, 7 | TFile, 8 | } from "obsidian"; 9 | 10 | export abstract class BlockUtils { 11 | private static getBlock( 12 | app: App, 13 | editor: Editor, 14 | file: TFile 15 | ): (SectionCache | ListItemCache) | undefined { 16 | const cursor = editor.getCursor("to"); 17 | const fileCache = app.metadataCache.getFileCache(file); 18 | const sections = fileCache?.sections; 19 | if (!sections || sections.length === 0) { 20 | console.log('error reading FileCache (empty file?)'); 21 | return; 22 | } 23 | const foundSectionIndex = sections.findIndex(section => section.position.start.line > cursor.line); 24 | let currentBlock: SectionCache | ListItemCache = foundSectionIndex > 0 ? sections[foundSectionIndex - 1] : sections[sections.length - 1]; 25 | if (currentBlock?.type == "list") { 26 | currentBlock = fileCache.listItems?.find(section => 27 | section.position.start.line <= cursor.line && 28 | section.position.end.line >= cursor.line 29 | ) ?? currentBlock; 30 | } 31 | return currentBlock; 32 | } 33 | 34 | private static getIdOfBlock( 35 | editor: Editor, 36 | block: SectionCache | ListItemCache 37 | ): string { 38 | const blockId = block.id; 39 | 40 | if (blockId) { 41 | return blockId; 42 | } 43 | 44 | // Add a block id 45 | const sectionEnd = block.position.end; 46 | const pos = { 47 | ch: sectionEnd.col, 48 | line: sectionEnd.line, 49 | }; 50 | 51 | const newId = Math.random().toString(36).substring(2, 8); 52 | const spacer = BlockUtils.shouldInsertAfter(block) ? "\n\n" : " "; 53 | 54 | editor.replaceRange(`${spacer}^${newId}`, pos); 55 | return newId; 56 | } 57 | 58 | private static shouldInsertAfter( 59 | block: SectionCache | ListItemCache 60 | ): boolean { 61 | if ((block as any).type) { 62 | return [ 63 | "blockquote", 64 | "code", 65 | "table", 66 | "heading", 67 | "comment", 68 | "footnoteDefinition", 69 | ].includes((block as any).type); 70 | } 71 | } 72 | 73 | public static getBlockId(app: App): string | undefined { 74 | const view = app.workspace.getActiveViewOfType(MarkdownView); 75 | if (view) { 76 | const editor = view.editor; 77 | const file = view.file; 78 | const block = this.getBlock(app, editor, file); 79 | if (block) return this.getIdOfBlock(editor, block); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { AdvancedURISettings } from "./types"; 2 | 3 | export const DEFAULT_SETTINGS: AdvancedURISettings = { 4 | openFileOnWrite: true, 5 | openDailyInNewPane: false, 6 | openFileOnWriteInNewPane: false, 7 | openFileWithoutWriteInNewPane: false, 8 | idField: "id", 9 | useUID: false, 10 | addFilepathWhenUsingUID: false, 11 | allowEval: false, 12 | includeVaultName: true, 13 | vaultParam: "name", 14 | }; 15 | -------------------------------------------------------------------------------- /src/daily_note_utils.ts: -------------------------------------------------------------------------------- 1 | import { normalizePath } from "obsidian"; 2 | import { getDailyNoteSettings } from "obsidian-daily-notes-interface"; 3 | 4 | //! All of these methods are taken from https://www.npmjs.com/package/obsidian-daily-notes-interface. 5 | function join(...partSegments: string[]): string { 6 | // Split the inputs into a list of path commands. 7 | let parts: string[] = []; 8 | for (let i = 0, l = partSegments.length; i < l; i++) { 9 | parts = parts.concat(partSegments[i].split("/")); 10 | } 11 | // Interpret the path commands to get the new resolved path. 12 | const newParts = []; 13 | for (let i = 0, l = parts.length; i < l; i++) { 14 | const part = parts[i]; 15 | // Remove leading and trailing slashes 16 | // Also remove "." segments 17 | if (!part || part === ".") continue; 18 | // Push new path segments. 19 | else newParts.push(part); 20 | } 21 | // Preserve the initial slash if there was one. 22 | if (parts[0] === "") newParts.unshift(""); 23 | // Turn back into a single string path. 24 | return newParts.join("/"); 25 | } 26 | 27 | async function getNotePath( 28 | directory: string, 29 | filename: string 30 | ): Promise { 31 | if (!filename.endsWith(".md")) { 32 | filename += ".md"; 33 | } 34 | const path = normalizePath(join(directory, filename)); 35 | 36 | await ensureFolderExists(path); 37 | 38 | return path; 39 | } 40 | 41 | async function ensureFolderExists(path: string): Promise { 42 | const dirs = path.replace(/\\/g, "/").split("/"); 43 | dirs.pop(); // remove basename 44 | 45 | if (dirs.length) { 46 | const dir = join(...dirs); 47 | if (!(window as any).app.vault.getAbstractFileByPath(dir)) { 48 | await (window as any).app.vault.createFolder(dir); 49 | } 50 | } 51 | } 52 | 53 | export async function getDailyNotePath(date: any): Promise { 54 | const { format, folder } = getDailyNoteSettings(); 55 | 56 | const filename = date.format(format); 57 | const normalizedPath = await getNotePath(folder, filename); 58 | return normalizedPath; 59 | } 60 | -------------------------------------------------------------------------------- /src/handlers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FileView, 3 | MarkdownView, 4 | Notice, 5 | TAbstractFile, 6 | TFile, 7 | View, 8 | } from "obsidian"; 9 | import AdvancedURI from "./main"; 10 | import { EnterDataModal } from "./modals/enter_data_modal"; 11 | import { FileModal } from "./modals/file_modal"; 12 | import Tools from "./tools"; 13 | import { CanvasView, Parameters } from "./types"; 14 | import { copyText, getAlternativeFilePath } from "./utils"; 15 | export default class Handlers { 16 | constructor(private readonly plugin: AdvancedURI) {} 17 | app = this.plugin.app; 18 | public get tools(): Tools { 19 | return this.plugin.tools; 20 | } 21 | 22 | handlePluginManagement(parameters: Parameters): void { 23 | if (parameters["enable-plugin"]) { 24 | const pluginId = parameters["enable-plugin"]; 25 | 26 | if ( 27 | pluginId in this.app.plugins.manifests && 28 | !this.app.plugins.getPlugin(pluginId) 29 | ) { 30 | this.app.plugins.enablePluginAndSave(pluginId); 31 | new Notice(`Enabled ${pluginId}`); 32 | } else if (this.app.internalPlugins.plugins[pluginId]) { 33 | this.app.internalPlugins.plugins[pluginId].enable(true); 34 | new Notice(`Enabled ${pluginId}`); 35 | } 36 | } else if (parameters["disable-plugin"]) { 37 | const pluginId = parameters["disable-plugin"]; 38 | 39 | if (this.app.plugins.getPlugin(pluginId)) { 40 | this.app.plugins.disablePluginAndSave(pluginId); 41 | new Notice(`Disabled ${pluginId}`); 42 | } else if (this.app.internalPlugins.plugins[pluginId]) { 43 | this.app.internalPlugins.plugins[pluginId].disable(true); 44 | new Notice(`Disabled ${pluginId}`); 45 | } 46 | } 47 | } 48 | handleFrontmatterKey(parameters: Parameters) { 49 | const key = parameters.frontmatterkey; 50 | const file = this.app.vault.getAbstractFileByPath( 51 | parameters.filepath ?? this.app.workspace.getActiveFile().path 52 | ); 53 | if (!(file instanceof TFile)) { 54 | return; 55 | } 56 | const frontmatter = 57 | this.app.metadataCache.getFileCache(file).frontmatter; 58 | 59 | if (parameters.data) { 60 | let data = parameters.data; 61 | try { 62 | // This try catch is needed to allow passing strings as a data value without extra ". 63 | data = JSON.parse(data); 64 | } catch { 65 | data = `"${data}"`; 66 | data = JSON.parse(data); 67 | } 68 | this.app.fileManager.processFrontMatter(file, (frontmatter) => { 69 | if (key.startsWith("[") && key.endsWith("]")) { 70 | const list = key.substring(1, key.length - 1).split(","); 71 | let cache: any = frontmatter; 72 | for (let i = 0; i < list.length; i++) { 73 | const item = list[i]; 74 | if (cache instanceof Array) { 75 | const index = parseInt(item); 76 | if (Number.isNaN(index)) { 77 | cache = cache.find((e) => e == item); 78 | } 79 | if (i == list.length - 1) { 80 | cache[parseInt(item)] = data; 81 | } else { 82 | cache = cache[parseInt(item)]; 83 | } 84 | } else { 85 | if (i == list.length - 1) { 86 | cache[item] = data; 87 | } else { 88 | cache = cache[item]; 89 | } 90 | } 91 | } 92 | } else { 93 | frontmatter[key] = data; 94 | } 95 | }); 96 | } else { 97 | let res: string; 98 | if (key.startsWith("[") && key.endsWith("]")) { 99 | const list = key.substring(1, key.length - 1).split(","); 100 | let cache: any = frontmatter; 101 | for (const item of list) { 102 | if (cache instanceof Array) { 103 | const index = parseInt(item); 104 | if (Number.isNaN(index)) { 105 | cache = cache.find((e) => e == item); 106 | } 107 | cache = cache[parseInt(item)]; 108 | } else { 109 | cache = cache[item]; 110 | } 111 | } 112 | res = cache; 113 | } else { 114 | res = frontmatter[key]; 115 | } 116 | 117 | copyText(res); 118 | } 119 | } 120 | 121 | handleWorkspace(parameters: Parameters) { 122 | const workspaces = 123 | this.app.internalPlugins.getEnabledPluginById("workspaces"); 124 | if (!workspaces) { 125 | new Notice("Workspaces plugin is not enabled"); 126 | this.plugin.failure(parameters); 127 | } else { 128 | if (parameters.saveworkspace == "true") { 129 | const active = workspaces.activeWorkspace; 130 | workspaces.saveWorkspace(active); 131 | new Notice(`Saved current workspace to ${active}`); 132 | } 133 | if (parameters.clipboard && parameters.clipboard != "false") { 134 | this.tools.copyURI({ 135 | workspace: workspaces.activeWorkspace, 136 | }); 137 | } else if (parameters.workspace != undefined) { 138 | workspaces.loadWorkspace(parameters.workspace); 139 | } 140 | this.plugin.success(parameters); 141 | } 142 | } 143 | 144 | async handleCommand(parameters: Parameters) { 145 | if (parameters.filepath) { 146 | if (parameters.mode) { 147 | if (parameters.mode == "new") { 148 | const file = this.app.metadataCache.getFirstLinkpathDest( 149 | parameters.filepath, 150 | "/" 151 | ); 152 | if (file instanceof TFile) { 153 | parameters.filepath = getAlternativeFilePath( 154 | this.app, 155 | file 156 | ); 157 | } 158 | } 159 | await this.plugin.open({ 160 | file: parameters.filepath, 161 | mode: "source", 162 | parameters: parameters, 163 | }); 164 | const view = 165 | this.app.workspace.getActiveViewOfType(MarkdownView); 166 | if (view) { 167 | const editor = view.editor; 168 | const data = editor.getValue(); 169 | if (parameters.mode === "append") { 170 | editor.setValue(data + "\n"); 171 | const lines = editor.lineCount(); 172 | editor.setCursor({ ch: 0, line: lines }); 173 | } else if (parameters.mode === "prepend") { 174 | editor.setValue("\n" + data); 175 | editor.setCursor({ ch: 0, line: 0 }); 176 | } else if (parameters.mode === "overwrite") { 177 | editor.setValue(""); 178 | } 179 | } 180 | } else if ( 181 | parameters.line != undefined || 182 | parameters.column != undefined || 183 | parameters.offset != undefined 184 | ) { 185 | await this.plugin.open({ 186 | file: parameters.filepath, 187 | mode: "source", 188 | parameters: parameters, 189 | }); 190 | 191 | await this.plugin.setCursorInLine(parameters); 192 | } else { 193 | await this.plugin.open({ 194 | file: parameters.filepath, 195 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 196 | parameters: parameters, 197 | }); 198 | } 199 | } else if (parameters.openmode || parameters.viewmode) { 200 | // Open a new leaf without a file. For example in a new window or split 201 | await this.plugin.open({ 202 | parameters: parameters, 203 | }); 204 | } 205 | if (parameters.commandid) { 206 | this.app.commands.executeCommandById(parameters.commandid); 207 | } else if (parameters.commandname) { 208 | const rawCommands = this.app.commands.commands; 209 | for (const command in rawCommands) { 210 | if (rawCommands[command].name === parameters.commandname) { 211 | if (rawCommands[command].callback) { 212 | await rawCommands[command].callback(); 213 | } else { 214 | rawCommands[command].checkCallback(false); 215 | } 216 | break; 217 | } 218 | } 219 | } 220 | 221 | if (parameters.confirm && parameters.confirm != "false") { 222 | await new Promise((r) => setTimeout(r, 750)); 223 | const button = document.querySelector( 224 | ".mod-cta:not([style*='display: none'])" 225 | ) as any; 226 | if (button.click instanceof Function) { 227 | button.click(); 228 | } 229 | } 230 | this.plugin.success(parameters); 231 | } 232 | 233 | async handleEval(parameters: Parameters) { 234 | if (parameters.filepath) { 235 | if (parameters.mode) { 236 | if (parameters.mode == "new") { 237 | const file = this.app.metadataCache.getFirstLinkpathDest( 238 | parameters.filepath, 239 | "/" 240 | ); 241 | if (file instanceof TFile) { 242 | parameters.filepath = getAlternativeFilePath( 243 | this.app, 244 | file 245 | ); 246 | } 247 | } 248 | await this.plugin.open({ 249 | file: parameters.filepath, 250 | mode: "source", 251 | parameters: parameters, 252 | }); 253 | const view = 254 | this.app.workspace.getActiveViewOfType(MarkdownView); 255 | if (view) { 256 | const editor = view.editor; 257 | const data = editor.getValue(); 258 | if (parameters.mode === "append") { 259 | editor.setValue(data + "\n"); 260 | const lines = editor.lineCount(); 261 | editor.setCursor({ ch: 0, line: lines }); 262 | } else if (parameters.mode === "prepend") { 263 | editor.setValue("\n" + data); 264 | editor.setCursor({ ch: 0, line: 0 }); 265 | } else if (parameters.mode === "overwrite") { 266 | editor.setValue(""); 267 | } 268 | } 269 | } else if ( 270 | parameters.line != undefined || 271 | parameters.column != undefined || 272 | parameters.offset != undefined 273 | ) { 274 | await this.plugin.open({ 275 | file: parameters.filepath, 276 | mode: "source", 277 | parameters: parameters, 278 | }); 279 | 280 | await this.plugin.setCursorInLine(parameters); 281 | } else { 282 | await this.plugin.open({ 283 | file: parameters.filepath, 284 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 285 | parameters: parameters, 286 | }); 287 | } 288 | } 289 | if (this.plugin.settings.allowEval) { 290 | //Call eval in a global scope 291 | const eval2 = eval; 292 | eval2(parameters.eval); 293 | this.plugin.success(parameters); 294 | } else { 295 | new Notice( 296 | "Eval is not allowed. Please enable it in the settings." 297 | ); 298 | this.plugin.failure(parameters); 299 | } 300 | } 301 | 302 | async handleDoesFileExist(parameters: Parameters) { 303 | const exists = await this.app.vault.adapter.exists(parameters.filepath); 304 | 305 | copyText((exists ? 1 : 0).toString()); 306 | this.plugin.success(parameters); 307 | } 308 | async handleSearchAndReplace(parameters: Parameters) { 309 | let file: TFile; 310 | if (parameters.filepath) { 311 | const abstractFile = this.app.vault.getAbstractFileByPath( 312 | parameters.filepath 313 | ); 314 | if (abstractFile instanceof TFile) { 315 | file = abstractFile; 316 | } 317 | } else { 318 | file = this.app.workspace.getActiveFile(); 319 | } 320 | 321 | if (file) { 322 | let data = await this.app.vault.read(file); 323 | if (parameters.searchregex) { 324 | try { 325 | const [, , pattern, flags] = 326 | parameters.searchregex.match(/(\/?)(.+)\1([a-z]*)/i); 327 | const regex = new RegExp(pattern, flags); 328 | data = data.replace(regex, parameters.replace); 329 | this.plugin.success(parameters); 330 | } catch (error) { 331 | new Notice( 332 | `Can't parse ${parameters.searchregex} as RegEx` 333 | ); 334 | this.plugin.failure(parameters); 335 | } 336 | } else { 337 | data = data.replaceAll(parameters.search, parameters.replace); 338 | this.plugin.success(parameters); 339 | } 340 | 341 | await this.plugin.writeAndOpenFile(file.path, data, parameters); 342 | } else { 343 | new Notice("Cannot find file"); 344 | this.plugin.failure(parameters); 345 | } 346 | } 347 | 348 | async handleSearch(parameters: Parameters) { 349 | if (parameters.filepath) { 350 | await this.plugin.open({ 351 | file: parameters.filepath, 352 | parameters: parameters, 353 | }); 354 | } 355 | const view = this.app.workspace.getActiveViewOfType(FileView); 356 | view.currentMode.showSearch(); 357 | const search = view.currentMode.search; 358 | search.searchInputEl.value = parameters.search; 359 | search.searchInputEl.dispatchEvent(new Event("input")); 360 | } 361 | 362 | async handleWrite( 363 | parameters: Parameters, 364 | createdDailyNote: boolean = false 365 | ) { 366 | let file: TAbstractFile | null; 367 | if (parameters.filepath) { 368 | file = this.app.vault.getAbstractFileByPath(parameters.filepath); 369 | } else { 370 | file = this.app.workspace.getActiveFile(); 371 | } 372 | 373 | if (parameters.filepath || file) { 374 | let outFile: TFile; 375 | let path = parameters.filepath ?? file.path; 376 | if (parameters.mode === "overwrite") { 377 | outFile = await this.plugin.writeAndOpenFile( 378 | path, 379 | parameters.data, 380 | parameters 381 | ); 382 | this.plugin.success(parameters); 383 | } else if (parameters.mode === "prepend") { 384 | if (file instanceof TFile) { 385 | outFile = await this.plugin.prepend(file, parameters); 386 | } else { 387 | outFile = await this.plugin.prepend(path, parameters); 388 | } 389 | this.plugin.success(parameters); 390 | } else if (parameters.mode === "append") { 391 | if (file instanceof TFile) { 392 | outFile = await this.plugin.append(file, parameters); 393 | } else { 394 | outFile = await this.plugin.append(path, parameters); 395 | } 396 | this.plugin.success(parameters); 397 | } else if (parameters.mode === "new") { 398 | if (file instanceof TFile) { 399 | outFile = await this.plugin.writeAndOpenFile( 400 | getAlternativeFilePath(this.app, file), 401 | parameters.data, 402 | parameters 403 | ); 404 | this.plugin.hookSuccess(parameters, outFile); 405 | } else { 406 | outFile = await this.plugin.writeAndOpenFile( 407 | path, 408 | parameters.data, 409 | parameters 410 | ); 411 | this.plugin.hookSuccess(parameters, outFile); 412 | } 413 | } else if (!createdDailyNote && file instanceof TFile) { 414 | new Notice("File already exists"); 415 | this.plugin.openExistingFileAndSetCursor(file.path, parameters); 416 | this.plugin.failure(parameters); 417 | } else { 418 | outFile = await this.plugin.writeAndOpenFile( 419 | path, 420 | parameters.data, 421 | parameters 422 | ); 423 | this.plugin.success(parameters); 424 | } 425 | if (parameters.uid) { 426 | this.tools.writeUIDToFile(outFile, parameters.uid); 427 | } 428 | } else { 429 | new Notice("Cannot find file"); 430 | this.plugin.failure(parameters); 431 | } 432 | } 433 | 434 | async handleOpen(parameters: Parameters) { 435 | if (parameters.heading != undefined) { 436 | await this.plugin.open({ 437 | file: parameters.filepath + "#" + parameters.heading, 438 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 439 | parameters: parameters, 440 | }); 441 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 442 | if (!view) return; 443 | const cache = this.app.metadataCache.getFileCache(view.file); 444 | const heading = cache.headings.find( 445 | (e) => e.heading === parameters.heading 446 | ); 447 | view.editor.focus(); 448 | view.editor.setCursor({ 449 | line: heading.position.start.line + 1, 450 | ch: 0, 451 | }); 452 | } else if (parameters.block != undefined) { 453 | await this.plugin.open({ 454 | file: parameters.filepath + "#^" + parameters.block, 455 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 456 | parameters: parameters, 457 | }); 458 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 459 | if (!view) return; 460 | const cache = this.app.metadataCache.getFileCache(view.file); 461 | const block = cache.blocks[parameters.block.toLowerCase()]; 462 | view.editor.focus(); 463 | if (block) { 464 | view.editor.setCursor({ 465 | line: block.position.start.line, 466 | ch: 0, 467 | }); 468 | } 469 | } else { 470 | await this.plugin.open({ 471 | file: parameters.filepath, 472 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 473 | parameters: parameters, 474 | }); 475 | if ( 476 | parameters.line != undefined || 477 | parameters.column != undefined || 478 | parameters.offset != undefined 479 | ) { 480 | await this.plugin.setCursorInLine(parameters); 481 | } 482 | } 483 | if (parameters.mode != undefined) { 484 | await this.plugin.setCursor(parameters); 485 | } 486 | if (parameters.uid) { 487 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 488 | 489 | this.tools.writeUIDToFile(view.file, parameters.uid); 490 | } 491 | this.plugin.success(parameters); 492 | } 493 | 494 | async handleOpenBlock(parameters: Parameters) { 495 | const file = this.tools.getFileFromBlockID(parameters.block); 496 | if (file) { 497 | await this.plugin.chooseHandler( 498 | { 499 | ...parameters, 500 | filepath: file.path, 501 | }, 502 | false 503 | ); 504 | } 505 | } 506 | 507 | handleCopyFileURI(withoutData: boolean, file?: TFile) { 508 | const view = this.app.workspace.getActiveViewOfType(FileView); 509 | if (!view && !file) return; 510 | if (view instanceof MarkdownView) { 511 | const pos = view.editor.getCursor(); 512 | const cache = this.app.metadataCache.getFileCache(view.file); 513 | if (cache.headings) { 514 | for (const heading of cache.headings) { 515 | if ( 516 | heading.position.start.line <= pos.line && 517 | heading.position.end.line >= pos.line 518 | ) { 519 | this.tools.copyURI({ 520 | filepath: view.file.path, 521 | heading: heading.heading, 522 | }); 523 | return; 524 | } 525 | } 526 | } 527 | if (cache.blocks) { 528 | for (const blockID of Object.keys(cache.blocks)) { 529 | const block = cache.blocks[blockID]; 530 | if ( 531 | block.position.start.line <= pos.line && 532 | block.position.end.line >= pos.line 533 | ) { 534 | this.tools.copyURI({ 535 | filepath: view.file.path, 536 | block: block.id, 537 | }); 538 | return; 539 | } 540 | } 541 | } 542 | } 543 | 544 | if (withoutData) { 545 | const file2 = file ?? this.app.workspace.getActiveFile(); 546 | if (!file2) { 547 | new Notice("No file opened"); 548 | return; 549 | } 550 | this.tools.copyURI({ 551 | filepath: file2.path, 552 | }); 553 | } else { 554 | const fileModal = new FileModal( 555 | this.plugin, 556 | "Choose a file", 557 | false 558 | ); 559 | fileModal.open(); 560 | fileModal.onChooseItem = (item, _) => { 561 | new EnterDataModal(this.plugin, item.source).open(); 562 | }; 563 | } 564 | } 565 | 566 | handleOpenSettings(parameters: Parameters) { 567 | if (this.app.setting.containerEl.parentElement === null) { 568 | this.app.setting.open(); 569 | } 570 | if (parameters.settingid == "plugin-browser") { 571 | this.app.setting.openTabById("community-plugins"); 572 | this.app.setting.activeTab.containerEl.find(".mod-cta").click(); 573 | } else if (parameters.settingid == "theme-browser") { 574 | this.app.setting.openTabById("appearance"); 575 | this.app.setting.activeTab.containerEl.find(".mod-cta").click(); 576 | } else { 577 | this.app.setting.openTabById(parameters.settingid); 578 | } 579 | 580 | if (parameters.settingsection) { 581 | const elements = 582 | this.app.setting.tabContentContainer.querySelectorAll("*"); 583 | const heading: Element = Array.prototype.find.call( 584 | elements, 585 | (e: Element) => e.textContent == parameters.settingsection 586 | ); 587 | 588 | if (heading) { 589 | heading.scrollIntoView(); 590 | } 591 | } 592 | this.plugin.success(parameters); 593 | } 594 | 595 | async handleUpdatePlugins(parameters: Parameters) { 596 | new Notice("Checking for updates…"); 597 | await this.app.plugins.checkForUpdates(); 598 | 599 | const updateCount = Object.keys(this.app.plugins.updates).length; 600 | if (updateCount > 0) { 601 | parameters.settingid = "community-plugins"; 602 | this.handleOpenSettings(parameters); 603 | this.app.setting.activeTab.containerEl 604 | .findAll(".mod-cta") 605 | .last() 606 | .click(); 607 | } 608 | this.plugin.success(parameters); 609 | } 610 | 611 | async handleBookmarks(parameters: Parameters) { 612 | const bookmarksPlugin = 613 | this.app.internalPlugins.getEnabledPluginById("bookmarks"); 614 | const bookmarks = bookmarksPlugin.getBookmarks(); 615 | const bookmark = bookmarks.find((b) => b.title == parameters.bookmark); 616 | let openMode; 617 | if (parameters.openmode == "true" || parameters.openmode == "false") { 618 | openMode = parameters.openmode == "true"; 619 | } else { 620 | openMode = parameters.openmode; 621 | } 622 | bookmarksPlugin.openBookmark(bookmark, openMode as any); 623 | } 624 | 625 | async handleCanvas(parameters: Parameters) { 626 | if (parameters.filepath) { 627 | await this.plugin.open({ 628 | file: parameters.filepath, 629 | setting: this.plugin.settings.openFileWithoutWriteInNewPane, 630 | parameters: parameters, 631 | }); 632 | } 633 | const activeView = (this.app.workspace as any).activeLeaf.view as View; 634 | if (activeView.getViewType() != "canvas") { 635 | new Notice("Active view is not a canvas"); 636 | return; 637 | } 638 | const canvasView = activeView as CanvasView; 639 | if (parameters.canvasnodes) { 640 | const ids = parameters.canvasnodes.split(","); 641 | const nodes = canvasView.canvas.nodes; 642 | const selectedNodes = ids.map((id) => nodes.get(id)); 643 | const selection = canvasView.canvas.selection; 644 | 645 | canvasView.canvas.updateSelection(() => { 646 | for (const node of selectedNodes) { 647 | selection.add(node); 648 | } 649 | }); 650 | 651 | canvasView.canvas.zoomToSelection(); 652 | } 653 | if (parameters.canvasviewport) { 654 | const [x, y, zoom] = parameters.canvasviewport.split(","); 655 | if (x != "-") { 656 | if (x.startsWith("--") || x.startsWith("++")) { 657 | const tx = canvasView.canvas.tx + Number(x.substring(1)); 658 | canvasView.canvas.tx = tx; 659 | } else { 660 | canvasView.canvas.tx = Number(x); 661 | } 662 | } 663 | if (y != "-") { 664 | if (y.startsWith("--") || y.startsWith("++")) { 665 | const ty = canvasView.canvas.ty + Number(y.substring(1)); 666 | canvasView.canvas.ty = ty; 667 | } else { 668 | canvasView.canvas.ty = Number(y); 669 | } 670 | } 671 | if (zoom != "-") { 672 | if (zoom.startsWith("--") || zoom.startsWith("++")) { 673 | const tZoom = 674 | canvasView.canvas.tZoom + Number(zoom.substring(1)); 675 | canvasView.canvas.tZoom = tZoom; 676 | } else { 677 | canvasView.canvas.tZoom = Number(zoom); 678 | } 679 | } 680 | canvasView.canvas.markViewportChanged(); 681 | } 682 | } 683 | } 684 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | base64ToArrayBuffer, 3 | getLinkpath, 4 | MarkdownView, 5 | normalizePath, 6 | Notice, 7 | parseFrontMatterAliases, 8 | Platform, 9 | Plugin, 10 | TFile, 11 | TFolder, 12 | View, 13 | WorkspaceLeaf, 14 | } from "obsidian"; 15 | import { stripMD } from "obsidian-community-lib"; 16 | import { 17 | appHasDailyNotesPluginLoaded, 18 | createDailyNote, 19 | getAllDailyNotes, 20 | getDailyNote, 21 | } from "obsidian-daily-notes-interface"; 22 | import { BlockUtils } from "./block_utils"; 23 | import { DEFAULT_SETTINGS } from "./constants"; 24 | import { getDailyNotePath } from "./daily_note_utils"; 25 | import Handlers from "./handlers"; 26 | import { CommandModal } from "./modals/command_modal"; 27 | import { EnterDataModal } from "./modals/enter_data_modal"; 28 | import { FileModal } from "./modals/file_modal"; 29 | import { ReplaceModal } from "./modals/replace_modal"; 30 | import { SearchModal } from "./modals/search_modal"; 31 | import { SettingsTab } from "./settings"; 32 | import Tools from "./tools"; 33 | import { 34 | AdvancedURISettings, 35 | CanvasView, 36 | FileModalData, 37 | HookParameters, 38 | OpenMode, 39 | Parameters, 40 | SearchModalData, 41 | } from "./types"; 42 | import { 43 | getEndAndBeginningOfHeading, 44 | getFileUri, 45 | getViewStateFromMode as getOpenViewStateFromMode, 46 | } from "./utils"; 47 | import { WorkspaceModal } from "./modals/workspace_modal"; 48 | 49 | export default class AdvancedURI extends Plugin { 50 | settings: AdvancedURISettings; 51 | lastParameters?: Object; 52 | handlers = new Handlers(this); 53 | tools = new Tools(this); 54 | 55 | async onload() { 56 | await this.loadSettings(); 57 | this.addSettingTab(new SettingsTab(this.app, this)); 58 | 59 | this.addCommand({ 60 | id: "copy-uri-current-file", 61 | name: "Copy URI for file with options", 62 | callback: () => this.handlers.handleCopyFileURI(false), 63 | }); 64 | 65 | this.addCommand({ 66 | id: "copy-uri-current-file-simple", 67 | name: "Copy URI for current file", 68 | callback: () => this.handlers.handleCopyFileURI(true), 69 | }); 70 | 71 | this.addCommand({ 72 | id: "copy-uri-daily", 73 | name: "Copy URI for daily note", 74 | callback: () => new EnterDataModal(this).open(), 75 | }); 76 | 77 | this.addCommand({ 78 | id: "copy-uri-search-and-replace", 79 | name: "Copy URI for search and replace", 80 | callback: () => { 81 | const fileModal = new FileModal( 82 | this, 83 | "Used file for search and replace" 84 | ); 85 | fileModal.open(); 86 | fileModal.onChooseItem = (filePath: FileModalData) => { 87 | const searchModal = new SearchModal(this); 88 | searchModal.open(); 89 | searchModal.onChooseSuggestion = ( 90 | item: SearchModalData 91 | ) => { 92 | new ReplaceModal(this, item, filePath?.source).open(); 93 | }; 94 | }; 95 | }, 96 | }); 97 | 98 | this.addCommand({ 99 | id: "copy-uri-command", 100 | name: "Copy URI for command", 101 | callback: () => { 102 | const fileModal = new FileModal( 103 | this, 104 | "Select a file to be opened before executing the command" 105 | ); 106 | fileModal.open(); 107 | fileModal.onChooseItem = (item: FileModalData) => { 108 | new CommandModal(this, item?.source).open(); 109 | }; 110 | }, 111 | }); 112 | 113 | this.addCommand({ 114 | id: "copy-uri-block", 115 | name: "Copy URI for current block", 116 | checkCallback: (checking) => { 117 | const view = 118 | this.app.workspace.getActiveViewOfType(MarkdownView); 119 | if (checking) return view != undefined; 120 | const id = BlockUtils.getBlockId(this.app); 121 | if (id) { 122 | this.tools.copyURI({ 123 | filepath: view.file.path, 124 | block: id, 125 | }); 126 | } 127 | }, 128 | }); 129 | 130 | this.addCommand({ 131 | id: "copy-uri-workspace", 132 | name: "Copy URI for workspace", 133 | callback: () => { 134 | const modal = new WorkspaceModal(this); 135 | modal.open(); 136 | }, 137 | }); 138 | 139 | this.addCommand({ 140 | id: "copy-uri-canvas-node", 141 | name: "Copy URI for selected canvas nodes", 142 | checkCallback: (checking) => { 143 | const activeView = (this.app.workspace as any).activeLeaf 144 | .view as View; 145 | if (checking) { 146 | return ( 147 | activeView.getViewType() === "canvas" && 148 | (activeView as CanvasView).canvas.selection.size > 0 149 | ); 150 | } 151 | if (activeView.getViewType() !== "canvas") return false; 152 | 153 | const canvasView = activeView as CanvasView; 154 | 155 | let ids: string[] = []; 156 | canvasView.canvas.selection.forEach((node) => { 157 | ids.push(node.id); 158 | }); 159 | 160 | this.tools.copyURI({ 161 | canvasnodes: ids.join(","), 162 | filepath: activeView.file.path, 163 | }); 164 | }, 165 | }); 166 | 167 | this.addCommand({ 168 | id: "copy-uri-canvas-viewport", 169 | name: "Copy URI for current canvas viewport", 170 | checkCallback: (checking) => { 171 | const activeView = (this.app.workspace as any).activeLeaf 172 | .view as View; 173 | if (checking) { 174 | return activeView.getViewType() === "canvas"; 175 | } 176 | if (activeView.getViewType() !== "canvas") return false; 177 | 178 | const canvasView = activeView as CanvasView; 179 | 180 | const canvas = canvasView.canvas; 181 | const tx = canvas.tx.toFixed(0), 182 | ty = canvas.ty.toFixed(0), 183 | tZoom = canvas.tZoom.toFixed(3); 184 | this.tools.copyURI({ 185 | filepath: activeView.file.path, 186 | canvasviewport: `${tx},${ty},${tZoom}`, 187 | }); 188 | }, 189 | }); 190 | 191 | // Old version, which needed each value to be encoded twice 192 | this.registerObsidianProtocolHandler("advanced-uri", async (e) => { 193 | const parameters = e as unknown as Parameters; 194 | 195 | for (const parameter in parameters) { 196 | (parameters as any)[parameter] = decodeURIComponent( 197 | (parameters as any)[parameter] 198 | ); 199 | } 200 | 201 | this.onUriCall(parameters); 202 | }); 203 | 204 | // New version starting with v1.44.0 205 | this.registerObsidianProtocolHandler("adv-uri", async (e) => { 206 | const parameters = e as unknown as Parameters; 207 | 208 | this.onUriCall(parameters); 209 | }); 210 | 211 | this.registerObsidianProtocolHandler( 212 | "hook-get-advanced-uri", 213 | async (e) => { 214 | const parameters = e as unknown as HookParameters; 215 | for (const parameter in parameters) { 216 | (parameters as any)[parameter] = decodeURIComponent( 217 | (parameters as any)[parameter] 218 | ); 219 | } 220 | const file = this.app.workspace.getActiveFile(); 221 | if (file) { 222 | this.hookSuccess(parameters, file); 223 | } else { 224 | this.failure(parameters, { 225 | errorMessage: "No file opened", 226 | }); 227 | } 228 | } 229 | ); 230 | 231 | this.registerEvent( 232 | this.app.workspace.on("file-menu", (menu, file, source) => { 233 | if ( 234 | !( 235 | source === "more-options" || 236 | source === "tab-header" || 237 | source == "file-explorer-context-menu" 238 | ) 239 | ) { 240 | return; 241 | } 242 | 243 | if (!(file instanceof TFile)) { 244 | return; 245 | } 246 | 247 | menu.addItem((item) => { 248 | item.setTitle(`Copy Advanced URI`) 249 | .setIcon("link") 250 | .setSection("info") 251 | .onClick((_) => 252 | this.handlers.handleCopyFileURI(true, file) 253 | ); 254 | }); 255 | }) 256 | ); 257 | } 258 | 259 | async onUriCall(parameters: Parameters) { 260 | /** Allows writing to new created daily note without any `Parameters.mode` */ 261 | let createdDailyNote = false; 262 | this.lastParameters = { ...parameters }; 263 | if (parameters.uid) { 264 | const res = this.tools.getFileFromUID(parameters.uid)?.path; 265 | if (res != undefined) { 266 | parameters.filepath = res; 267 | parameters.uid = undefined; 268 | } 269 | } else if (parameters.filename) { 270 | let file = this.app.metadataCache.getFirstLinkpathDest( 271 | parameters.filename, 272 | "" 273 | ); 274 | if (!file) { 275 | file = this.app.vault 276 | .getMarkdownFiles() 277 | .find((file) => 278 | parseFrontMatterAliases( 279 | this.app.metadataCache.getFileCache(file) 280 | .frontmatter 281 | )?.includes(parameters.filename) 282 | ); 283 | } 284 | const parentFolder = this.app.fileManager.getNewFileParent( 285 | this.app.workspace.getActiveFile()?.path 286 | ); 287 | const parentFolderPath = parentFolder.isRoot() 288 | ? "" 289 | : parentFolder.path + "/"; 290 | parameters.filepath = 291 | file?.path ?? 292 | parentFolderPath + normalizePath(parameters.filename); 293 | } 294 | if (parameters.filepath) { 295 | parameters.filepath = normalizePath(parameters.filepath); 296 | const index = parameters.filepath.lastIndexOf("."); 297 | const extension = parameters.filepath.substring( 298 | index < 0 ? parameters.filepath.length : index 299 | ); 300 | 301 | if (extension === "") { 302 | parameters.filepath = parameters.filepath + ".md"; 303 | } 304 | } else if (parameters.daily === "true") { 305 | if (!appHasDailyNotesPluginLoaded()) { 306 | new Notice("Daily notes plugin is not loaded"); 307 | return; 308 | } 309 | const moment = window.moment(Date.now()); 310 | const allDailyNotes = getAllDailyNotes(); 311 | let dailyNote = getDailyNote(moment, allDailyNotes); 312 | if (!dailyNote) { 313 | /// Prevent daily note from being created on existing check 314 | if (parameters.exists === "true") { 315 | parameters.filepath = await getDailyNotePath(moment); 316 | } else { 317 | dailyNote = await createDailyNote(moment); 318 | 319 | // delay to let Obsidian index and generate CachedMetadata 320 | await new Promise((r) => setTimeout(r, 500)); 321 | 322 | createdDailyNote = true; 323 | } 324 | } 325 | if (dailyNote !== undefined) { 326 | parameters.filepath = dailyNote.path; 327 | } 328 | } 329 | if (parameters.clipboard === "true") { 330 | parameters.data = await navigator.clipboard.readText(); 331 | } 332 | 333 | this.chooseHandler(parameters, createdDailyNote); 334 | } 335 | 336 | async chooseHandler(parameters: Parameters, createdDailyNote: boolean) { 337 | if (parameters["enable-plugin"] || parameters["disable-plugin"]) { 338 | this.handlers.handlePluginManagement(parameters); 339 | } else if (parameters.frontmatterkey) { 340 | this.handlers.handleFrontmatterKey(parameters); 341 | } else if (parameters.workspace || parameters.saveworkspace == "true") { 342 | this.handlers.handleWorkspace(parameters); 343 | } else if (parameters.commandname || parameters.commandid) { 344 | this.handlers.handleCommand(parameters); 345 | } else if (parameters.bookmark) { 346 | this.handlers.handleBookmarks(parameters); 347 | } else if (parameters.eval) { 348 | this.handlers.handleEval(parameters); 349 | } else if (parameters.filepath && parameters.exists === "true") { 350 | this.handlers.handleDoesFileExist(parameters); 351 | } else if (parameters.canvasnodes || parameters.canvasviewport) { 352 | this.handlers.handleCanvas(parameters); 353 | } else if (parameters.data) { 354 | this.handlers.handleWrite(parameters, createdDailyNote); 355 | } else if (parameters.filepath && parameters.heading) { 356 | await this.handlers.handleOpen(parameters); 357 | parameters.filepath = undefined; 358 | parameters.heading = undefined; 359 | this.chooseHandler(parameters, createdDailyNote); 360 | } else if (parameters.filepath && parameters.block) { 361 | await this.handlers.handleOpen(parameters); 362 | parameters.filepath = undefined; 363 | parameters.block = undefined; 364 | this.chooseHandler(parameters, createdDailyNote); 365 | } else if ( 366 | (parameters.search || parameters.searchregex) && 367 | parameters.replace != undefined 368 | ) { 369 | this.handlers.handleSearchAndReplace(parameters); 370 | } else if (parameters.search) { 371 | this.handlers.handleSearch(parameters); 372 | } else if (parameters.filepath) { 373 | this.handlers.handleOpen(parameters); 374 | } else if (parameters.block) { 375 | this.handlers.handleOpenBlock(parameters); 376 | } else if (parameters.settingid) { 377 | this.handlers.handleOpenSettings(parameters); 378 | } else if (parameters.updateplugins) { 379 | this.handlers.handleUpdatePlugins(parameters); 380 | } 381 | } 382 | 383 | async hookSuccess(parameters: Parameters, file: TFile): Promise { 384 | if (!parameters["x-success"]) return; 385 | 386 | const options = { 387 | title: stripMD(file.name), 388 | advanceduri: await this.tools.generateURI({ filepath: file.path }), 389 | urlkey: "advanceduri", 390 | fileuri: getFileUri(this.app, file), 391 | }; 392 | this.success(parameters, options); 393 | } 394 | 395 | success(parameters: Parameters, options?: Record): void { 396 | if (parameters["x-success"]) { 397 | const url = new URL(parameters["x-success"]); 398 | for (const param in options) { 399 | url.searchParams.set(param, options[param]); 400 | } 401 | window.open(url.toString()); 402 | } 403 | } 404 | 405 | failure(parameters: Parameters, options?: Record): void { 406 | if (parameters["x-error"]) { 407 | const url = new URL(parameters["x-error"]); 408 | for (const param in options) { 409 | url.searchParams.set(param, options[param]); 410 | } 411 | window.open(url.toString()); 412 | } 413 | } 414 | 415 | async append(file: TFile | string, parameters: Parameters): Promise { 416 | let path: string; 417 | let dataToWrite: string; 418 | 419 | if (file instanceof TFile) { 420 | path = file.path; 421 | const data = await this.app.vault.read(file); 422 | const lines = data.split("\n"); 423 | 424 | // determine which line to perform append operation 425 | let line: number = undefined; // 1-indexed 426 | if (parameters.heading) { 427 | const lineInfo = getEndAndBeginningOfHeading( 428 | this.app, 429 | file, 430 | parameters.heading 431 | ); 432 | line = lineInfo?.lastLine; 433 | if (line === undefined) return; 434 | 435 | // When the specified heading has no content, we should 436 | // add a newline before the separator to make sure the content 437 | // does not inserted after the heading. 438 | if ( 439 | lineInfo.firstLine == lineInfo.lastLine && 440 | parameters.separator 441 | ) { 442 | parameters.separator = "\n" + parameters.separator; 443 | } 444 | } else if (parameters.line) { 445 | line = Number(parameters.line); 446 | } else { 447 | line = lines.length; 448 | } 449 | 450 | line = Math.max(1, line); 451 | lines[line - 1] = 452 | (lines[line - 1] ?? "") + 453 | (parameters.separator ?? "\n") + 454 | parameters.data; 455 | dataToWrite = lines.join("\n"); 456 | } else { 457 | path = file; 458 | dataToWrite = parameters.data; 459 | } 460 | 461 | return this.writeAndOpenFile(path, dataToWrite, parameters); 462 | } 463 | 464 | async prepend( 465 | file: TFile | string, 466 | parameters: Parameters 467 | ): Promise { 468 | let path: string; 469 | let dataToWrite: string; 470 | 471 | if (file instanceof TFile) { 472 | path = file.path; 473 | const data = await this.app.vault.read(file); 474 | const cache = this.app.metadataCache.getFileCache(file); 475 | 476 | const lines = data.split("\n"); 477 | 478 | // determine which line to perform prepend operation 479 | let line = undefined; // 1-indexed 480 | if (parameters.heading) { 481 | line = getEndAndBeginningOfHeading( 482 | this.app, 483 | file, 484 | parameters.heading 485 | )?.firstLine; 486 | if (line === undefined) { 487 | return; 488 | } else { 489 | line += 1; 490 | } 491 | } else if (parameters.line) { 492 | line = Number(parameters.line); 493 | } else if (cache.frontmatterPosition) { 494 | // +1 to convert 0-indexed to 1-indexed 495 | // another +1 to ensure prepend operation performed 496 | // at the next line of the end of frontmatter 497 | line = cache.frontmatterPosition.end.line + 2; 498 | } else { 499 | line = 1; 500 | } 501 | 502 | line = Math.max(1, line); 503 | lines[line - 1] = `${parameters.data}${ 504 | parameters.separator ?? "\n" 505 | }${lines[line - 1] ?? ""}`; 506 | dataToWrite = lines.join("\n"); 507 | } else { 508 | path = file; 509 | dataToWrite = parameters.data; 510 | } 511 | 512 | return this.writeAndOpenFile(path, dataToWrite, parameters); 513 | } 514 | 515 | async writeAndOpenFile( 516 | outputFileName: string, 517 | text: string, 518 | parameters: Parameters 519 | ): Promise { 520 | const file = this.app.vault.getAbstractFileByPath(outputFileName); 521 | 522 | if (file instanceof TFile) { 523 | await this.app.vault.modify(file, text); 524 | } else { 525 | const parts = outputFileName.split("/"); 526 | const dir = parts.slice(0, parts.length - 1).join("/"); 527 | if ( 528 | parts.length > 1 && 529 | !(this.app.vault.getAbstractFileByPath(dir) instanceof TFolder) 530 | ) { 531 | await this.app.vault.createFolder(dir); 532 | } 533 | const base64regex = 534 | /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; 535 | if (base64regex.test(text)) { 536 | await this.app.vault.createBinary( 537 | outputFileName, 538 | base64ToArrayBuffer(text) 539 | ); 540 | } else { 541 | await this.app.vault.create(outputFileName, text); 542 | } 543 | } 544 | this.openExistingFileAndSetCursor(outputFileName, parameters); 545 | 546 | return this.app.vault.getAbstractFileByPath(outputFileName) as TFile; 547 | } 548 | 549 | async openExistingFileAndSetCursor(file: string, parameters: Parameters) { 550 | if (parameters.openmode == "silent") return; 551 | if (this.settings.openFileOnWrite) { 552 | await this.open({ 553 | file: file, 554 | setting: this.settings.openFileOnWriteInNewPane, 555 | parameters, 556 | }); 557 | if ( 558 | parameters.line != undefined || 559 | parameters.column != undefined || 560 | parameters.offset != undefined 561 | ) { 562 | await this.setCursorInLine(parameters); 563 | } 564 | } 565 | } 566 | 567 | async open({ 568 | file, 569 | setting, 570 | parameters, 571 | supportPopover, 572 | mode, 573 | }: { 574 | file?: string | TFile; 575 | setting?: boolean; 576 | parameters: Parameters; 577 | supportPopover?: boolean; 578 | mode?: "source"; 579 | }): Promise { 580 | let leaf: WorkspaceLeaf; 581 | if (parameters.openmode == "popover" && (supportPopover ?? true)) { 582 | const hoverEditor = 583 | this.app.plugins.plugins["obsidian-hover-editor"]; 584 | if (!hoverEditor) { 585 | new Notice( 586 | "Cannot find Hover Editor plugin. Please file an issue." 587 | ); 588 | this.failure(parameters); 589 | } 590 | 591 | await new Promise((resolve) => { 592 | leaf = hoverEditor.spawnPopover(undefined, () => { 593 | this.app.workspace.setActiveLeaf(leaf, { focus: true }); 594 | resolve(); 595 | }); 596 | }); 597 | } else { 598 | let openMode: OpenMode | boolean = setting; 599 | if (parameters.newpane !== undefined) { 600 | openMode = parameters.newpane == "true"; 601 | } 602 | if (parameters.openmode !== undefined) { 603 | if ( 604 | parameters.openmode == "true" || 605 | parameters.openmode == "false" 606 | ) { 607 | openMode = parameters.openmode == "true"; 608 | } else if (parameters.openmode == "popover") { 609 | openMode = false; 610 | } else if ( 611 | Platform.isMobile && 612 | parameters.openmode == "window" 613 | ) { 614 | } else { 615 | openMode = parameters.openmode; 616 | } 617 | } 618 | if (openMode == "silent") { 619 | return; 620 | } 621 | 622 | // `window` is only supported on desktop 623 | if (Platform.isMobileApp && openMode == "window") { 624 | openMode = true; 625 | } 626 | 627 | if (file != undefined) { 628 | let fileIsAlreadyOpened = false; 629 | if (isBoolean(openMode)) { 630 | this.app.workspace.iterateAllLeaves((existingLeaf) => { 631 | if ( 632 | existingLeaf.view.file?.path === parameters.filepath 633 | ) { 634 | if (fileIsAlreadyOpened && existingLeaf.width == 0) 635 | return; 636 | fileIsAlreadyOpened = true; 637 | 638 | this.app.workspace.setActiveLeaf(existingLeaf, { 639 | focus: true, 640 | }); 641 | leaf = existingLeaf; 642 | } 643 | }); 644 | } 645 | } 646 | if (!leaf) { 647 | leaf = this.app.workspace.getLeaf(openMode); 648 | this.app.workspace.setActiveLeaf(leaf, { focus: true }); 649 | } 650 | } 651 | if (file instanceof TFile) { 652 | await leaf.openFile(file); 653 | } else if (file != undefined) { 654 | await this.app.workspace.openLinkText( 655 | file, 656 | "/", 657 | false, 658 | mode != undefined 659 | ? { state: { mode: mode } } 660 | : getOpenViewStateFromMode(parameters) 661 | ); 662 | } 663 | 664 | if (leaf.view instanceof MarkdownView) { 665 | const viewState = leaf.getViewState(); 666 | if (mode != undefined) { 667 | viewState.state.mode = mode; 668 | } else { 669 | viewState.state = { 670 | ...viewState.state, 671 | ...getOpenViewStateFromMode(parameters)?.state, 672 | }; 673 | } 674 | await leaf.setViewState(viewState); 675 | } 676 | 677 | return leaf; 678 | } 679 | 680 | async setCursor(parameters: Parameters) { 681 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 682 | if (!view) return; 683 | const mode = parameters.mode; 684 | const editor = view.editor; 685 | 686 | let viewState = view.leaf.getViewState(); 687 | viewState.state.mode = "source"; 688 | 689 | if (mode === "append") { 690 | const lastLine = editor.lastLine(); 691 | const lastLineLength = editor.getLine(lastLine).length; 692 | await view.leaf.setViewState(viewState, { focus: true }); 693 | 694 | editor.setCursor({ ch: lastLineLength, line: lastLine }); 695 | } else if (mode === "prepend") { 696 | await view.leaf.setViewState(viewState, { focus: true }); 697 | 698 | editor.setCursor({ ch: 0, line: 0 }); 699 | } 700 | 701 | await new Promise((resolve) => setTimeout(resolve, 10)); 702 | 703 | if (parameters.viewmode == "preview") { 704 | viewState.state.mode = "preview"; 705 | await view.leaf.setViewState(viewState); 706 | } 707 | } 708 | 709 | async setCursorInLine(parameters: Parameters) { 710 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 711 | if (!view) return; 712 | const viewState = view.leaf.getViewState(); 713 | 714 | const rawLine = 715 | parameters.line != undefined ? Number(parameters.line) : undefined; 716 | const rawColumn = parameters.column 717 | ? Number(parameters.column) 718 | : undefined; 719 | viewState.state.mode = "source"; 720 | await view.leaf.setViewState(viewState); 721 | 722 | let line: number, column: number; 723 | if (parameters.offset != undefined) { 724 | const pos = view.editor.offsetToPos(Number(parameters.offset)); 725 | line = pos.line; 726 | column = pos.ch; 727 | } else { 728 | line = 729 | rawLine != undefined 730 | ? Math.min(rawLine - 1, view.editor.lineCount() - 1) 731 | : view.editor.getCursor().line; 732 | const maxColumn = view.editor.getLine(line).length - 1; 733 | column = Math.min(rawColumn - 1, maxColumn); 734 | } 735 | view.editor.focus(); 736 | view.editor.setCursor({ 737 | line: line, 738 | ch: column, 739 | }); 740 | view.editor.scrollIntoView( 741 | { 742 | from: { line: line, ch: column }, 743 | to: { line: line, ch: column }, 744 | }, 745 | true 746 | ); 747 | 748 | await new Promise((resolve) => setTimeout(resolve, 10)); 749 | 750 | if (parameters.viewmode == "preview") { 751 | viewState.state.mode = "preview"; 752 | await view.leaf.setViewState(viewState); 753 | } 754 | } 755 | 756 | async loadSettings() { 757 | this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()); 758 | } 759 | 760 | async saveSettings() { 761 | await this.saveData(this.settings); 762 | } 763 | } 764 | -------------------------------------------------------------------------------- /src/modals/command_modal.ts: -------------------------------------------------------------------------------- 1 | import { Command, FuzzySuggestModal } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | 4 | export class CommandModal extends FuzzySuggestModal { 5 | plugin: AdvancedURI; 6 | file: string; 7 | constructor(plugin: AdvancedURI, file?: string) { 8 | super(plugin.app); 9 | this.plugin = plugin; 10 | this.file = file; 11 | } 12 | 13 | getItems(): Command[] { 14 | const rawCommands = this.app.commands.commands; 15 | const commands: Command[] = Object.keys(rawCommands).map((e) => { 16 | return { id: rawCommands[e].id, name: rawCommands[e].name }; 17 | }); 18 | return commands; 19 | } 20 | 21 | getItemText(item: Command): string { 22 | return item.name; 23 | } 24 | 25 | onChooseItem(item: Command, _: MouseEvent | KeyboardEvent): void { 26 | this.plugin.tools.copyURI({ 27 | filepath: this.file, 28 | commandid: item.id, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/modals/enter_data_modal.ts: -------------------------------------------------------------------------------- 1 | import { SuggestModal } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | import { EnterData, Parameters } from "../types"; 4 | 5 | export class EnterDataModal extends SuggestModal { 6 | plugin: AdvancedURI; 7 | //null if for normal write mode, its not associated with a special mode like "append" or "prepend" 8 | modes = [null, "overwrite", "append", "prepend"]; 9 | 10 | constructor( 11 | plugin: AdvancedURI, 12 | private file?: string | undefined 13 | ) { 14 | super(plugin.app); 15 | this.plugin = plugin; 16 | this.setPlaceholder( 17 | "Type your data to be written to the file or leave it empty to just open it" 18 | ); 19 | } 20 | 21 | getSuggestions(query: string): EnterData[] { 22 | if (query == "") query = null; 23 | 24 | let suggestions: EnterData[] = []; 25 | for (const mode of this.modes) { 26 | if (!(mode === "overwrite" && !query)) { 27 | let display: string; 28 | if (query) { 29 | if (mode) { 30 | display = `Write "${query}" in ${mode} mode`; 31 | } else { 32 | display = `Write "${query}"`; 33 | } 34 | } else { 35 | if (mode) { 36 | display = `Open in ${mode} mode`; 37 | } else { 38 | display = `Open`; 39 | } 40 | } 41 | suggestions.push({ 42 | data: query, 43 | display: display, 44 | mode: mode, 45 | func: () => { 46 | if (this.file) { 47 | this.plugin.tools.copyURI({ 48 | filepath: this.file, 49 | data: query, 50 | mode: mode as Parameters["mode"], 51 | }); 52 | } else { 53 | this.plugin.tools.copyURI({ 54 | daily: "true", 55 | data: query, 56 | mode: mode as Parameters["mode"], 57 | }); 58 | } 59 | }, 60 | }); 61 | } 62 | } 63 | 64 | return suggestions; 65 | } 66 | 67 | renderSuggestion(value: EnterData, el: HTMLElement): void { 68 | el.innerText = value.display; 69 | } 70 | 71 | onChooseSuggestion(item: EnterData, _: MouseEvent | KeyboardEvent): void { 72 | item.func(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/modals/file_modal.ts: -------------------------------------------------------------------------------- 1 | import { FuzzySuggestModal } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | import { FileModalData } from "../types"; 4 | 5 | export class FileModal extends FuzzySuggestModal { 6 | plugin: AdvancedURI; 7 | constructor( 8 | plugin: AdvancedURI, 9 | private placeHolder: string, 10 | private allowNoFile: boolean = true 11 | ) { 12 | super(plugin.app); 13 | this.plugin = plugin; 14 | this.setPlaceholder(this.placeHolder); 15 | } 16 | 17 | getItems(): FileModalData[] { 18 | let specialItems: FileModalData[] = []; 19 | if (this.allowNoFile) { 20 | specialItems.push({ 21 | display: "", 22 | source: undefined, 23 | }); 24 | } 25 | const file = this.app.workspace.getActiveFile(); 26 | if (file) { 27 | specialItems.push({ display: "", source: file.path }); 28 | } 29 | return [ 30 | ...specialItems, 31 | ...this.app.vault.getFiles().map((e) => { 32 | return { display: e.path, source: e.path }; 33 | }), 34 | ]; 35 | } 36 | 37 | getItemText(item: FileModalData): string { 38 | return item.display; 39 | } 40 | 41 | onChooseItem(item: FileModalData, evt: MouseEvent | KeyboardEvent): void {} 42 | } 43 | -------------------------------------------------------------------------------- /src/modals/replace_modal.ts: -------------------------------------------------------------------------------- 1 | import { SuggestModal } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | import { SearchModalData } from "../types"; 4 | 5 | export class ReplaceModal extends SuggestModal { 6 | plugin: AdvancedURI; 7 | emptyText = "Empty text (replace with nothing)"; 8 | constructor( 9 | plugin: AdvancedURI, 10 | private search: SearchModalData, 11 | private filepath: string 12 | ) { 13 | super(plugin.app); 14 | this.plugin = plugin; 15 | this.setPlaceholder("Replacement text"); 16 | } 17 | 18 | getSuggestions(query: string): string[] { 19 | if (query === "") { 20 | query = this.emptyText; 21 | } 22 | return [query]; 23 | } 24 | 25 | renderSuggestion(value: string, el: HTMLElement): void { 26 | el.innerText = value; 27 | } 28 | 29 | onChooseSuggestion(item: string, _: MouseEvent | KeyboardEvent): void { 30 | if (this.search.isRegEx) { 31 | this.plugin.tools.copyURI({ 32 | filepath: this.filepath, 33 | searchregex: this.search.source, 34 | replace: item == this.emptyText ? "" : item, 35 | }); 36 | } else { 37 | this.plugin.tools.copyURI({ 38 | filepath: this.filepath, 39 | search: this.search.source, 40 | replace: item == this.emptyText ? "" : item, 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modals/search_modal.ts: -------------------------------------------------------------------------------- 1 | import { SuggestModal } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | import { SearchModalData } from "../types"; 4 | 5 | export class SearchModal extends SuggestModal { 6 | plugin: AdvancedURI; 7 | 8 | constructor(plugin: AdvancedURI) { 9 | super(plugin.app); 10 | this.plugin = plugin; 11 | this.setPlaceholder("Searched text. RegEx is supported"); 12 | } 13 | 14 | getSuggestions(query: string): SearchModalData[] { 15 | if (query === "") { 16 | query = "..."; 17 | } 18 | let regex: RegExp; 19 | try { 20 | regex = new RegExp(query); 21 | } catch (error) {} 22 | return [ 23 | { 24 | source: query, 25 | isRegEx: false, 26 | display: query, 27 | }, 28 | { 29 | source: query, 30 | display: regex ? `As RegEx: ${query}` : `Can't parse RegEx`, 31 | isRegEx: true, 32 | }, 33 | ]; 34 | } 35 | 36 | renderSuggestion(value: SearchModalData, el: HTMLElement): void { 37 | el.innerText = value.display; 38 | } 39 | 40 | onChooseSuggestion( 41 | item: SearchModalData, 42 | _: MouseEvent | KeyboardEvent 43 | ): void {} 44 | } 45 | -------------------------------------------------------------------------------- /src/modals/workspace_modal.ts: -------------------------------------------------------------------------------- 1 | import { FuzzySuggestModal, Notice } from "obsidian"; 2 | import AdvancedURI from "../main"; 3 | 4 | export class WorkspaceModal extends FuzzySuggestModal { 5 | plugin: AdvancedURI; 6 | constructor(plugin: AdvancedURI) { 7 | super(plugin.app); 8 | this.plugin = plugin; 9 | this.setPlaceholder("Choose a workspace"); 10 | } 11 | 12 | getItems(): string[] { 13 | const workspacesPlugin = 14 | this.app.internalPlugins.getEnabledPluginById("workspaces"); 15 | if (!workspacesPlugin) { 16 | new Notice("Workspaces plugin is not enabled"); 17 | } else { 18 | return Object.keys(workspacesPlugin.workspaces); 19 | } 20 | } 21 | 22 | getItemText(item: string): string { 23 | return item; 24 | } 25 | 26 | onChooseItem(item: string, _: MouseEvent | KeyboardEvent): void { 27 | this.plugin.tools.copyURI({ workspace: item }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from "obsidian"; 2 | import AdvancedURI from "./main"; 3 | 4 | export class SettingsTab extends PluginSettingTab { 5 | plugin: AdvancedURI; 6 | constructor(app: App, plugin: AdvancedURI) { 7 | super(app, plugin); 8 | this.plugin = plugin; 9 | } 10 | 11 | display(): void { 12 | let { containerEl } = this; 13 | containerEl.empty(); 14 | containerEl.createEl("h2", { text: this.plugin.manifest.name }); 15 | 16 | new Setting(containerEl).setName("Open file on write").addToggle((cb) => 17 | cb 18 | .setValue(this.plugin.settings.openFileOnWrite) 19 | .onChange((value) => { 20 | this.plugin.settings.openFileOnWrite = value; 21 | this.plugin.saveSettings(); 22 | }) 23 | ); 24 | 25 | new Setting(containerEl) 26 | .setName("Open file on write in a new pane") 27 | .setDisabled(this.plugin.settings.openFileOnWrite) 28 | .addToggle((cb) => 29 | cb 30 | .setValue(this.plugin.settings.openFileOnWriteInNewPane) 31 | .onChange((value) => { 32 | this.plugin.settings.openFileOnWriteInNewPane = value; 33 | this.plugin.saveSettings(); 34 | }) 35 | ); 36 | 37 | new Setting(containerEl) 38 | .setName("Open daily note in a new pane") 39 | .addToggle((cb) => 40 | cb 41 | .setValue(this.plugin.settings.openDailyInNewPane) 42 | .onChange((value) => { 43 | this.plugin.settings.openDailyInNewPane = value; 44 | this.plugin.saveSettings(); 45 | }) 46 | ); 47 | 48 | new Setting(containerEl) 49 | .setName("Open file without write in new pane") 50 | .addToggle((cb) => 51 | cb 52 | .setValue( 53 | this.plugin.settings.openFileWithoutWriteInNewPane 54 | ) 55 | .onChange((value) => { 56 | this.plugin.settings.openFileWithoutWriteInNewPane = 57 | value; 58 | this.plugin.saveSettings(); 59 | }) 60 | ); 61 | 62 | new Setting(containerEl) 63 | .setName("Use UID instead of file paths") 64 | .addToggle((cb) => 65 | cb.setValue(this.plugin.settings.useUID).onChange((value) => { 66 | this.plugin.settings.useUID = value; 67 | this.plugin.saveSettings(); 68 | this.display(); 69 | }) 70 | ); 71 | 72 | new Setting(containerEl) 73 | .setName("Include vault name/ID parameter") 74 | .addToggle((cb) => 75 | cb 76 | .setValue(this.plugin.settings.includeVaultName) 77 | .onChange((value) => { 78 | this.plugin.settings.includeVaultName = value; 79 | this.plugin.saveSettings(); 80 | this.display(); 81 | }) 82 | ); 83 | 84 | if (this.plugin.settings.includeVaultName) { 85 | new Setting(containerEl) 86 | .setName("Vault identifying parameter") 87 | .setDesc( 88 | "Choose whether to use the vault Name or its internal ID as the identifying parameter." 89 | ) 90 | .addDropdown((cb) => 91 | cb 92 | .addOption("name", "Name") 93 | .addOption("id", "ID") 94 | .setValue(this.plugin.settings.vaultParam) 95 | .onChange((value: "id" | "name") => { 96 | this.plugin.settings.vaultParam = value; 97 | this.plugin.saveSettings(); 98 | }) 99 | ); 100 | } 101 | 102 | if (this.plugin.settings.useUID) { 103 | new Setting(containerEl) 104 | .setName("Add filepath parameter") 105 | .setDesc( 106 | "When using UID instead of file paths, you can still add the filepath parameter to know what this URI is about. It's NOT actually used." 107 | ) 108 | .addToggle((cb) => 109 | cb 110 | .setValue(this.plugin.settings.addFilepathWhenUsingUID) 111 | .onChange((value) => { 112 | this.plugin.settings.addFilepathWhenUsingUID = 113 | value; 114 | this.plugin.saveSettings(); 115 | }) 116 | ); 117 | } 118 | new Setting(containerEl) 119 | .setName("UID field in frontmatter") 120 | .addText((cb) => 121 | cb.setValue(this.plugin.settings.idField).onChange((value) => { 122 | this.plugin.settings.idField = value; 123 | this.plugin.saveSettings(); 124 | }) 125 | ); 126 | 127 | new Setting(containerEl) 128 | .setName("Allow executing arbitrary code via eval") 129 | .setDesc( 130 | "⚠️ This can be dangerous as it allows executing arbitrary code. Only enable this if you trust the source of the URIs you are using and know what you are doing. ⚠️" 131 | ) 132 | .addToggle((cb) => 133 | cb 134 | .setValue(this.plugin.settings.allowEval) 135 | .onChange((value) => { 136 | this.plugin.settings.allowEval = value; 137 | this.plugin.saveSettings(); 138 | }) 139 | ); 140 | 141 | new Setting(containerEl) 142 | .setName("Donate") 143 | .setDesc( 144 | "If you like this Plugin, consider donating to support continued development." 145 | ) 146 | .addButton((bt) => { 147 | bt.buttonEl.outerHTML = 148 | "Buy Me a Coffee at ko-fi.com"; 149 | }); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/tools.ts: -------------------------------------------------------------------------------- 1 | import { CachedMetadata, Notice, parseFrontMatterEntry, TFile } from "obsidian"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | import AdvancedURI from "./main"; 4 | import { Parameters } from "./types"; 5 | import { copyText } from "./utils"; 6 | /** 7 | * These methods depend on the plugins settings in contrast to the utils.ts file, which's functions are independent of the plugins settings. 8 | */ 9 | export default class Tools { 10 | constructor(private readonly plugin: AdvancedURI) {} 11 | 12 | app = this.plugin.app; 13 | 14 | get settings() { 15 | return this.plugin.settings; 16 | } 17 | 18 | async writeUIDToFile(file: TFile, uid: string): Promise { 19 | const frontmatter = 20 | this.app.metadataCache.getFileCache(file)?.frontmatter; 21 | const fileContent: string = await this.app.vault.read(file); 22 | const isYamlEmpty: boolean = 23 | (!frontmatter || frontmatter.length === 0) && 24 | !fileContent.match(/^-{3}\s*\n*\r*-{3}/); 25 | let splitContent = fileContent.split("\n"); 26 | const key = `${this.plugin.settings.idField}:`; 27 | if (isYamlEmpty) { 28 | splitContent.unshift("---"); 29 | splitContent.unshift(`${key} ${uid}`); 30 | splitContent.unshift("---"); 31 | } else { 32 | const lineIndexOfKey = splitContent.findIndex((line) => 33 | line.startsWith(key) 34 | ); 35 | if (lineIndexOfKey != -1) { 36 | splitContent[lineIndexOfKey] = `${key} ${uid}`; 37 | } else { 38 | splitContent.splice(1, 0, `${key} ${uid}`); 39 | } 40 | } 41 | 42 | const newFileContent = splitContent.join("\n"); 43 | await this.app.vault.modify(file, newFileContent); 44 | return uid; 45 | } 46 | 47 | async getUIDFromFile(file: TFile): Promise { 48 | //await parsing of frontmatter 49 | const cache = 50 | this.app.metadataCache.getFileCache(file) ?? 51 | (await new Promise((resolve) => { 52 | const ref = this.app.metadataCache.on("changed", (metaFile) => { 53 | if (metaFile.path == file.path) { 54 | const cache = this.app.metadataCache.getFileCache(file); 55 | this.app.metadataCache.offref(ref); 56 | resolve(cache); 57 | } 58 | }); 59 | })); 60 | 61 | const uid = parseFrontMatterEntry( 62 | cache.frontmatter, 63 | this.plugin.settings.idField 64 | ); 65 | if (uid != undefined) { 66 | if (uid instanceof Array) { 67 | return uid[0]; 68 | } else { 69 | return uid; 70 | } 71 | } 72 | return await this.writeUIDToFile(file, uuidv4()); 73 | } 74 | 75 | async generateURI(parameters: Parameters) { 76 | const prefix = "obsidian://adv-uri"; 77 | let suffix = ""; 78 | const file = this.app.vault.getAbstractFileByPath(parameters.filepath); 79 | if (this.settings.includeVaultName) { 80 | suffix += "?vault="; 81 | if (this.settings.vaultParam == "id" && this.app.appId) { 82 | suffix += encodeURIComponent(this.app.appId); 83 | } else { 84 | suffix += encodeURIComponent(this.app.vault.getName()); 85 | } 86 | } 87 | if ( 88 | this.settings.useUID && 89 | file instanceof TFile && 90 | file.extension == "md" 91 | ) { 92 | if (!this.settings.addFilepathWhenUsingUID) 93 | parameters.filepath = undefined; 94 | parameters.uid = await this.getUIDFromFile(file); 95 | } 96 | const sortedParameterKeys = ( 97 | Object.keys(parameters) as (keyof Parameters)[] 98 | ) 99 | .filter((key) => parameters[key]) 100 | .sort((a, b) => { 101 | const first = ["filepath", "filename", "uid", "daily"]; 102 | const last = ["data", "eval"]; 103 | if (first.includes(a)) return -1; 104 | if (first.includes(b)) return 1; 105 | if (last.includes(a)) return 1; 106 | if (last.includes(b)) return -1; 107 | return 0; 108 | }); 109 | for (const parameter of sortedParameterKeys) { 110 | if (parameters[parameter] != undefined) { 111 | suffix += suffix ? "&" : "?"; 112 | suffix += `${parameter}=${encodeURIComponent( 113 | parameters[parameter] 114 | )}`; 115 | } 116 | } 117 | // When the URI gets decoded, the %20 at the end gets somehow removed. 118 | // Adding a trailing & to prevent this. 119 | if (suffix.endsWith("%20")) suffix += "&"; 120 | return prefix + suffix; 121 | } 122 | 123 | async copyURI(parameters: Parameters) { 124 | const uri = await this.generateURI(parameters); 125 | await copyText(uri); 126 | 127 | new Notice("Advanced URI copied to your clipboard"); 128 | } 129 | 130 | getFileFromUID(uid: string): TFile | undefined { 131 | const files = this.app.vault.getMarkdownFiles(); 132 | const idKey = this.settings.idField; 133 | for (const file of files) { 134 | const fieldValue = parseFrontMatterEntry( 135 | this.app.metadataCache.getFileCache(file)?.frontmatter, 136 | idKey 137 | ); 138 | 139 | if (fieldValue instanceof Array) { 140 | if (fieldValue.contains(uid)) return file; 141 | } else { 142 | if (fieldValue == uid) return file; 143 | } 144 | } 145 | } 146 | 147 | getFileFromBlockID(blockId: string): TFile | undefined { 148 | const files = this.app.vault.getMarkdownFiles(); 149 | 150 | blockId = blockId.toLowerCase(); 151 | for (const file of files) { 152 | const blockExists = 153 | this.app.metadataCache.getFileCache(file)?.blocks?.[blockId] != 154 | undefined; 155 | if (blockExists) return file; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { PaneType, View } from "obsidian"; 2 | import { CanvasNodeData } from "obsidian/canvas"; 3 | 4 | declare module "obsidian" { 5 | interface App { 6 | setting: { 7 | containerEl: HTMLElement; 8 | tabContentContainer: HTMLElement; 9 | openTabById(id: string): void; 10 | pluginTabs: Array<{ 11 | id: string; 12 | name: string; 13 | instance?: { 14 | description: string; 15 | id: string; 16 | name: string; 17 | }; 18 | }>; 19 | activeTab: SettingTab; 20 | open(): void; 21 | }; 22 | appId: string; 23 | commands: { 24 | executeCommandById(id: string): void; 25 | commands: { 26 | [key: string]: Command; 27 | }; 28 | }; 29 | plugins: { 30 | plugins: { 31 | [key: string]: { manifest: PluginManifest }; 32 | "obsidian-hover-editor": { 33 | spawnPopover( 34 | initiatingEl?: HTMLElement, 35 | onShowCallback?: () => unknown 36 | ): WorkspaceLeaf; 37 | manifest: PluginManifest; 38 | }; 39 | }; 40 | enablePluginAndSave(plugin: string): void; 41 | disablePluginAndSave(plugin: string): void; 42 | /** Returns the plugin instance if enabled and loaded */ 43 | getPlugin(plugin: string): Plugin | null; 44 | manifests: Record; 45 | checkForUpdates(): Promise; 46 | updates: { 47 | [key: string]: {}; 48 | }; 49 | }; 50 | internalPlugins: { 51 | getEnabledPluginById(plugin: String): Plugin; 52 | getEnabledPluginById(plugin: "bookmarks"): { 53 | openBookmark( 54 | bookmark: Bookmark, 55 | viewmode: PaneType | boolean 56 | ): void; 57 | getBookmarks(): Bookmark[]; 58 | } | null; 59 | getEnabledPluginById(plugin: "workspaces"): { 60 | activeWorkspace: string; 61 | workspaces: { [key: string]: any }; 62 | saveWorkspace(workspace: string): void; 63 | loadWorkspace(workspace: string): void; 64 | } | null; 65 | plugins: { 66 | [key: string]: { 67 | disable(_: boolean): void; 68 | enable(_: boolean): void; 69 | }; 70 | }; 71 | }; 72 | } 73 | interface Bookmark { 74 | path: string; 75 | title: string; 76 | type: string; 77 | } 78 | 79 | interface View { 80 | file?: TFile; 81 | } 82 | interface FileView { 83 | currentMode: { 84 | showSearch(): void; 85 | search: { 86 | searchInputEl: HTMLInputElement; 87 | }; 88 | }; 89 | } 90 | interface WorkspaceLeaf { 91 | width: number; 92 | } 93 | } 94 | 95 | export interface FileModalData { 96 | source: string; 97 | display: string; 98 | } 99 | 100 | export interface EnterData { 101 | mode: string; 102 | data: string; 103 | display: string; 104 | func: Function; 105 | } 106 | 107 | export interface AdvancedURISettings { 108 | openFileOnWrite: boolean; 109 | openFileOnWriteInNewPane: boolean; 110 | openDailyInNewPane: boolean; 111 | openFileWithoutWriteInNewPane: boolean; 112 | idField: string; 113 | useUID: boolean; 114 | addFilepathWhenUsingUID: boolean; 115 | allowEval: boolean; 116 | includeVaultName: boolean; 117 | vaultParam: "id" | "name"; 118 | } 119 | 120 | export interface Parameters { 121 | workspace?: string; 122 | filepath?: string; 123 | daily?: "true"; 124 | data?: string; 125 | /** 126 | * Separator used between previous data and new data when performing 127 | * an append or prepend command. 128 | * @default "\n" 129 | */ 130 | separator?: string; 131 | mode?: "overwrite" | "append" | "prepend" | "new"; 132 | heading?: string; 133 | block?: string; 134 | commandname?: string; 135 | commandid?: string; 136 | search?: string; 137 | searchregex?: string; 138 | replace?: string; 139 | uid?: string; 140 | filename?: string; 141 | exists?: string; 142 | viewmode?: "source" | "preview" | "live"; 143 | openmode?: OpenMode; 144 | settingid?: string; 145 | settingsection?: string; 146 | "x-success"?: string; 147 | "x-error"?: string; 148 | saveworkspace?: "true"; 149 | updateplugins?: "true"; 150 | line?: string; 151 | column?: string; 152 | /** 153 | * @deprecated Use "openMode" instead 154 | */ 155 | newpane?: "true" | "false"; 156 | clipboard?: string; 157 | "enable-plugin"?: string; 158 | "disable-plugin"?: string; 159 | frontmatterkey?: string; 160 | eval?: string; 161 | bookmark?: string; 162 | /** 163 | * A list of comma separated node ids 164 | */ 165 | canvasnodes?: string; 166 | /** 167 | * x,y,zoom split by `,` 168 | * To keep current value a `-` can be used 169 | * To alter a value by a number use `++` or `-` before the number 170 | * @example 171 | * 0,0,1 to reset to default 172 | * --50,++25,- to decrease x by 50, increase y by 25 and keep current zoom 173 | */ 174 | canvasviewport?: string; 175 | confirm?: string; 176 | offset?: string; 177 | } 178 | 179 | export type OpenMode = "silent" | "popover" | PaneType | "true" | "false"; 180 | 181 | export interface HookParameters { 182 | "x-success": string; 183 | "x-error": string; 184 | } 185 | 186 | export interface SearchModalData { 187 | source: string; 188 | display: string; 189 | isRegEx: boolean; 190 | } 191 | 192 | export interface CanvasView extends View { 193 | canvas: { 194 | selection: Set; 195 | zoomToSelection(): void; 196 | nodes: Map; 197 | select(node: CanvasNodeData): void; 198 | /* 199 | * Update `selection` in `func` 200 | */ 201 | updateSelection(func: () => void): void; 202 | tx: number; 203 | ty: number; 204 | tZoom: number; 205 | markViewportChanged(): void; 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { App, Notice, OpenViewState, TFile } from "obsidian"; 2 | import { stripMD } from "obsidian-community-lib"; 3 | import { Parameters } from "./types"; 4 | 5 | export function getViewStateFromMode( 6 | parameters: Parameters 7 | ): OpenViewState | undefined { 8 | return parameters.viewmode 9 | ? { 10 | state: { 11 | mode: parameters.viewmode, 12 | source: parameters.viewmode == "source", 13 | }, 14 | } 15 | : undefined; 16 | } 17 | 18 | export function copyText(text: string) { 19 | return navigator.clipboard.writeText(text); 20 | } 21 | 22 | export function getAlternativeFilePath(app: App, file: TFile): string { 23 | const dir = file.parent?.path; 24 | const formattedDir = dir === "/" ? "" : dir; 25 | const name = file.name; 26 | for (let index = 1; index < 100; index++) { 27 | const base = stripMD(name); 28 | const alternative = 29 | formattedDir + 30 | (formattedDir == "" ? "" : "/") + 31 | base + 32 | ` ${index}.md`; 33 | 34 | const exists = app.vault.getAbstractFileByPath(alternative) !== null; 35 | if (!exists) { 36 | return alternative; 37 | } 38 | } 39 | } 40 | 41 | export function getFileUri(app: App, file: TFile): string { 42 | const url = new URL(app.vault.getResourcePath(file)); 43 | url.host = "localhosthostlocal"; 44 | url.protocol = "file"; 45 | url.search = ""; 46 | 47 | url.pathname = decodeURIComponent(url.pathname); 48 | const res = url.toString().replace("/localhosthostlocal/", "/"); 49 | return res; 50 | } 51 | 52 | export function getEndAndBeginningOfHeading( 53 | app: App, 54 | file: TFile, 55 | heading: string 56 | ): { lastLine: number; firstLine: number } { 57 | const cache = app.metadataCache.getFileCache(file); 58 | const sections = cache.sections; 59 | const foundHeading = cache.headings?.find((e) => e.heading === heading); 60 | 61 | if (foundHeading) { 62 | const foundSectionIndex = sections.findIndex( 63 | (section) => 64 | section.type === "heading" && 65 | section.position.start.line === foundHeading.position.start.line 66 | ); 67 | const restSections = sections.slice(foundSectionIndex + 1); 68 | 69 | const nextHeadingIndex = restSections?.findIndex( 70 | (e) => e.type === "heading" 71 | ); 72 | 73 | const lastSection = 74 | restSections[ 75 | (nextHeadingIndex !== -1 76 | ? nextHeadingIndex 77 | : restSections.length) - 1 78 | ] ?? sections[foundSectionIndex]; 79 | const lastLine = lastSection.position.end.line + 1; 80 | 81 | return { 82 | lastLine: lastLine, 83 | firstLine: sections[foundSectionIndex].position.end.line + 1, 84 | }; 85 | } else { 86 | new Notice("Can't find heading"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es5", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015", 17 | "ESNext" 18 | ], 19 | "skipLibCheck": true, 20 | }, 21 | "include": [ 22 | "src/**/*.ts" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------