├── .github ├── dependabot.yml └── workflows │ └── releases.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── esbuild.mjs ├── manifest.json ├── package.json ├── resources ├── back-forward-settings.png ├── customizable-page-header.jpg ├── pane-relief.png └── settings.png ├── src ├── constants.ts ├── interfaces.ts ├── lucide.ts ├── main.ts ├── settings.ts ├── styles.scss ├── ui │ ├── commandSuggester.ts │ └── iconPicker.ts └── utils.ts ├── tsconfig.json └── versions.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | # From: https://github.com/argenos/nldates-obsidian/blob/master/.github/workflows/release.yml 2 | name: Build obsidian plugin 3 | 4 | on: 5 | push: 6 | # Sequence of patterns matched against refs/tags 7 | tags: 8 | - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10 9 | 10 | env: 11 | PLUGIN_NAME: customizable-page-header-buttons # Change this to the name of your plugin-id folder 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '16.13.0' # You might need to adjust this value to your own version 24 | - name: Build 25 | id: build 26 | run: | 27 | npm install 28 | npm run build --if-present 29 | mkdir ${{ env.PLUGIN_NAME }} 30 | cp build/main.js build/manifest.json build/styles.css ${{ env.PLUGIN_NAME }} 31 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 32 | ls 33 | npx rexreplace "^.*?#(#+\s\[.*?\n.*?)(?=\s*#+\s\[)" "_" -s -M -G -m -o "CHANGELOG.md" > CHANGELOG-LATEST.md 34 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 35 | - name: Create Release 36 | id: create_release 37 | uses: actions/create-release@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | VERSION: ${{ github.ref }} 41 | with: 42 | tag_name: ${{ github.ref }} 43 | release_name: ${{ github.ref }} 44 | body_path: CHANGELOG-LATEST.md 45 | draft: false 46 | prerelease: false 47 | - name: Upload zip file 48 | id: upload-zip 49 | uses: actions/upload-release-asset@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | upload_url: ${{ steps.create_release.outputs.upload_url }} 54 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 55 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 56 | asset_content_type: application/zip 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: build/main.js 65 | asset_name: main.js 66 | asset_content_type: text/javascript 67 | - name: Upload manifest.json 68 | id: upload-manifest 69 | uses: actions/upload-release-asset@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} 74 | asset_path: build/manifest.json 75 | asset_name: manifest.json 76 | asset_content_type: application/json 77 | - name: Upload styles.css 78 | id: upload-css 79 | uses: actions/upload-release-asset@v1 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 82 | with: 83 | upload_url: ${{ steps.create_release.outputs.upload_url }} 84 | asset_path: build/styles.css 85 | asset_name: styles.css 86 | asset_content_type: text/css 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | package-lock.json 4 | 5 | # build 6 | main.js 7 | *.js.map 8 | 9 | # obsidian 10 | data.json 11 | 12 | build/ 13 | 14 | *.zip 15 | .vscode/ 16 | 17 | .idea/ 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "useTabs": false 6 | } 7 | -------------------------------------------------------------------------------- /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 | ### [4.7.2](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.7.1...4.7.2) (2022-08-16) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * improve title visibility on mobile ([08407bd](https://github.com/kometenstaub/customizable-page-header-buttons/commit/08407bdf08b5e0f285d6660bc69e8f6238ef50dd)) 11 | 12 | ### [4.7.1](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.7.0...4.7.1) (2022-08-14) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * clickable-icon class ([e113ef4](https://github.com/kometenstaub/customizable-page-header-buttons/commit/e113ef434e51bc39b928b6eaf68182cf8e3ec25a)) 18 | 19 | ## [4.7.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.6.3...4.7.0) (2022-08-11) 20 | 21 | 22 | ### Features 23 | 24 | * add hide header option ([8022577](https://github.com/kometenstaub/customizable-page-header-buttons/commit/80225770cb3516703968927641e8797d8af986d3)) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * check for undefined ([ee0175f](https://github.com/kometenstaub/customizable-page-header-buttons/commit/ee0175f1aee721b438e2ee98c206faf6a9b6b150)) 30 | * more checks for titlebar ([73c549b](https://github.com/kometenstaub/customizable-page-header-buttons/commit/73c549baaf4f1a4cb67afb2da4347caaf7e90856)) 31 | 32 | ### [4.6.3](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.6.2...4.6.3) (2022-08-11) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * CSS ([3f69fcf](https://github.com/kometenstaub/customizable-page-header-buttons/commit/3f69fcfdc90a792f8d926544a7d3aa393738aaca)) 38 | * CSS for Obsi 0.16 ([cfe48b1](https://github.com/kometenstaub/customizable-page-header-buttons/commit/cfe48b14274633bd61ffb6e94ff5c6a70eaee962)) 39 | * CSS for titlebar buttons ([4fd43e2](https://github.com/kometenstaub/customizable-page-header-buttons/commit/4fd43e200528bde4f46c1eb6545be7f656393173)) 40 | * icon-btn class ([f4897b1](https://github.com/kometenstaub/customizable-page-header-buttons/commit/f4897b16fa59accfbe24b1e79d91f2b4d1b86809)) 41 | 42 | ### [4.6.2](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.6.1...4.6.2) (2022-07-15) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * better compatibility with Pane Relief history ([08b5b5d](https://github.com/kometenstaub/customizable-page-header-buttons/commit/08b5b5de211aa87a38f8d1009ea977db61103a58)) 48 | 49 | ### [4.6.1](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.6.0...4.6.1) (2022-07-13) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * CSS ([bc7b77f](https://github.com/kometenstaub/customizable-page-header-buttons/commit/bc7b77f84faf534e2142923802533f9ed72c1af5)) 55 | 56 | ## [4.6.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.5.2...4.6.0) (2022-07-05) 57 | 58 | 59 | ### Features 60 | 61 | * load titlebar buttons in pop-out windows... ([601a43d](https://github.com/kometenstaub/customizable-page-header-buttons/commit/601a43d5c34b7c3565dc558ae8855c783d9589bc)) 62 | * support left and right titlebar buttons ([ccb9f32](https://github.com/kometenstaub/customizable-page-header-buttons/commit/ccb9f3260432b842e266bae5a0f3f0c1e588b898)) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * exchangeCenterTitleBar ([ab98d70](https://github.com/kometenstaub/customizable-page-header-buttons/commit/ab98d70addcb1e4ecbcc8e6a627fa71812d28801)) 68 | * support 0.15.4 ([1f40baa](https://github.com/kometenstaub/customizable-page-header-buttons/commit/1f40baab96b488a0b6d03261ef1ddfa4360ec269)) 69 | * use insertBefore instead of prepend ([d952a6c](https://github.com/kometenstaub/customizable-page-header-buttons/commit/d952a6cbfc8fb0f2fd11593754b72b460b4c82ec)) 70 | 71 | ### [4.5.2](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.5.0...4.5.2) (2022-07-03) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * use better API ([c0910f9](https://github.com/kometenstaub/customizable-page-header-buttons/commit/c0910f91762894eec46ba35d9ee67987b36d17af)), closes [#34](https://github.com/kometenstaub/customizable-page-header-buttons/issues/34) [#33](https://github.com/kometenstaub/customizable-page-header-buttons/issues/33) 77 | 78 | ### [4.5.1](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.5.0...4.5.1) (2022-07-03) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * use better API ([83e20d7](https://github.com/kometenstaub/customizable-page-header-buttons/commit/83e20d78953db529ffb1524bc8faf5078d2abb7e)) 84 | 85 | ## [4.5.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.4.0...4.5.0) (2022-07-01) 86 | 87 | 88 | ### Features 89 | 90 | * Support refreshing on workspace load ([659ed5e](https://github.com/kometenstaub/customizable-page-header-buttons/commit/659ed5ec0058cd3408f4147a999bb123cb4ea69e)) 91 | 92 | ## [4.4.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.3.4...4.4.0) (2022-07-01) 93 | 94 | 95 | ### Features 96 | 97 | * Auto-refresh buttons on setting changes & startup ([b88e5b0](https://github.com/kometenstaub/customizable-page-header-buttons/commit/b88e5b0e14462d8653d05dc40f3dc3a86d242b55)) 98 | 99 | ### [4.3.4](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.3.3...4.3.4) (2022-06-26) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * CSS for pop-out windows ([877b25d](https://github.com/kometenstaub/customizable-page-header-buttons/commit/877b25d5fa581539a50895d97163503dd0b030dc)) 105 | 106 | ### [4.3.3](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.3.2...4.3.3) (2022-06-24) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * remove console log ([cd8722c](https://github.com/kometenstaub/customizable-page-header-buttons/commit/cd8722cbe1a1331adf0fd0cc907cc8781f042850)) 112 | 113 | ### [4.3.2](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.3.1...4.3.2) (2022-06-24) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * revert last change ([c5e7373](https://github.com/kometenstaub/customizable-page-header-buttons/commit/c5e73738db2d07a68e6a9d62090b26a3aee493ab)) 119 | 120 | ### [4.3.1](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.3.0...4.3.1) (2022-06-23) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * account for notes in sidebars, use better API ([07f6e7a](https://github.com/kometenstaub/customizable-page-header-buttons/commit/07f6e7a8ebac3dc6a12d3a9fb69fe9099c1907f6)) 126 | 127 | ## [4.3.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.2.0...4.3.0) (2022-06-14) 128 | 129 | 130 | ### Features 131 | 132 | * support 0.15.0 ([0effcb1](https://github.com/kometenstaub/customizable-page-header-buttons/commit/0effcb1ef096d09b3ec2a7fde4442294d3d99cc5)) 133 | 134 | ## [4.2.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.1.2...4.2.0) (2022-04-07) 135 | 136 | 137 | ### Features 138 | 139 | * Page Header Button spacing ([a4dd5ac](https://github.com/kometenstaub/customizable-page-header-buttons/commit/a4dd5acc6f4999c03aacf414dfaa19116a5b256f)), closes [#17](https://github.com/kometenstaub/customizable-page-header-buttons/issues/17) 140 | 141 | ### [4.1.2](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.1.1...4.1.2) (2022-03-31) 142 | 143 | 144 | ### Bug Fixes 145 | 146 | * timeout for all page header button commands ([590a544](https://github.com/kometenstaub/customizable-page-header-buttons/commit/590a544f140650b05ad49e2410d0f8cba3ce46d3)) 147 | 148 | ### [4.1.1](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.1.0...4.1.1) (2022-03-28) 149 | 150 | 151 | ### Bug Fixes 152 | 153 | * apply back/forward to correct pane ([85b9f50](https://github.com/kometenstaub/customizable-page-header-buttons/commit/85b9f509fd2e4d144f4d84bd064cb59a744cf45c)) 154 | 155 | ## [4.1.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/4.0.0...4.1.0) (2022-03-27) 156 | 157 | 158 | ### Features 159 | 160 | * settings option for pane relief count ([5c0ce1f](https://github.com/kometenstaub/customizable-page-header-buttons/commit/5c0ce1f7c800c77140cafe75a4f4424fda07cf6e)) 161 | 162 | ## [4.0.0](https://github.com/kometenstaub/customizable-page-header-buttons/compare/3.2.0...4.0.0) (2022-03-21) 163 | 164 | 165 | ### Features 166 | 167 | * add class for settings styling ([6f9ad94](https://github.com/kometenstaub/customizable-page-header-buttons/commit/6f9ad948923bdb1509401002f6548682eb6914b2)) 168 | * add constant ([2b031c9](https://github.com/kometenstaub/customizable-page-header-buttons/commit/2b031c9e2c9018aaf3a798abe420faa9966225a3)) 169 | * add methods for adding center buttons ([9128293](https://github.com/kometenstaub/customizable-page-header-buttons/commit/91282934135a144e066a345c460e5c1aa0a4c15b)) 170 | * add styling from titlebar-text ([9fca0a0](https://github.com/kometenstaub/customizable-page-header-buttons/commit/9fca0a04be852b63f4d1c6344c75c92c72d7e095)) 171 | * center title bar buttons ([f282e76](https://github.com/kometenstaub/customizable-page-header-buttons/commit/f282e76d2fced4112142b4db519f4af3ed296825)) 172 | * change feather to lucide icons ([2bc09d7](https://github.com/kometenstaub/customizable-page-header-buttons/commit/2bc09d7b24435fd9984cb1e476919e77fec10029)) 173 | * update default settings for center title bar ([9a742b2](https://github.com/kometenstaub/customizable-page-header-buttons/commit/9a742b2fce5be5a4cc1b5eec35d92f4a09ac21d9)) 174 | * update obsi icons ([8f72294](https://github.com/kometenstaub/customizable-page-header-buttons/commit/8f72294d960ab1cc062a7a073ab43bfe3a14a3c8)) 175 | * utility functions for center title bar ([86268a7](https://github.com/kometenstaub/customizable-page-header-buttons/commit/86268a73f0272df733f9a33132f452bdb587c639)) 176 | 177 | ### [3.1.2](https://github.com/kometenstaub/quick-switcher-button/compare/3.1.1...3.1.2) (2021-11-20) 178 | 179 | ### [3.1.1](https://github.com/kometenstaub/quick-switcher-button/compare/3.1.0...3.1.1) (2021-11-17) 180 | 181 | 182 | ### Bug Fixes 183 | 184 | * :bug: unload event listener onunload ([b25387f](https://github.com/kometenstaub/quick-switcher-button/commit/b25387fd47e136eae4b229432cdfcd78698d1933)) 185 | 186 | ## [3.1.0](https://github.com/kometenstaub/quick-switcher-button/compare/3.0.4...3.1.0) (2021-11-16) 187 | 188 | 189 | ### Features 190 | 191 | * :sparkles: buttons can be moved in settings; closes [#10](https://github.com/kometenstaub/quick-switcher-button/issues/10) ([1288cb5](https://github.com/kometenstaub/quick-switcher-button/commit/1288cb52054b0a9f7da0aaab94f8008c8d0a817f)) 192 | 193 | ### [3.0.4](https://github.com/kometenstaub/quick-switcher-button/compare/3.0.3...3.0.4) (2021-11-16) 194 | 195 | 196 | ### Bug Fixes 197 | 198 | * :bug: feather icons can now be chosen ([dcfdd33](https://github.com/kometenstaub/quick-switcher-button/commit/dcfdd33ee1a49649a3bb46a394b2857e33b8a08e)) 199 | 200 | ### [3.0.3](https://github.com/kometenstaub/quick-switcher-button/compare/3.0.2...3.0.3) (2021-11-15) 201 | 202 | ### [3.0.2](https://github.com/kometenstaub/quick-switcher-button/compare/3.0.1...3.0.2) (2021-11-15) 203 | 204 | 205 | ### Bug Fixes 206 | 207 | * :bug: adding command if it has no icon works again ([c579bab](https://github.com/kometenstaub/quick-switcher-button/commit/c579bab2c55a6a738a463b68d409e3344c3038ae)) 208 | 209 | ### [3.0.1](https://github.com/kometenstaub/quick-switcher-button/compare/3.0.0...3.0.1) (2021-11-15) 210 | 211 | 212 | ### Bug Fixes 213 | 214 | * :bug: make buttons mobile only when desktop is turned off again ([6ddd1c0](https://github.com/kometenstaub/quick-switcher-button/commit/6ddd1c05b6558958b45878b516afea581ed06055)) 215 | 216 | ## [3.0.0](https://github.com/kometenstaub/quick-switcher-button/compare/2.2.0...3.0.0) (2021-11-15) 217 | 218 | 219 | ### Features 220 | 221 | * :sparkles: configure action for both/mobile/desktop; closes [#9](https://github.com/kometenstaub/quick-switcher-button/issues/9) ([4adc89b](https://github.com/kometenstaub/quick-switcher-button/commit/4adc89b8dbce7ff3b86f580ef59a5710aa53422a)) 222 | * :sparkles: remove button if only other Platform is selected ([4146248](https://github.com/kometenstaub/quick-switcher-button/commit/41462488cc440eec60b9f1eaa1b06ed4fe40ab92)) 223 | 224 | ## [2.2.0](https://github.com/kometenstaub/quick-switcher-button/compare/2.1.1...2.2.0) (2021-11-14) 225 | 226 | 227 | ### Features 228 | 229 | * :sparkles: add tooltip ([dcedeef](https://github.com/kometenstaub/quick-switcher-button/commit/dcedeefa8abe298c459d9ef19e62699509829cad)) 230 | 231 | 232 | ### Bug Fixes 233 | 234 | * :lipstick: make tooltip look native; closes [#8](https://github.com/kometenstaub/quick-switcher-button/issues/8) ([4254412](https://github.com/kometenstaub/quick-switcher-button/commit/4254412339d1ed7a6b67481dc37595ac80b32001)) 235 | 236 | ### [2.1.1](https://github.com/kometenstaub/quick-switcher-button/compare/2.1.0...2.1.1) (2021-11-13) 237 | 238 | ## [2.1.0](https://github.com/kometenstaub/quick-switcher-button/compare/2.0.0...2.1.0) (2021-11-13) 239 | 240 | ## [2.0.0](https://github.com/kometenstaub/quick-switcher-button/compare/1.0.2...2.0.0) (2021-11-13) 241 | 242 | ### [1.0.2](https://github.com/kometenstaub/quick-switcher-button/compare/1.0.1...1.0.2) (2021-11-13) 243 | 244 | ### [1.0.1](https://github.com/kometenstaub/quick-switcher-button/compare/1.0.0...1.0.1) (2021-11-13) 245 | 246 | ## [1.0.0](https://github.com/kometenstaub/quick-switcher-button/compare/0.0.5...1.0.0) (2021-11-13) 247 | 248 | ### [0.0.5](https://github.com/kometenstaub/quick-switcher-button/compare/0.0.4...0.0.5) (2021-11-13) 249 | 250 | 251 | ### Performance 252 | 253 | * :zap: store ID and not WorkspaceLeaf; closes [#4](https://github.com/kometenstaub/quick-switcher-button/issues/4) ([97686d8](https://github.com/kometenstaub/quick-switcher-button/commit/97686d8b577fc95d03ac364fc1dfc1d94d2fe9a6)) 254 | 255 | ### [0.0.4](https://github.com/kometenstaub/quick-switcher-button/compare/0.0.3...0.0.4) (2021-11-12) 256 | 257 | ### [0.0.3](https://github.com/kometenstaub/quick-switcher-button/compare/0.0.2...0.0.3) (2021-11-12) 258 | 259 | ### [0.0.2](https://github.com/kometenstaub/quick-switcher-button/compare/0.0.1...0.0.2) (2021-11-12) 260 | 261 | ### 0.0.1 (2021-11-12) 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 kometenstaub and contributors 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 | # ~~Customizable Page Header and Title Bar Obsidian Plugin~~ Discontinued, please use the Commander plugin 2 | 3 | This plugin lets you add buttons that execute commands to page header in the mobile (and desktop, off by default) app. 4 | 5 | It lets you choose from all commands and configure their icons. You can set icons from the core and lucide icons. 6 | 7 | If you enable desktop compatibility, it will also let you configure buttons to be shown on both mobile and desktop, only desktop or only mobile. 8 | 9 | On desktop, you can also add buttons to the title bar. You can add them to the left or right side and even exchange the center version text with buttons. 10 | 11 | If you use **Pane Relief**, you can toggle whether to show the forward/backwards history count when you're adding back/forward buttons. 12 | 13 | ![pane-relief-history-count](https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/main/resources/pane-relief.png) 14 | 15 | ![back-forward-settings](https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/main/resources/back-forward-settings.png) 16 | 17 | Thank you to @pjeby for supporting this in Pane Relief. 18 | 19 | ## Known limitations 20 | 21 | This plugin uses an internal API for showing the commands. That means for example that if some commands only work in edit mode, the last active pane before entering settings has to be in edit mode, otherwise you will not be able to select that command. 22 | 23 | 24 | ## Example configuration 25 | 26 | ![Example configuration](https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/main/resources/customizable-page-header.jpg) 27 | 28 | ## Example settings 29 | 30 | ![Settings picture](https://raw.githubusercontent.com/kometenstaub/top-bar-buttons/main/resources/settings.png) 31 | 32 | ## Credits 33 | 34 | Shoutout to [@phibr0](https://github.com/phibr0) for his awesome work on the [Customizable Sidebar Plugin](https://github.com/phibr0/obsidian-customizable-sidebar). The code that powers this plugins' settings (adding/removing commands/changing icons) is powered by an adapted version of his code. 35 | 36 | Thanks to @pjeby the buttons auto-refresh on setting change and at startup. It will also tell Pane Relief when buttons changed so that title tooltips for the back/forward history count can, if needed, be updated and all the leaves will be populated when the workspace is changed (using monkey-around, which ISC-licensed). 37 | 38 | -------------------------------------------------------------------------------- /esbuild.mjs: -------------------------------------------------------------------------------- 1 | // Thank you: https://github.com/aidenlx/media-extended/blob/main/esbuild.js 2 | import esbuild from 'esbuild'; 3 | import fs from 'fs'; 4 | import minify from 'css-minify'; 5 | import sass from 'sass' 6 | import builtins from 'builtin-modules' 7 | 8 | const license = ` 9 | MIT License 10 | 11 | Copyright (c) 2021-2022 kometenstaub and contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | ` 31 | 32 | const banner = `/* 33 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 34 | If you want to view the source, visit the plugin's github repository: 35 | https://github.com/kometenstaub/customizable-page-header-buttons 36 | 37 | This plugin is MIT-licensed: 38 | ${license} 39 | 40 | The commandSuggester, iconPicker, Obsidian icon names and the biggest part of the settings tab have been adapted from the Obsidian Customizable Sidebar Plugin (https://github.com/phibr0/obsidian-customizable-sidebar). 41 | It is MIT-licensed: 42 | MIT License 43 | 44 | Copyright (c) 2021 Phillip 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining a copy 47 | of this software and associated documentation files (the "Software"), to deal 48 | in the Software without restriction, including without limitation the rights 49 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 50 | copies of the Software, and to permit persons to whom the Software is 51 | furnished to do so, subject to the following conditions: 52 | 53 | The above copyright notice and this permission notice shall be included in all 54 | copies or substantial portions of the Software. 55 | 56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 57 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 58 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 59 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 60 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 61 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 62 | SOFTWARE. 63 | 64 | The workspace monkey-patching uses code from monkey-around, which is ISC-licensed. 65 | https://github.com/pjeby/monkey-around 66 | 67 | */ 68 | `; 69 | 70 | const copyManifest = { 71 | name: 'copy-manifest-and-styles', 72 | setup: (build) => { 73 | build.onEnd(() => { 74 | fs.copyFileSync('manifest.json', 'build/manifest.json'); 75 | }); 76 | }, 77 | }; 78 | 79 | const styleSettings = ` 80 | /* @settings 81 | 82 | name: Customizable Page Header and Title Bar 83 | id: customizable-page-header-buttons 84 | settings: 85 | - 86 | id: page-header-spacing-mobile 87 | title: Page Header Button Spacing (mobile) 88 | type: variable-number-slider 89 | default: 12 90 | min: 0 91 | max: 30 92 | step: 1 93 | format: px 94 | - 95 | id: page-header-spacing-desktop 96 | title: Page Header Button Spacing (desktop) 97 | type: variable-number-slider 98 | default: 8 99 | min: 0 100 | max: 30 101 | step: 1 102 | format: px 103 | - 104 | id: titlebar-button-horizontal-spacing 105 | title: Horizontal spacing of titlebar buttons 106 | type: variable-number-slider 107 | default: 16 108 | min: 4 109 | max: 16 110 | step: 1 111 | format: px 112 | - 113 | id: display-view-header-title 114 | title: Hide the view header title 115 | description: Hiding it makes sense because of the embedded title. It gives more space for buttons, which is useful when many tab containers are open. 116 | type: variable-select 117 | default: initial 118 | options: 119 | - 120 | label: Show title 121 | value: initial 122 | - 123 | label: Hide title 124 | value: none 125 | 126 | */ 127 | ` 128 | 129 | const copyMinifiedCSS = { 130 | name: 'minify-css', 131 | setup: (build) => { 132 | build.onEnd(async () => { 133 | const {css} = sass.compile('src/styles.scss'); 134 | const minCss = await minify(css); 135 | const content = `/*${license}*/\n${styleSettings}\n${minCss}`; 136 | fs.writeFileSync('build/styles.css', content, {encoding: 'utf-8'}); 137 | }) 138 | } 139 | } 140 | 141 | 142 | const isProd = process.env.BUILD === 'production'; 143 | 144 | (async () => { 145 | try { 146 | await esbuild.build({ 147 | entryPoints: ['src/main.ts', 'src/styles.scss'], 148 | bundle: true, 149 | watch: !isProd, 150 | external: ['obsidian', 'electron', ...builtins], 151 | format: 'cjs', 152 | target: 'es2018', 153 | banner: { js: banner }, 154 | sourcemap: isProd ? false : 'inline', 155 | minify: isProd, 156 | treeShaking: true, 157 | define: { 158 | 'process.env.NODE_ENV': JSON.stringify(process.env.BUILD), 159 | }, 160 | outdir: 'build/', 161 | logLevel: 'info', 162 | plugins: [copyManifest, copyMinifiedCSS], 163 | loader: { '.scss': 'text' } 164 | }); 165 | } catch (err) { 166 | console.error(err); 167 | process.exit(1); 168 | } 169 | })(); 170 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "customizable-page-header-buttons", 3 | "name": "Customizable Page Header and Title Bar", 4 | "version": "4.7.2", 5 | "minAppVersion": "0.16.0", 6 | "description": "This plugin lets you add buttons for executing commands to the page header and on desktop to the title bar.", 7 | "author": "kometenstaub", 8 | "authorUrl": "https://github.com/kometenstaub", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customizable-page-header-buttons", 3 | "version": "4.7.2", 4 | "description": "This plugin lets you add buttons for executing commands to the page header.", 5 | "main": "src/main.ts", 6 | "scripts": { 7 | "dev": "cross-env BUILD=dev node esbuild.mjs", 8 | "build": "cross-env BUILD=production node esbuild.mjs", 9 | "release": "standard-version", 10 | "format": "npx prettier --write src/", 11 | "css": "npx sass --watch src/styles.scss build/styles.css" 12 | }, 13 | "standard-version": { 14 | "t": "", 15 | "types": [ 16 | { 17 | "type": "perf", 18 | "hidden": false, 19 | "section": "Performance" 20 | }, 21 | { 22 | "type": "feat", 23 | "hidden": false, 24 | "section": "Features" 25 | }, 26 | { 27 | "type": "fix", 28 | "hidden": false, 29 | "section": "Bug Fixes" 30 | } 31 | ], 32 | "releaseCommitMessageFormat": "Customizable Page Header and Title Bar: {{currentTag}}" 33 | }, 34 | "keywords": [], 35 | "author": "kometenstaub", 36 | "license": "MIT", 37 | "devDependencies": { 38 | "@types/node": "^14.14.37", 39 | "builtin-modules": "^3.2.0", 40 | "cross-env": "^7.0.3", 41 | "css-minify": "^2.0.0", 42 | "esbuild": "0.14.3", 43 | "monkey-around": "^2.3.0", 44 | "obsidian": "^0.15.3", 45 | "prettier": "2.5.1", 46 | "sass": "^1.49.9", 47 | "standard-version": "^9.3.2", 48 | "tslib": "^2.2.0", 49 | "typescript": "^4.2.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/back-forward-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/3551bfd86dae842f7a1288839bcd5a74d24344b7/resources/back-forward-settings.png -------------------------------------------------------------------------------- /resources/customizable-page-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/3551bfd86dae842f7a1288839bcd5a74d24344b7/resources/customizable-page-header.jpg -------------------------------------------------------------------------------- /resources/pane-relief.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/3551bfd86dae842f7a1288839bcd5a74d24344b7/resources/pane-relief.png -------------------------------------------------------------------------------- /resources/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kometenstaub/customizable-page-header-buttons/3551bfd86dae842f7a1288839bcd5a74d24344b7/resources/settings.png -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/phibr0/obsidian-customizable-sidebar/blob/master/src/main.ts 2 | export const obsiIcons = [ 3 | 'add-note-glyph', 4 | 'any-key', 5 | 'audio-file', 6 | 'blocks', 7 | 'bold-glyph', 8 | 'box-glyph', 9 | 'bracket-glyph', 10 | 'broken-link', 11 | 'bullet-list-glyph', 12 | 'bullet-list', 13 | 'calendar-glyph', 14 | 'calendar-with-checkmark', 15 | 'check-in-circle', 16 | 'check-small', 17 | 'checkbox-glyph', 18 | 'checkmark', 19 | 'clock-glyph', 20 | 'clock', 21 | 'cloud', 22 | 'code-glyph', 23 | 'command-glyph', 24 | 'compress-glyph', 25 | 'create-new', 26 | 'cross-in-box', 27 | 'cross', 28 | 'crossed-star', 29 | 'dice-glyph', 30 | 'dice', 31 | 'document', 32 | 'documents', 33 | 'dot-network', 34 | 'double-down-arrow-glyph', 35 | 'double-up-arrow-glyph', 36 | 'down-arrow-with-tail', 37 | 'down-chevron-glyph', 38 | 'down-curly-arrow-glyph', 39 | 'duplicate-glyph', 40 | 'enlarge-glyph', 41 | 'enter', 42 | 'exit-fullscreen', 43 | 'expand-vertically', 44 | 'file-explorer-glyph', 45 | 'filled-pin', 46 | 'folder', 47 | 'forward-arrow', 48 | 'fullscreen', 49 | 'gear', 50 | 'github-glyph', 51 | 'go-to-file', 52 | 'graph-glyph', 53 | 'hashtag', 54 | 'heading-glyph', 55 | 'help', 56 | 'highlight-glyph', 57 | 'horizontal-split', 58 | 'image-file', 59 | 'image-glyph', 60 | 'import-glyph', 61 | 'indent-glyph', 62 | 'info', 63 | 'install', 64 | 'italic-glyph', 65 | 'keyboard-glyph', 66 | 'languages', 67 | 'left-arrow-with-tail', 68 | 'left-arrow', 69 | 'left-chevron-glyph', 70 | 'lines-of-text', 71 | 'link-glyph', 72 | 'link', 73 | 'links-coming-in', 74 | 'links-going-out', 75 | 'logo-crystal', 76 | 'magnifying-glass', 77 | 'merge-files-glyph', 78 | 'merge-files', 79 | 'microphone-filled', 80 | 'microphone', 81 | 'minus-with-circle', 82 | 'navigate-glyph', 83 | 'note-glyph', 84 | 'number-list-glyph', 85 | 'open-elsewhere-glyph', 86 | 'open-vault', 87 | 'pane-layout', 88 | 'paper-plane-glyph', 89 | 'paper-plane', 90 | 'paste-text', 91 | 'paste', 92 | 'paused', 93 | 'pdf-file', 94 | 'pencil', 95 | 'percent-sign-glyph', 96 | 'pin', 97 | 'play-audio-glyph', 98 | 'plus-minus-glyph', 99 | 'plus-with-circle', 100 | 'popup-open', 101 | 'presentation-glyph', 102 | 'presentation', 103 | 'price-tag-glyph', 104 | 'question-mark-glyph', 105 | 'quote-glyph', 106 | 'reading-glasses', 107 | 'redo-glyph', 108 | 'reset', 109 | 'restore-file-glyph', 110 | 'right-arrow-with-tail', 111 | 'right-arrow', 112 | 'right-chevron-glyph', 113 | 'right-triangle', 114 | 'run-command', 115 | 'scissors-glyph', 116 | 'scissors', 117 | 'search-glyph', 118 | 'search', 119 | 'select-all-text', 120 | 'sheets-in-box', 121 | 'split', 122 | 'stacked-levels', 123 | 'star-glyph', 124 | 'star-list', 125 | 'star', 126 | 'stop-audio-glyph', 127 | 'strikethrough-glyph', 128 | 'switch', 129 | 'sync-small', 130 | 'sync', 131 | 'tag-glyph', 132 | 'three-horizontal-bars', 133 | 'tomorrow-glyph', 134 | 'trash', 135 | 'two-blank-pages', 136 | 'undo-glyph', 137 | 'unindent-glyph', 138 | 'up-and-down-arrows', 139 | 'up-arrow-with-tail', 140 | 'up-chevron-glyph', 141 | 'up-curly-arrow-glyph', 142 | 'uppercase-lowercase-a', 143 | 'vault', 144 | 'vertical-split', 145 | 'vertical-three-dots', 146 | 'wand-glyph', 147 | 'wand', 148 | 'workspace-glyph', 149 | 'wrench-screwdriver-glyph', 150 | 'yesterday-glyph', 151 | ]; 152 | 153 | export const PLUGIN_CLASS_NAME = 'page-header-button'; 154 | export const TITLEBAR_CLASS = 'titlebar-button'; 155 | export const TITLEBAR_CENTER = 'titlebar-center'; 156 | export const TITLEBAR_CLASSES = [TITLEBAR_CLASS, PLUGIN_CLASS_NAME]; 157 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'obsidian'; 2 | 3 | export interface TopBarButtonsSettings { 4 | enabledButtons: enabledButton[]; 5 | desktop: boolean; 6 | titleLeft: baseButton[]; 7 | titleRight: baseButton[]; 8 | titleCenter: baseButton[]; 9 | paneRelief: boolean; 10 | } 11 | 12 | export type Buttons = 'both' | 'mobile' | 'desktop'; 13 | 14 | export interface baseButton { 15 | id: string; 16 | icon: string; 17 | name: string; 18 | } 19 | 20 | export interface enabledButton extends baseButton { 21 | showButtons: Buttons; 22 | } 23 | 24 | declare module 'obsidian' { 25 | interface App { 26 | commands: { 27 | executeCommandById: any; 28 | // listCommands: () => { 29 | // id: string; 30 | // name: string; 31 | // hotkeys: [key: string, modifiers: string[]]; 32 | // }[]; 33 | listCommands: () => Command[]; 34 | }; 35 | plugins: { 36 | plugins: Record; 37 | }; 38 | setting: { 39 | openTabById(id: string): void; 40 | pluginTabs: Array<{ 41 | id: string; 42 | name: string; 43 | plugin: { [key: string]: manifest }; 44 | instance?: { 45 | description: string; 46 | id: string; 47 | name: string; 48 | }; 49 | }>; 50 | activeTab: any; 51 | open(): void; 52 | }; 53 | } 54 | } 55 | 56 | export type TitleOrPage = 57 | | 'title-left' 58 | | 'title-right' 59 | | 'title-center' 60 | | 'page'; 61 | 62 | export type TitleSettings = 'titleLeft' | 'titleRight' | 'titleCenter'; 63 | 64 | export type ButtonSettings = TitleSettings | 'enabledSettings'; 65 | 66 | interface manifest { 67 | author: string; 68 | authorUrl: string; 69 | description: string; 70 | dir: string; 71 | id: string; 72 | isDesktopOnly: boolean; 73 | minAppVersion: string; 74 | name: string; 75 | version: string; 76 | } 77 | -------------------------------------------------------------------------------- /src/lucide.ts: -------------------------------------------------------------------------------- 1 | export const lucideIcons = [ 2 | 'lucide-activity', 3 | 'lucide-airplay', 4 | 'lucide-alarm-check', 5 | 'lucide-alarm-clock-off', 6 | 'lucide-alarm-clock', 7 | 'lucide-alarm-minus', 8 | 'lucide-alarm-plus', 9 | 'lucide-album', 10 | 'lucide-alert-circle', 11 | 'lucide-alert-octagon', 12 | 'lucide-alert-triangle', 13 | 'lucide-align-center-horizontal', 14 | 'lucide-align-center', 15 | 'lucide-align-center-vertical', 16 | 'lucide-align-end-horizontal', 17 | 'lucide-align-end-vertical', 18 | 'lucide-align-horizontal-distribute-center', 19 | 'lucide-align-horizontal-distribute-end', 20 | 'lucide-align-horizontal-distribute-start', 21 | 'lucide-align-horizontal-justify-center', 22 | 'lucide-align-horizontal-justify-end', 23 | 'lucide-align-horizontal-justify-start', 24 | 'lucide-align-horizontal-space-around', 25 | 'lucide-align-horizontal-space-between', 26 | 'lucide-align-justify', 27 | 'lucide-align-left', 28 | 'lucide-align-right', 29 | 'lucide-align-start-horizontal', 30 | 'lucide-align-start-vertical', 31 | 'lucide-align-vertical-distribute-center', 32 | 'lucide-align-vertical-distribute-end', 33 | 'lucide-align-vertical-distribute-start', 34 | 'lucide-align-vertical-justify-center', 35 | 'lucide-align-vertical-justify-end', 36 | 'lucide-align-vertical-justify-start', 37 | 'lucide-align-vertical-space-around', 38 | 'lucide-align-vertical-space-between', 39 | 'lucide-anchor', 40 | 'lucide-aperture', 41 | 'lucide-archive', 42 | 'lucide-arrow-big-down', 43 | 'lucide-arrow-big-left', 44 | 'lucide-arrow-big-right', 45 | 'lucide-arrow-big-up', 46 | 'lucide-arrow-down-circle', 47 | 'lucide-arrow-down-left', 48 | 'lucide-arrow-down-right', 49 | 'lucide-arrow-down', 50 | 'lucide-arrow-left-circle', 51 | 'lucide-arrow-left-right', 52 | 'lucide-arrow-left', 53 | 'lucide-arrow-right-circle', 54 | 'lucide-arrow-right', 55 | 'lucide-arrow-up-circle', 56 | 'lucide-arrow-up-left', 57 | 'lucide-arrow-up-right', 58 | 'lucide-arrow-up', 59 | 'lucide-asterisk', 60 | 'lucide-at-sign', 61 | 'lucide-award', 62 | 'lucide-axe', 63 | 'lucide-banknote', 64 | 'lucide-bar-chart-2', 65 | 'lucide-bar-chart', 66 | 'lucide-baseline', 67 | 'lucide-battery-charging', 68 | 'lucide-battery-full', 69 | 'lucide-battery-low', 70 | 'lucide-battery-medium', 71 | 'lucide-battery', 72 | 'lucide-beaker', 73 | 'lucide-bell-minus', 74 | 'lucide-bell-off', 75 | 'lucide-bell-plus', 76 | 'lucide-bell-ring', 77 | 'lucide-bell', 78 | 'lucide-bike', 79 | 'lucide-binary', 80 | 'lucide-bitcoin', 81 | 'lucide-bluetooth-connected', 82 | 'lucide-bluetooth-off', 83 | 'lucide-bluetooth-searching', 84 | 'lucide-bluetooth', 85 | 'lucide-bold', 86 | 'lucide-bookmark-minus', 87 | 'lucide-bookmark-plus', 88 | 'lucide-bookmark', 89 | 'lucide-book-open', 90 | 'lucide-book', 91 | 'lucide-bot', 92 | 'lucide-box-select', 93 | 'lucide-box', 94 | 'lucide-briefcase', 95 | 'lucide-brush', 96 | 'lucide-bug', 97 | 'lucide-building', 98 | 'lucide-bus', 99 | 'lucide-calculator', 100 | 'lucide-calendar', 101 | 'lucide-camera-off', 102 | 'lucide-camera', 103 | 'lucide-carrot', 104 | 'lucide-car', 105 | 'lucide-cast', 106 | 'lucide-check-circle-2', 107 | 'lucide-check-circle', 108 | 'lucide-check-square', 109 | 'lucide-check', 110 | 'lucide-chevron-down', 111 | 'lucide-chevron-first', 112 | 'lucide-chevron-last', 113 | 'lucide-chevron-left', 114 | 'lucide-chevron-right', 115 | 'lucide-chevrons-down', 116 | 'lucide-chevrons-down-up', 117 | 'lucide-chevrons-left', 118 | 'lucide-chevrons-right', 119 | 'lucide-chevrons-up-down', 120 | 'lucide-chevrons-up', 121 | 'lucide-chevron-up', 122 | 'lucide-chrome', 123 | 'lucide-circle-slashed', 124 | 'lucide-circle', 125 | 'lucide-clipboard-check', 126 | 'lucide-clipboard-copy', 127 | 'lucide-clipboard-list', 128 | 'lucide-clipboard', 129 | 'lucide-clipboard-x', 130 | 'lucide-clock-1', 131 | 'lucide-clock-2', 132 | 'lucide-clock-3', 133 | 'lucide-clock-4', 134 | 'lucide-clock-5', 135 | 'lucide-clock-6', 136 | 'lucide-clock-7', 137 | 'lucide-clock-8', 138 | 'lucide-clock-9', 139 | 'lucide-clock-10', 140 | 'lucide-clock-11', 141 | 'lucide-clock-12', 142 | 'lucide-clock', 143 | 'lucide-cloud-drizzle', 144 | 'lucide-cloud-fog', 145 | 'lucide-cloud-hail', 146 | 'lucide-cloud-lightning', 147 | 'lucide-cloud-moon', 148 | 'lucide-cloud-off', 149 | 'lucide-cloud-rain', 150 | 'lucide-cloud-rain-wind', 151 | 'lucide-cloud-snow', 152 | 'lucide-cloud-sun', 153 | 'lucide-cloud', 154 | 'lucide-cloudy', 155 | 'lucide-clover', 156 | 'lucide-code-2', 157 | 'lucide-codepen', 158 | 'lucide-codesandbox', 159 | 'lucide-code', 160 | 'lucide-coffee', 161 | 'lucide-coins', 162 | 'lucide-columns', 163 | 'lucide-command', 164 | 'lucide-compass', 165 | 'lucide-contact', 166 | 'lucide-contrast', 167 | 'lucide-cookie', 168 | 'lucide-copyleft', 169 | 'lucide-copyright', 170 | 'lucide-copy', 171 | 'lucide-corner-down-left', 172 | 'lucide-corner-down-right', 173 | 'lucide-corner-left-down', 174 | 'lucide-corner-left-up', 175 | 'lucide-corner-right-down', 176 | 'lucide-corner-right-up', 177 | 'lucide-corner-up-left', 178 | 'lucide-corner-up-right', 179 | 'lucide-cpu', 180 | 'lucide-credit-card', 181 | 'lucide-crop', 182 | 'lucide-crosshair', 183 | 'lucide-cross', 184 | 'lucide-crown', 185 | 'lucide-currency', 186 | 'lucide-database', 187 | 'lucide-delete', 188 | 'lucide-disc', 189 | 'lucide-divide-circle', 190 | 'lucide-divide-square', 191 | 'lucide-divide', 192 | 'lucide-dollar-sign', 193 | 'lucide-download-cloud', 194 | 'lucide-download', 195 | 'lucide-dribbble', 196 | 'lucide-droplets', 197 | 'lucide-droplet', 198 | 'lucide-drumstick', 199 | 'lucide-edit-2', 200 | 'lucide-edit-3', 201 | 'lucide-edit', 202 | 'lucide-egg', 203 | 'lucide-equal-not', 204 | 'lucide-equal', 205 | 'lucide-euro', 206 | 'lucide-expand', 207 | 'lucide-external-link', 208 | 'lucide-eye-off', 209 | 'lucide-eye', 210 | 'lucide-facebook', 211 | 'lucide-fast-forward', 212 | 'lucide-feather', 213 | 'lucide-figma', 214 | 'lucide-file-check-2', 215 | 'lucide-file-check', 216 | 'lucide-file-code', 217 | 'lucide-file-digit', 218 | 'lucide-file-input', 219 | 'lucide-file-minus-2', 220 | 'lucide-file-minus', 221 | 'lucide-file-output', 222 | 'lucide-file-plus-2', 223 | 'lucide-file-plus', 224 | 'lucide-file-search', 225 | 'lucide-files', 226 | 'lucide-file', 227 | 'lucide-file-text', 228 | 'lucide-file-x-2', 229 | 'lucide-file-x', 230 | 'lucide-film', 231 | 'lucide-filter', 232 | 'lucide-flag', 233 | 'lucide-flag-triangle-left', 234 | 'lucide-flag-triangle-right', 235 | 'lucide-flame', 236 | 'lucide-flashlight-off', 237 | 'lucide-flashlight', 238 | 'lucide-flask-conical', 239 | 'lucide-flask-round', 240 | 'lucide-folder-minus', 241 | 'lucide-folder-open', 242 | 'lucide-folder-plus', 243 | 'lucide-folder', 244 | 'lucide-form-input', 245 | 'lucide-forward', 246 | 'lucide-framer', 247 | 'lucide-frown', 248 | 'lucide-function-square', 249 | 'lucide-gamepad-2', 250 | 'lucide-gamepad', 251 | 'lucide-gauge', 252 | 'lucide-gavel', 253 | 'lucide-gem', 254 | 'lucide-ghost', 255 | 'lucide-gift', 256 | 'lucide-git-branch-plus', 257 | 'lucide-git-branch', 258 | 'lucide-git-commit', 259 | 'lucide-github', 260 | 'lucide-gitlab', 261 | 'lucide-git-merge', 262 | 'lucide-git-pull-request', 263 | 'lucide-glasses', 264 | 'lucide-globe-2', 265 | 'lucide-globe', 266 | 'lucide-grab', 267 | 'lucide-graduation-cap', 268 | 'lucide-grid', 269 | 'lucide-grip-horizontal', 270 | 'lucide-grip-vertical', 271 | 'lucide-hammer', 272 | 'lucide-hand-metal', 273 | 'lucide-hand', 274 | 'lucide-hard-drive', 275 | 'lucide-hard-hat', 276 | 'lucide-hash', 277 | 'lucide-haze', 278 | 'lucide-headphones', 279 | 'lucide-heart', 280 | 'lucide-help-circle', 281 | 'lucide-hexagon', 282 | 'lucide-highlighter', 283 | 'lucide-history', 284 | 'lucide-home', 285 | 'lucide-image-minus', 286 | 'lucide-image-off', 287 | 'lucide-image-plus', 288 | 'lucide-image', 289 | 'lucide-import', 290 | 'lucide-inbox', 291 | 'lucide-indent', 292 | 'lucide-indian-rupee', 293 | 'lucide-infinity', 294 | 'lucide-info', 295 | 'lucide-inspect', 296 | 'lucide-instagram', 297 | 'lucide-italic', 298 | 'lucide-japanese-yen', 299 | 'lucide-keyboard', 300 | 'lucide-key', 301 | 'lucide-landmark', 302 | 'lucide-languages', 303 | 'lucide-laptop-2', 304 | 'lucide-laptop', 305 | 'lucide-lasso-select', 306 | 'lucide-lasso', 307 | 'lucide-layers', 308 | 'lucide-layout-dashboard', 309 | 'lucide-layout-grid', 310 | 'lucide-layout-list', 311 | 'lucide-layout', 312 | 'lucide-layout-template', 313 | 'lucide-library', 314 | 'lucide-life-buoy', 315 | 'lucide-lightbulb-off', 316 | 'lucide-lightbulb', 317 | 'lucide-link-2-off', 318 | 'lucide-link-2', 319 | 'lucide-linkedin', 320 | 'lucide-link', 321 | 'lucide-list-checks', 322 | 'lucide-list-minus', 323 | 'lucide-list-ordered', 324 | 'lucide-list-plus', 325 | 'lucide-list', 326 | 'lucide-list-x', 327 | 'lucide-loader-2', 328 | 'lucide-loader', 329 | 'lucide-locate-fixed', 330 | 'lucide-locate-off', 331 | 'lucide-locate', 332 | 'lucide-lock', 333 | 'lucide-log-in', 334 | 'lucide-log-out', 335 | 'lucide-mail', 336 | 'lucide-map-pin', 337 | 'lucide-map', 338 | 'lucide-maximize-2', 339 | 'lucide-maximize', 340 | 'lucide-megaphone', 341 | 'lucide-meh', 342 | 'lucide-menu', 343 | 'lucide-message-circle', 344 | 'lucide-message-square', 345 | 'lucide-mic-off', 346 | 'lucide-mic', 347 | 'lucide-minimize-2', 348 | 'lucide-minimize', 349 | 'lucide-minus-circle', 350 | 'lucide-minus-square', 351 | 'lucide-minus', 352 | 'lucide-monitor-off', 353 | 'lucide-monitor-speaker', 354 | 'lucide-monitor', 355 | 'lucide-moon', 356 | 'lucide-more-horizontal', 357 | 'lucide-more-vertical', 358 | 'lucide-mountain-snow', 359 | 'lucide-mountain', 360 | 'lucide-mouse-pointer-2', 361 | 'lucide-mouse-pointer-click', 362 | 'lucide-mouse-pointer', 363 | 'lucide-move-diagonal-2', 364 | 'lucide-move-diagonal', 365 | 'lucide-move-horizontal', 366 | 'lucide-move', 367 | 'lucide-move-vertical', 368 | 'lucide-music', 369 | 'lucide-navigation-2', 370 | 'lucide-navigation', 371 | 'lucide-network', 372 | 'lucide-octagon', 373 | 'lucide-option', 374 | 'lucide-outdent', 375 | 'lucide-package-check', 376 | 'lucide-package-minus', 377 | 'lucide-package-plus', 378 | 'lucide-package-search', 379 | 'lucide-package', 380 | 'lucide-package-x', 381 | 'lucide-palette', 382 | 'lucide-palmtree', 383 | 'lucide-paperclip', 384 | 'lucide-pause-circle', 385 | 'lucide-pause-octagon', 386 | 'lucide-pause', 387 | 'lucide-pencil', 388 | 'lucide-pen-tool', 389 | 'lucide-percent', 390 | 'lucide-person-standing', 391 | 'lucide-phone-call', 392 | 'lucide-phone-forwarded', 393 | 'lucide-phone-incoming', 394 | 'lucide-phone-missed', 395 | 'lucide-phone-off', 396 | 'lucide-phone-outgoing', 397 | 'lucide-phone', 398 | 'lucide-pie-chart', 399 | 'lucide-piggy-bank', 400 | 'lucide-pin', 401 | 'lucide-pipette', 402 | 'lucide-plane', 403 | 'lucide-play-circle', 404 | 'lucide-play', 405 | 'lucide-plug-zap', 406 | 'lucide-plus-circle', 407 | 'lucide-plus-square', 408 | 'lucide-plus', 409 | 'lucide-pocket', 410 | 'lucide-podcast', 411 | 'lucide-pointer', 412 | 'lucide-pound-sterling', 413 | 'lucide-power-off', 414 | 'lucide-power', 415 | 'lucide-printer', 416 | 'lucide-qr-code', 417 | 'lucide-quote', 418 | 'lucide-radio-receiver', 419 | 'lucide-radio', 420 | 'lucide-redo', 421 | 'lucide-refresh-ccw', 422 | 'lucide-refresh-cw', 423 | 'lucide-regex', 424 | 'lucide-repeat-1', 425 | 'lucide-repeat', 426 | 'lucide-reply-all', 427 | 'lucide-reply', 428 | 'lucide-rewind', 429 | 'lucide-rocking-chair', 430 | 'lucide-rotate-ccw', 431 | 'lucide-rotate-cw', 432 | 'lucide-rss', 433 | 'lucide-ruler', 434 | 'lucide-russian-ruble', 435 | 'lucide-save', 436 | 'lucide-scale', 437 | 'lucide-scan-line', 438 | 'lucide-scan', 439 | 'lucide-scissors', 440 | 'lucide-screen-share-off', 441 | 'lucide-screen-share', 442 | 'lucide-search', 443 | 'lucide-send', 444 | 'lucide-separator-horizontal', 445 | 'lucide-separator-vertical', 446 | 'lucide-server-crash', 447 | 'lucide-server-off', 448 | 'lucide-server', 449 | 'lucide-settings-2', 450 | 'lucide-settings', 451 | 'lucide-share-2', 452 | 'lucide-share', 453 | 'lucide-sheet', 454 | 'lucide-shield-alert', 455 | 'lucide-shield-check', 456 | 'lucide-shield-close', 457 | 'lucide-shield-off', 458 | 'lucide-shield', 459 | 'lucide-shirt', 460 | 'lucide-shopping-bag', 461 | 'lucide-shopping-cart', 462 | 'lucide-shovel', 463 | 'lucide-shrink', 464 | 'lucide-shuffle', 465 | 'lucide-sidebar-close', 466 | 'lucide-sidebar-open', 467 | 'lucide-sidebar', 468 | 'lucide-sigma', 469 | 'lucide-signal-high', 470 | 'lucide-signal-low', 471 | 'lucide-signal-medium', 472 | 'lucide-signal', 473 | 'lucide-signal-zero', 474 | 'lucide-skip-back', 475 | 'lucide-skip-forward', 476 | 'lucide-skull', 477 | 'lucide-slack', 478 | 'lucide-slash', 479 | 'lucide-sliders', 480 | 'lucide-smartphone-charging', 481 | 'lucide-smartphone', 482 | 'lucide-smile', 483 | 'lucide-snowflake', 484 | 'lucide-sort-asc', 485 | 'lucide-sort-desc', 486 | 'lucide-speaker', 487 | 'lucide-sprout', 488 | 'lucide-square', 489 | 'lucide-star-half', 490 | 'lucide-star', 491 | 'lucide-stop-circle', 492 | 'lucide-stretch-horizontal', 493 | 'lucide-stretch-vertical', 494 | 'lucide-strikethrough', 495 | 'lucide-subscript', 496 | 'lucide-sunrise', 497 | 'lucide-sunset', 498 | 'lucide-sun', 499 | 'lucide-superscript', 500 | 'lucide-swiss-franc', 501 | 'lucide-switch-camera', 502 | 'lucide-table', 503 | 'lucide-tablet', 504 | 'lucide-tag', 505 | 'lucide-target', 506 | 'lucide-tent', 507 | 'lucide-terminal-square', 508 | 'lucide-terminal', 509 | 'lucide-text-cursor-input', 510 | 'lucide-text-cursor', 511 | 'lucide-thermometer-snowflake', 512 | 'lucide-thermometer-sun', 513 | 'lucide-thermometer', 514 | 'lucide-thumbs-down', 515 | 'lucide-thumbs-up', 516 | 'lucide-ticket', 517 | 'lucide-timer-off', 518 | 'lucide-timer-reset', 519 | 'lucide-timer', 520 | 'lucide-toggle-left', 521 | 'lucide-toggle-right', 522 | 'lucide-tornado', 523 | 'lucide-trash-2', 524 | 'lucide-trash', 525 | 'lucide-trello', 526 | 'lucide-trending-down', 527 | 'lucide-trending-up', 528 | 'lucide-triangle', 529 | 'lucide-truck', 530 | 'lucide-tv-2', 531 | 'lucide-tv', 532 | 'lucide-twitch', 533 | 'lucide-twitter', 534 | 'lucide-type', 535 | 'lucide-umbrella', 536 | 'lucide-underline', 537 | 'lucide-undo', 538 | 'lucide-unlink-2', 539 | 'lucide-unlink', 540 | 'lucide-unlock', 541 | 'lucide-upload-cloud', 542 | 'lucide-upload', 543 | 'lucide-user-check', 544 | 'lucide-user-minus', 545 | 'lucide-user-plus', 546 | 'lucide-users', 547 | 'lucide-user', 548 | 'lucide-user-x', 549 | 'lucide-verified', 550 | 'lucide-vibrate', 551 | 'lucide-video-off', 552 | 'lucide-video', 553 | 'lucide-view', 554 | 'lucide-voicemail', 555 | 'lucide-volume-1', 556 | 'lucide-volume-2', 557 | 'lucide-volume', 558 | 'lucide-volume-x', 559 | 'lucide-wallet', 560 | 'lucide-wand', 561 | 'lucide-watch', 562 | 'lucide-waves', 563 | 'lucide-webcam', 564 | 'lucide-wifi-off', 565 | 'lucide-wifi', 566 | 'lucide-wind', 567 | 'lucide-wrap-text', 568 | 'lucide-wrench', 569 | 'lucide-x-circle', 570 | 'lucide-x-octagon', 571 | 'lucide-x-square', 572 | 'lucide-x', 573 | 'lucide-youtube', 574 | 'lucide-zap-off', 575 | 'lucide-zap', 576 | 'lucide-zoom-in', 577 | 'lucide-zoom-out', 578 | ]; 579 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Platform, 3 | Plugin, 4 | View, 5 | Workspace, 6 | WorkspaceLeaf, 7 | WorkspaceWindow, 8 | } from 'obsidian'; 9 | import { around } from 'monkey-around'; 10 | import type { 11 | baseButton, 12 | enabledButton, 13 | TopBarButtonsSettings, 14 | } from './interfaces'; 15 | import TopBarButtonsSettingTab from './settings'; 16 | import { obsiIcons, PLUGIN_CLASS_NAME, TITLEBAR_CLASSES } from './constants'; 17 | import { 18 | exchangeCenterTitleBar, 19 | getButtonIcon, 20 | getCenterTitleBar, 21 | getIconSize, 22 | getLeftTitleBar, 23 | getRightTitleBar, 24 | getTitlebarText, 25 | removeAllPageHeaderButtons, 26 | removeAllTitleBarButtons, 27 | removeTitlebarText, 28 | restoreCenterTitlebar, 29 | } from './utils'; 30 | import { lucideIcons } from './lucide'; 31 | 32 | declare module 'obsidian' { 33 | interface Workspace { 34 | onLayoutChange(): void; // tell plugins the layout changed 35 | floatingSplit: { 36 | children: WorkspaceWindow[]; 37 | }; 38 | } 39 | } 40 | 41 | const DEFAULT_SETTINGS: TopBarButtonsSettings = { 42 | enabledButtons: [], 43 | desktop: false, 44 | titleLeft: [], 45 | titleRight: [], 46 | titleCenter: [], 47 | paneRelief: false, 48 | }; 49 | 50 | export default class TopBarButtonsPlugin extends Plugin { 51 | settings!: TopBarButtonsSettings; 52 | iconList: string[] = obsiIcons.concat(lucideIcons); 53 | listener!: () => void; 54 | titlebarText: Element[] = []; 55 | windows: Document[] = []; 56 | 57 | addPageHeaderButton( 58 | viewActions: Element, 59 | button: enabledButton | baseButton 60 | ) { 61 | const { id, icon, name } = button; 62 | const iconSize = getIconSize(); 63 | const classes = ['view-action', 'clickable-icon', PLUGIN_CLASS_NAME]; 64 | 65 | const buttonIcon = getButtonIcon(name, id, icon, iconSize, classes); 66 | 67 | if ( 68 | this.settings.paneRelief && 69 | (id === 'app:go-forward' || id === 'app:go-back') 70 | ) { 71 | buttonIcon.addClass('pane-relief'); 72 | } 73 | 74 | viewActions.prepend(buttonIcon); 75 | 76 | this.registerDomEvent(buttonIcon, 'mousedown', (evt) => { 77 | /* This way the pane gets activated from the click, 78 | * otherwise the action would get executed on the former active pane. 79 | * Timeout of 1 was enough, but 5 is chosen for slower computers. 80 | * May need to be made its own setting in the future. 81 | * 82 | * Only execute event on primary button press. Needed for compatibility with Pane Relief. 83 | */ 84 | if (evt.button === 0) { 85 | setTimeout(() => { 86 | this.app.commands.executeCommandById(id); 87 | }, 5); 88 | } 89 | }); 90 | } 91 | 92 | addLeftTitleBarButton(viewActions: Element, button: baseButton) { 93 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 94 | const { id, icon, name } = button; 95 | const iconSize = 15; 96 | const buttonIcon = getButtonIcon( 97 | name, 98 | id, 99 | icon, 100 | iconSize, 101 | TITLEBAR_CLASSES, 102 | 'div' 103 | ); 104 | viewActions.append(buttonIcon); 105 | 106 | this.registerDomEvent(buttonIcon, 'click', () => { 107 | this.app.commands.executeCommandById(id); 108 | }); 109 | } 110 | 111 | addRightTitleBarButton(viewActions: Element, button: baseButton) { 112 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 113 | const { id, icon, name } = button; 114 | const iconSize = 15; 115 | const buttonIcon = getButtonIcon( 116 | name, 117 | id, 118 | icon, 119 | iconSize, 120 | TITLEBAR_CLASSES, 121 | 'div' 122 | ); 123 | // necessary for popout windows; in the main window .prepend() works, 124 | // but not in popout windows 125 | viewActions.insertBefore(buttonIcon, viewActions.children[0]); 126 | 127 | this.registerDomEvent(buttonIcon, 'click', () => { 128 | this.app.commands.executeCommandById(id); 129 | }); 130 | } 131 | 132 | addCenterTitleBarButton(viewActions: Element, button: baseButton) { 133 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 134 | const { id, icon, name } = button; 135 | const iconSize = 15; 136 | const buttonIcon = getButtonIcon( 137 | name, 138 | id, 139 | icon, 140 | iconSize, 141 | TITLEBAR_CLASSES, 142 | 'div' 143 | ); 144 | viewActions.append(buttonIcon); 145 | 146 | this.registerDomEvent(buttonIcon, 'click', () => { 147 | this.app.commands.executeCommandById(id); 148 | }); 149 | } 150 | 151 | addLeftTitleBarButtons(doc: Document) { 152 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 153 | if (this.settings.titleLeft.length > 0) { 154 | const modLeft = getLeftTitleBar(doc); 155 | if (!modLeft) return; 156 | for (const button of this.settings.titleLeft) { 157 | this.addLeftTitleBarButton(modLeft, button); 158 | } 159 | } 160 | } 161 | 162 | addRightTitleBarButtons(doc: Document) { 163 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 164 | if (this.settings.titleRight.length > 0) { 165 | const modRight = getRightTitleBar(doc); 166 | if (!modRight) return; 167 | for (let i = this.settings.titleRight.length - 1; i >= 0; i--) { 168 | this.addRightTitleBarButton( 169 | modRight, 170 | this.settings.titleRight[i] 171 | ); 172 | } 173 | } 174 | } 175 | 176 | // before any button is added, the parent needs to be removed and the 177 | // own parent added; this requires checks in the settings modal when 178 | // buttons are added and removed 179 | /* 180 | addInitialCenterTitleBarButtons(doc: Document) { 181 | if (this.settings.titleCenter.length > 0) { 182 | const center = exchangeCenterTitleBar(doc); 183 | //const center = document.getElementsByClassName( 184 | // `${PLUGIN_CLASS_NAME} ${TITLEBAR_CENTER}` 185 | //)[0]; 186 | for (const button of this.settings.titleCenter) { 187 | this.addCenterTitleBarButton(center, button); 188 | } 189 | } 190 | } 191 | */ 192 | 193 | addCenterTitleBarButtons(doc: Document) { 194 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 195 | if (this.settings.titleCenter.length > 0) { 196 | const center = getCenterTitleBar(doc); 197 | if (!center) return; 198 | //const center = document.getElementsByClassName( 199 | // `${PLUGIN_CLASS_NAME} ${TITLEBAR_CENTER}` 200 | //)[0]; 201 | for (const button of this.settings.titleCenter) { 202 | this.addCenterTitleBarButton(center, button); 203 | } 204 | } 205 | } 206 | 207 | initTitleBar(doc: Document) { 208 | if (activeDocument.body.classList.contains('is-hidden-frameless')) return; 209 | 210 | this.addLeftTitleBarButtons(doc); 211 | this.addRightTitleBarButtons(doc); 212 | // store the element so that it can be restored later on 213 | if (this.settings.titleCenter.length > 0) { 214 | const titleBarText = getTitlebarText(doc); 215 | if (!titleBarText) return; 216 | // differentiate between enabled after start and enabled on start 217 | if (!this.titlebarText.contains(titleBarText)) { 218 | this.titlebarText.push(titleBarText); 219 | removeTitlebarText(doc); 220 | // could be passed; maybe for next refactoring 221 | const newActions = exchangeCenterTitleBar(doc); 222 | this.addCenterTitleBarButtons(doc); 223 | } 224 | } 225 | } 226 | async onload() { 227 | console.log('loading Customizable Page Header Plugin'); 228 | 229 | await this.loadSettings(); 230 | this.addSettingTab(new TopBarButtonsSettingTab(this.app, this)); 231 | 232 | this.app.workspace.onLayoutReady(() => { 233 | if (Platform.isDesktopApp) { 234 | this.windows.push(document); 235 | this.initTitleBar(document); 236 | const { children } = this.app.workspace.floatingSplit; 237 | // window-open onload (restart of app) gets called before 238 | // onLayoutReady; this covers the case that obsidian starts with 239 | // multiple windows, but adds the buttons to pop-out windows when 240 | // the plugin is newly activated 241 | if (children.length + 1 !== this.windows.length) { 242 | for (const el of children) { 243 | // should be redundant, but Licat said to check 244 | if (el instanceof WorkspaceWindow) { 245 | const currentDoc = el.getContainer()?.doc; 246 | if (currentDoc) { 247 | this.windows.push(currentDoc); 248 | this.initTitleBar(currentDoc); 249 | } 250 | } 251 | } 252 | } 253 | } 254 | if (Platform.isMobile || this.settings.desktop) { 255 | this.addButtonsToAllLeaves(); 256 | } 257 | }); 258 | 259 | if (Platform.isMobile || this.settings.desktop) { 260 | const self = this; 261 | this.register( 262 | around(Workspace.prototype, { 263 | changeLayout(old) { 264 | return async function changeLayout( 265 | this: Workspace, 266 | ws: any 267 | ) { 268 | await old.call(this, ws); 269 | self.addButtonsToAllLeaves(); 270 | }; 271 | }, 272 | }) 273 | ); 274 | this.registerEvent( 275 | this.app.workspace.on('layout-change', () => { 276 | const activeLeaf = app.workspace.getActiveViewOfType(View); 277 | if (!activeLeaf) { 278 | return; 279 | } 280 | this.addButtonsToLeaf(activeLeaf.leaf); 281 | }) 282 | ); 283 | this.registerEvent( 284 | this.app.workspace.on('window-open', (win, window) => { 285 | const currentDoc = win.getContainer()?.doc; 286 | if (currentDoc) { 287 | this.windows.push(currentDoc); 288 | this.initTitleBar(currentDoc); 289 | } 290 | }) 291 | ); 292 | } 293 | } 294 | 295 | addButtonsToAllLeaves() { 296 | app.workspace.iterateAllLeaves((leaf) => this.addButtonsToLeaf(leaf)); 297 | app.workspace.onLayoutChange(); 298 | } 299 | 300 | addButtonsToLeaf(leaf: WorkspaceLeaf) { 301 | const activeLeaf = leaf?.view.containerEl; 302 | const viewActions = 303 | activeLeaf?.getElementsByClassName('view-actions')[0]; 304 | 305 | // sidebar panes without a view-header can take focus 306 | if (!viewActions) { 307 | return; 308 | } 309 | 310 | for (let i = this.settings.enabledButtons.length - 1; i >= 0; i--) { 311 | // Remove the existing element first 312 | viewActions 313 | .getElementsByClassName( 314 | `view-action page-header-button ${this.settings.enabledButtons[i].id}` 315 | )[0] 316 | ?.detach(); 317 | if ( 318 | this.settings.enabledButtons[i].showButtons === 'both' || 319 | (this.settings.enabledButtons[i].showButtons === 'mobile' && 320 | Platform.isMobile) || 321 | (this.settings.enabledButtons[i].showButtons === 'desktop' && 322 | Platform.isDesktop) 323 | ) { 324 | this.addPageHeaderButton( 325 | viewActions, 326 | this.settings.enabledButtons[i] 327 | ); 328 | } 329 | } 330 | } 331 | 332 | onunload() { 333 | console.log('unloading Customizable Page Header Plugin'); 334 | removeAllPageHeaderButtons(); 335 | 336 | for (let i = 0; i < this.windows.length; i++) { 337 | removeAllTitleBarButtons(this.windows[i]); 338 | restoreCenterTitlebar(this.titlebarText[i], this.windows[i]); 339 | } 340 | 341 | globalThis.removeEventListener('TopBar-addedCommand', this.listener); 342 | } 343 | 344 | async loadSettings() { 345 | this.settings = Object.assign( 346 | {}, 347 | DEFAULT_SETTINGS, 348 | await this.loadData() 349 | ); 350 | } 351 | 352 | async saveSettings() { 353 | await this.saveData(this.settings); 354 | app.workspace.onLayoutReady(() => this.addButtonsToAllLeaves()); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, Platform, PluginSettingTab, setIcon, Setting } from 'obsidian'; 2 | import type TopBarButtonsPlugin from './main'; 3 | import CommandSuggester from './ui/commandSuggester'; 4 | import IconPicker from './ui/iconPicker'; 5 | import type { Buttons } from './interfaces'; 6 | import { 7 | removeAllTitleBarButtons, 8 | removeCenterTitleBarButton, 9 | removeCenterTitleBarButtons, 10 | removeLeftTitleBarButton, 11 | removeLeftTitleBarButtons, 12 | removePageHeaderButton, 13 | removeRightTitleBarButton, 14 | removeRightTitleBarButtons, 15 | restoreCenterTitlebar, 16 | } from './utils'; 17 | 18 | export default class TopBarButtonsSettingTab extends PluginSettingTab { 19 | plugin: TopBarButtonsPlugin; 20 | 21 | constructor(app: App, plugin: TopBarButtonsPlugin) { 22 | super(app, plugin); 23 | this.plugin = plugin; 24 | this.plugin.listener = () => { 25 | this.display(); 26 | }; 27 | this.containerEl.addClass('page-header-button'); 28 | addEventListener('TopBar-addedCommand', this.plugin.listener); 29 | } 30 | 31 | display(): void { 32 | const { containerEl } = this; 33 | const { settings } = this.plugin; 34 | 35 | containerEl.empty(); 36 | 37 | containerEl.createEl('h3', { 38 | text: 'Page Header Buttons', 39 | }); 40 | 41 | containerEl.createEl('p', { 42 | text: 'The buttons are added in the order in which they are shown here.', 43 | }); 44 | 45 | new Setting(containerEl) 46 | .setName('Show buttons on desktop') 47 | .setDesc( 48 | 'By default, the buttons will only be shown in Obsidian Mobile.' 49 | ) 50 | .addToggle((toggle) => { 51 | toggle.setValue(settings.desktop).onChange(async (state) => { 52 | settings.desktop = state; 53 | await this.plugin.saveSettings(); 54 | this.display(); 55 | if (!state) { 56 | for (let button of settings.enabledButtons) { 57 | button.showButtons = 'mobile'; 58 | } 59 | await this.plugin.saveSettings(); 60 | } 61 | }); 62 | }); 63 | 64 | // Thank you: https://github.com/phibr0/obsidian-customizable-sidebar/blob/50099ff41b17758b20f52bfd9a80abf8319c29fb/src/ui/settingsTab.ts 65 | new Setting(containerEl) 66 | .setName('Add Button') 67 | .setDesc( 68 | 'Add a new button left to the switch edit/preview mode toggle.' 69 | ) 70 | .addButton((button) => { 71 | button.setButtonText('Add Command').onClick(() => { 72 | new CommandSuggester(this.plugin, 'page').open(); 73 | }); 74 | }); 75 | 76 | if (this.app.plugins.plugins['pane-relief']) { 77 | new Setting(containerEl) 78 | .setName('Pane Relief count') 79 | .setDesc( 80 | 'Enable to show the pane relief history count next to back/forward buttons.' 81 | ) 82 | .addToggle((toggle) => { 83 | toggle 84 | .setValue(settings.paneRelief) 85 | .onChange(async (state) => { 86 | settings.paneRelief = state; 87 | await this.plugin.saveSettings(); 88 | }); 89 | }); 90 | } 91 | 92 | new Setting(containerEl) 93 | .setName(`Button spacing`) 94 | .setDesc('Set the spacing for Page Header buttons.') 95 | .addButton((b) => { 96 | b.setButtonText( 97 | 'Click to go to the Style settings. Requires the Style settings plugin.' 98 | ); 99 | b.onClick((e) => 100 | this.app.setting.openTabById('obsidian-style-settings') 101 | ); 102 | }); 103 | 104 | for (let i = 0; i < settings.enabledButtons.length; i++) { 105 | let command = settings.enabledButtons[i]; 106 | const iconDiv = createDiv({ cls: 'CS-settings-icon' }); 107 | setIcon(iconDiv, command.icon, 24); 108 | let setting = new Setting(containerEl).setName(command.name); 109 | if (settings.desktop) { 110 | setting.addDropdown((dropdown) => { 111 | dropdown 112 | .addOption( 113 | 'both', 114 | 'Add button for both mobile and desktop.' 115 | ) 116 | .addOption('mobile', 'Add button only for mobile.') 117 | .addOption('desktop', 'Add button only for desktop.') 118 | .setValue(command.showButtons) 119 | .onChange( 120 | //@ts-ignore 121 | async (newValue: Buttons) => { 122 | command.showButtons = newValue; 123 | settings.enabledButtons[i] = command; 124 | await this.plugin.saveSettings(); 125 | if ( 126 | newValue === 'desktop' && 127 | Platform.isMobile 128 | ) { 129 | removePageHeaderButton(command.id); 130 | } else if ( 131 | newValue === 'mobile' && 132 | Platform.isDesktop 133 | ) { 134 | removePageHeaderButton(command.id); 135 | } 136 | } 137 | ); 138 | }); 139 | } 140 | if (i > 0) { 141 | setting.addExtraButton((button) => { 142 | button 143 | .setIcon('up-arrow-with-tail') 144 | .setTooltip('Move button to the left') 145 | .onClick(async () => { 146 | settings.enabledButtons[i] = 147 | settings.enabledButtons[i - 1]; 148 | settings.enabledButtons[i - 1] = command; 149 | await this.plugin.saveSettings(); 150 | this.display(); 151 | }); 152 | }); 153 | } 154 | if (i < settings.enabledButtons.length - 1) { 155 | setting.addExtraButton((button) => { 156 | button 157 | .setIcon('down-arrow-with-tail') 158 | .setTooltip('Move button to the right') 159 | .onClick(async () => { 160 | settings.enabledButtons[i] = 161 | settings.enabledButtons[i + 1]; 162 | settings.enabledButtons[i + 1] = command; 163 | await this.plugin.saveSettings(); 164 | this.display(); 165 | }); 166 | }); 167 | } 168 | setting 169 | .addExtraButton((button) => { 170 | button 171 | .setIcon('trash') 172 | .setTooltip('Remove Command') 173 | .onClick(async () => { 174 | settings.enabledButtons.remove(command); 175 | removePageHeaderButton(command.id); 176 | await this.plugin.saveSettings(); 177 | this.display(); 178 | }); 179 | }) 180 | .addExtraButton((button) => { 181 | button 182 | .setIcon('gear') 183 | .setTooltip('Edit Icon') 184 | .onClick(() => { 185 | const index = settings.enabledButtons.findIndex( 186 | (el) => el === command 187 | ); 188 | new IconPicker( 189 | this.plugin, 190 | command, 191 | 'page', 192 | index 193 | ).open(); 194 | }); 195 | }); 196 | setting.nameEl.prepend(iconDiv); 197 | setting.nameEl.addClass('CS-flex'); 198 | } 199 | 200 | if (Platform.isDesktopApp) { 201 | containerEl.createEl('br'); 202 | containerEl.createEl('h3', { 203 | text: 'Titlebar buttons', 204 | }); 205 | 206 | containerEl.createEl('h4', { 207 | text: 'Left titlebar', 208 | }); 209 | 210 | new Setting(containerEl) 211 | .setName('Add Button') 212 | .setDesc('Add a new button right to the back/forward buttons.') 213 | .addButton((button) => { 214 | button.setButtonText('Add Command').onClick(() => { 215 | new CommandSuggester(this.plugin, 'title-left').open(); 216 | }); 217 | }); 218 | 219 | for (let i = 0; i < settings.titleLeft.length; i++) { 220 | let command = settings.titleLeft[i]; 221 | const iconDiv = createDiv({ cls: 'CS-settings-icon' }); 222 | setIcon(iconDiv, command.icon, 24); 223 | let setting = new Setting(containerEl).setName(command.name); 224 | if (i > 0) { 225 | setting.addExtraButton((button) => { 226 | button 227 | .setIcon('up-arrow-with-tail') 228 | .setTooltip('Move button to the left') 229 | .onClick(async () => { 230 | settings.titleLeft[i] = 231 | settings.titleLeft[i - 1]; 232 | settings.titleLeft[i - 1] = command; 233 | await this.plugin.saveSettings(); 234 | for ( 235 | let j = 0; 236 | j < this.plugin.windows.length; 237 | j++ 238 | ) { 239 | removeLeftTitleBarButtons( 240 | this.plugin.windows[j] 241 | ); 242 | this.plugin.addLeftTitleBarButtons( 243 | this.plugin.windows[j] 244 | ); 245 | } 246 | this.display(); 247 | }); 248 | }); 249 | } 250 | if (i < settings.titleLeft.length - 1) { 251 | setting.addExtraButton((button) => { 252 | button 253 | .setIcon('down-arrow-with-tail') 254 | .setTooltip('Move button to the right') 255 | .onClick(async () => { 256 | settings.titleLeft[i] = 257 | settings.titleLeft[i + 1]; 258 | settings.titleLeft[i + 1] = command; 259 | await this.plugin.saveSettings(); 260 | for ( 261 | let j = 0; 262 | j < this.plugin.windows.length; 263 | j++ 264 | ) { 265 | removeLeftTitleBarButtons( 266 | this.plugin.windows[j] 267 | ); 268 | this.plugin.addLeftTitleBarButtons( 269 | this.plugin.windows[j] 270 | ); 271 | } 272 | this.display(); 273 | }); 274 | }); 275 | } 276 | setting 277 | .addExtraButton((button) => { 278 | button 279 | .setIcon('trash') 280 | .setTooltip('Remove Command') 281 | .onClick(async () => { 282 | settings.titleLeft.remove(command); 283 | await this.plugin.saveSettings(); 284 | for ( 285 | let j = 0; 286 | j < this.plugin.windows.length; 287 | j++ 288 | ) { 289 | removeLeftTitleBarButton( 290 | command.id, 291 | this.plugin.windows[j] 292 | ); 293 | } 294 | this.display(); 295 | }); 296 | }) 297 | .addExtraButton((button) => { 298 | button 299 | .setIcon('gear') 300 | .setTooltip('Edit Icon') 301 | .onClick(() => { 302 | const index = settings.titleLeft.findIndex( 303 | (el) => el === command 304 | ); 305 | new IconPicker( 306 | this.plugin, 307 | command, 308 | 'title-left', 309 | index 310 | ).open(); 311 | for ( 312 | let j = 0; 313 | j < this.plugin.windows.length; 314 | j++ 315 | ) { 316 | removeLeftTitleBarButtons( 317 | this.plugin.windows[j] 318 | ); 319 | this.plugin.addLeftTitleBarButtons( 320 | this.plugin.windows[j] 321 | ); 322 | } 323 | }); 324 | }); 325 | setting.nameEl.prepend(iconDiv); 326 | setting.nameEl.addClass('CS-flex'); 327 | } 328 | 329 | containerEl.createEl('h4', { 330 | text: 'Right titlebar', 331 | }); 332 | 333 | new Setting(containerEl) 334 | .setName('Add Button') 335 | .setDesc( 336 | 'Add a new button left to the minimize/maximize/close buttons.' 337 | ) 338 | .addButton((button) => { 339 | button.setButtonText('Add Command').onClick(() => { 340 | new CommandSuggester(this.plugin, 'title-right').open(); 341 | }); 342 | }); 343 | 344 | for (let i = 0; i < settings.titleRight.length; i++) { 345 | let command = settings.titleRight[i]; 346 | const iconDiv = createDiv({ cls: 'CS-settings-icon' }); 347 | setIcon(iconDiv, command.icon, 24); 348 | let setting = new Setting(containerEl).setName(command.name); 349 | if (i > 0) { 350 | setting.addExtraButton((button) => { 351 | button 352 | .setIcon('up-arrow-with-tail') 353 | .setTooltip('Move button to the left') 354 | .onClick(async () => { 355 | settings.titleRight[i] = 356 | settings.titleRight[i - 1]; 357 | settings.titleRight[i - 1] = command; 358 | await this.plugin.saveSettings(); 359 | for ( 360 | let j = 0; 361 | j < this.plugin.windows.length; 362 | j++ 363 | ) { 364 | removeRightTitleBarButtons( 365 | this.plugin.windows[j] 366 | ); 367 | this.plugin.addRightTitleBarButtons( 368 | this.plugin.windows[j] 369 | ); 370 | } 371 | this.display(); 372 | }); 373 | }); 374 | } 375 | if (i < settings.titleRight.length - 1) { 376 | setting.addExtraButton((button) => { 377 | button 378 | .setIcon('down-arrow-with-tail') 379 | .setTooltip('Move button to the right') 380 | .onClick(async () => { 381 | settings.titleRight[i] = 382 | settings.titleRight[i + 1]; 383 | settings.titleRight[i + 1] = command; 384 | await this.plugin.saveSettings(); 385 | for ( 386 | let j = 0; 387 | j < this.plugin.windows.length; 388 | j++ 389 | ) { 390 | removeRightTitleBarButtons( 391 | this.plugin.windows[j] 392 | ); 393 | this.plugin.addRightTitleBarButtons( 394 | this.plugin.windows[j] 395 | ); 396 | } 397 | this.display(); 398 | }); 399 | }); 400 | } 401 | setting 402 | .addExtraButton((button) => { 403 | button 404 | .setIcon('trash') 405 | .setTooltip('Remove Command') 406 | .onClick(async () => { 407 | settings.titleRight.remove(command); 408 | await this.plugin.saveSettings(); 409 | for ( 410 | let j = 0; 411 | j < this.plugin.windows.length; 412 | j++ 413 | ) { 414 | removeRightTitleBarButton( 415 | command.id, 416 | this.plugin.windows[j] 417 | ); 418 | } 419 | this.display(); 420 | }); 421 | }) 422 | .addExtraButton((button) => { 423 | button 424 | .setIcon('gear') 425 | .setTooltip('Edit Icon') 426 | .onClick(() => { 427 | const index = settings.titleRight.findIndex( 428 | (el) => el === command 429 | ); 430 | new IconPicker( 431 | this.plugin, 432 | command, 433 | 'title-right', 434 | index 435 | ).open(); 436 | for ( 437 | let j = 0; 438 | j < this.plugin.windows.length; 439 | j++ 440 | ) { 441 | removeRightTitleBarButtons( 442 | this.plugin.windows[j] 443 | ); 444 | this.plugin.addRightTitleBarButtons( 445 | this.plugin.windows[j] 446 | ); 447 | } 448 | }); 449 | }); 450 | setting.nameEl.prepend(iconDiv); 451 | setting.nameEl.addClass('CS-flex'); 452 | } 453 | 454 | containerEl.createEl('h4', { 455 | text: 'Center titlebar', 456 | }); 457 | 458 | new Setting(containerEl) 459 | .setName('Add Button') 460 | .setDesc('Add a new button to the center titlebar.') 461 | .addButton((button) => { 462 | button.setButtonText('Add Command').onClick(() => { 463 | new CommandSuggester( 464 | this.plugin, 465 | 'title-center' 466 | ).open(); 467 | }); 468 | }); 469 | 470 | for (let i = 0; i < settings.titleCenter.length; i++) { 471 | let command = settings.titleCenter[i]; 472 | const iconDiv = createDiv({ cls: 'CS-settings-icon' }); 473 | setIcon(iconDiv, command.icon, 24); 474 | let setting = new Setting(containerEl).setName(command.name); 475 | if (i > 0) { 476 | setting.addExtraButton((button) => { 477 | button 478 | .setIcon('up-arrow-with-tail') 479 | .setTooltip('Move button to the left') 480 | .onClick(async () => { 481 | settings.titleCenter[i] = 482 | settings.titleCenter[i - 1]; 483 | settings.titleCenter[i - 1] = command; 484 | await this.plugin.saveSettings(); 485 | for ( 486 | let j = 0; 487 | j < this.plugin.windows.length; 488 | j++ 489 | ) { 490 | removeCenterTitleBarButtons( 491 | this.plugin.windows[j] 492 | ); 493 | this.plugin.addCenterTitleBarButtons( 494 | this.plugin.windows[j] 495 | ); 496 | } 497 | this.display(); 498 | }); 499 | }); 500 | } 501 | if (i < settings.titleCenter.length - 1) { 502 | setting.addExtraButton((button) => { 503 | button 504 | .setIcon('down-arrow-with-tail') 505 | .setTooltip('Move button to the right') 506 | .onClick(async () => { 507 | settings.titleCenter[i] = 508 | settings.titleCenter[i + 1]; 509 | settings.titleCenter[i + 1] = command; 510 | await this.plugin.saveSettings(); 511 | for ( 512 | let j = 0; 513 | j < this.plugin.windows.length; 514 | j++ 515 | ) { 516 | removeCenterTitleBarButtons( 517 | this.plugin.windows[j] 518 | ); 519 | this.plugin.addCenterTitleBarButtons( 520 | this.plugin.windows[j] 521 | ); 522 | } 523 | this.display(); 524 | }); 525 | }); 526 | } 527 | setting 528 | .addExtraButton((button) => { 529 | button 530 | .setIcon('trash') 531 | .setTooltip('Remove Command') 532 | .onClick(async () => { 533 | settings.titleCenter.remove(command); 534 | await this.plugin.saveSettings(); 535 | if ( 536 | this.plugin.settings.titleCenter.length === 537 | 0 538 | ) { 539 | for ( 540 | let i = 0; 541 | i < this.plugin.windows.length; 542 | i++ 543 | ) { 544 | restoreCenterTitlebar( 545 | this.plugin.titlebarText[i], 546 | this.plugin.windows[i] 547 | ); 548 | } 549 | } else { 550 | for ( 551 | let j = 0; 552 | j < this.plugin.windows.length; 553 | j++ 554 | ) { 555 | removeCenterTitleBarButton( 556 | command.id, 557 | this.plugin.windows[j] 558 | ); 559 | } 560 | } 561 | this.display(); 562 | }); 563 | }) 564 | .addExtraButton((button) => { 565 | button 566 | .setIcon('gear') 567 | .setTooltip('Edit Icon') 568 | .onClick(() => { 569 | const index = settings.titleCenter.findIndex( 570 | (el) => el === command 571 | ); 572 | new IconPicker( 573 | this.plugin, 574 | command, 575 | 'title-center', 576 | index 577 | ).open(); 578 | }); 579 | }); 580 | setting.nameEl.prepend(iconDiv); 581 | setting.nameEl.addClass('CS-flex'); 582 | } 583 | } 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | .view-header-title { 2 | display: var(--display-view-header-title, initial); 3 | } 4 | 5 | .page-header-button.titlebar-center { 6 | position: absolute; 7 | width: 100%; 8 | height: 100%; 9 | top: 0; 10 | left: 0; 11 | flex-grow: 1; 12 | font-size: 13px; 13 | text-align: center; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | opacity: 0.8; 18 | padding: 0 125px; 19 | overflow: hidden; 20 | white-space: nowrap; 21 | text-overflow: ellipsis; 22 | } 23 | 24 | .titlebar-button { 25 | align-items: center; 26 | 27 | &.titlebar-button.page-header-button { 28 | padding: 16px var(--titlebar-button-horizontal-spacing, 16px); 29 | display: flex; 30 | align-items: center; 31 | } 32 | } 33 | 34 | /* Pane relief count */ 35 | 36 | body:not(.is-mobile) { 37 | .view-actions { 38 | align-items: center; 39 | } 40 | 41 | .view-action { 42 | /* fix for pop-out windows */ 43 | position: unset; 44 | display: flex; 45 | 46 | &.pane-relief { 47 | display: flex; 48 | position: unset; 49 | 50 | &.app\:go-forward::after, 51 | &.app\:go-back::before { 52 | display: inline; 53 | font-size: 1em; 54 | vertical-align: text-top; 55 | line-height: 1; 56 | } 57 | &.app\:go-forward::after { 58 | padding-left: 0.4em; 59 | content: var(--pane-relief-forward-count); 60 | } 61 | &.app\:go-back::before { 62 | padding-right: 0.4em; 63 | content: var(--pane-relief-backward-count); 64 | } 65 | } 66 | 67 | &:not(:last-child) { 68 | margin-right: var(--page-header-spacing-desktop); 69 | } 70 | } 71 | 72 | .pane-relief body:not(.no-svg-replace) svg { 73 | vertical-align: top; 74 | } 75 | } 76 | 77 | .is-mobile { 78 | .view-actions { 79 | align-items: center; 80 | } 81 | 82 | .view-action:not(:last-child) { 83 | margin-right: var(--page-header-spacing-mobile); 84 | } 85 | 86 | .view-header-title { 87 | -webkit-mask-image: none; 88 | mask-image: none; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ui/commandSuggester.ts: -------------------------------------------------------------------------------- 1 | // Thank you: https://github.com/phibr0/obsidian-customizable-sidebar/blob/50099ff41b17758b20f52bfd9a80abf8319c29fb/src/ui/commandSuggester.ts 2 | import { FuzzySuggestModal, Command } from 'obsidian'; 3 | import IconPicker from './iconPicker'; 4 | import type { 5 | ButtonSettings, 6 | TitleOrPage, 7 | TopBarButtonsSettings, 8 | } from '../interfaces'; 9 | import type TopBarButtonsPlugin from '../main'; 10 | import { 11 | exchangeCenterTitleBar, 12 | getTitlebarText, 13 | removeAllTitleBarButtons, 14 | removeCenterTitleBarButtons, 15 | removeLeftTitleBarButtons, 16 | removeRightTitleBarButtons, 17 | removeTitlebarText, 18 | restoreCenterTitlebar, 19 | } from '../utils'; 20 | 21 | export default class CommandSuggester extends FuzzySuggestModal { 22 | plugin: TopBarButtonsPlugin; 23 | type: TitleOrPage; 24 | 25 | constructor(plugin: TopBarButtonsPlugin, type: TitleOrPage) { 26 | super(plugin.app); 27 | this.plugin = plugin; 28 | this.type = type; 29 | } 30 | 31 | pushToSettings( 32 | settings: TopBarButtonsSettings, 33 | item: Command, 34 | location: ButtonSettings 35 | ) { 36 | if ( 37 | location === 'titleRight' || 38 | location === 'titleLeft' || 39 | location === 'titleCenter' 40 | ) { 41 | settings[location].push({ 42 | id: item.id, 43 | icon: item.icon as string, 44 | name: item.name, 45 | }); 46 | } else { 47 | settings.enabledButtons.push({ 48 | id: item.id, 49 | icon: item.icon as string, 50 | name: item.name, 51 | showButtons: 'both', 52 | }); 53 | } 54 | } 55 | 56 | getItems(): Command[] { 57 | return this.app.commands.listCommands(); 58 | } 59 | 60 | getItemText(item: Command): string { 61 | return item.name; 62 | } 63 | 64 | async onChooseItem( 65 | item: Command, 66 | evt: MouseEvent | KeyboardEvent 67 | ): Promise { 68 | const { settings } = this.plugin; 69 | let centerCounter = 0; 70 | if (item.icon !== undefined) { 71 | if (this.type === 'page') { 72 | this.pushToSettings(settings, item, 'enabledSettings'); 73 | } else if (this.type === 'title-left') { 74 | this.pushToSettings(settings, item, 'titleLeft'); 75 | } else if (this.type === 'title-right') { 76 | this.pushToSettings(settings, item, 'titleRight'); 77 | } else { 78 | const length = this.plugin.settings.titleCenter.length; 79 | if (length !== 0) { 80 | centerCounter = length; 81 | } 82 | this.pushToSettings(settings, item, 'titleCenter'); 83 | } 84 | await this.plugin.saveSettings(); 85 | if (this.type === 'title-left') { 86 | for (let i = 0; i < this.plugin.windows.length; i++) { 87 | removeLeftTitleBarButtons(this.plugin.windows[i]); 88 | this.plugin.addLeftTitleBarButtons(this.plugin.windows[i]); 89 | } 90 | } else if (this.type === 'title-right') { 91 | for (let i = 0; i < this.plugin.windows.length; i++) { 92 | removeRightTitleBarButtons(this.plugin.windows[i]); 93 | this.plugin.addRightTitleBarButtons(this.plugin.windows[i]); 94 | } 95 | } else if (this.type === 'title-center') { 96 | // initial button is added 97 | if (centerCounter === 0) { 98 | this.plugin.titlebarText = []; 99 | for (let i = 0; i < this.plugin.windows.length; i++) { 100 | const doc = this.plugin.windows[i]; 101 | this.plugin.titlebarText.push(getTitlebarText(doc)); 102 | removeTitlebarText(doc); 103 | // could be passed; maybe for next refactoring 104 | const newActions = exchangeCenterTitleBar(doc); 105 | this.plugin.addCenterTitleBarButtons(doc); 106 | } 107 | // all buttons removed 108 | } else if (this.plugin.settings.titleCenter.length === 0) { 109 | for (let i = 0; i < this.plugin.windows.length; i++) { 110 | restoreCenterTitlebar( 111 | this.plugin.titlebarText[i], 112 | this.plugin.windows[i] 113 | ); 114 | } 115 | // button was there before or at least one is left 116 | } else { 117 | for (let i = 0; i < this.plugin.windows.length; i++) { 118 | removeCenterTitleBarButtons(this.plugin.windows[i]); 119 | this.plugin.addCenterTitleBarButtons( 120 | this.plugin.windows[i] 121 | ); 122 | } 123 | } 124 | } 125 | setTimeout(() => { 126 | dispatchEvent(new Event('TopBar-addedCommand')); 127 | }, 100); 128 | } else { 129 | new IconPicker(this.plugin, item, this.type).open(); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/ui/iconPicker.ts: -------------------------------------------------------------------------------- 1 | // Thank you: https://github.com/phibr0/obsidian-customizable-sidebar/blob/50099ff41b17758b20f52bfd9a80abf8319c29fb/src/ui/iconPicker.ts 2 | 3 | import { 4 | FuzzySuggestModal, 5 | Command, 6 | FuzzyMatch, 7 | setIcon, 8 | Notice, 9 | } from 'obsidian'; 10 | import type TopBarButtonsPlugin from '../main'; 11 | import type { Buttons, TitleOrPage } from 'src/interfaces'; 12 | import { 13 | exchangeCenterTitleBar, 14 | getTitlebarText, 15 | removeCenterTitleBarButtons, 16 | removeLeftTitleBarButtons, 17 | removeRightTitleBarButtons, 18 | removeTitlebarText, 19 | restoreCenterTitlebar, 20 | } from '../utils'; 21 | 22 | export default class IconPicker extends FuzzySuggestModal { 23 | plugin: TopBarButtonsPlugin; 24 | command: Command; 25 | type: TitleOrPage; 26 | index: number; 27 | 28 | constructor( 29 | plugin: TopBarButtonsPlugin, 30 | command: Command, 31 | type: TitleOrPage, 32 | index = -1 33 | ) { 34 | super(plugin.app); 35 | this.plugin = plugin; 36 | this.command = command; 37 | this.type = type; 38 | this.index = index; 39 | this.setPlaceholder('Please pick an icon'); 40 | } 41 | 42 | private cap(string: string): string { 43 | const words = string.split(' '); 44 | 45 | return words 46 | .map((word) => { 47 | return word[0].toUpperCase() + word.substring(1); 48 | }) 49 | .join(' '); 50 | } 51 | 52 | getItems(): string[] { 53 | return this.plugin.iconList; 54 | } 55 | 56 | getItemText(item: string): string { 57 | return this.cap(item.replace('lucide-', '').replace(/-/gi, ' ')); 58 | } 59 | 60 | renderSuggestion(item: FuzzyMatch, el: HTMLElement): void { 61 | el.addClass('CS-icon-container'); 62 | const div = el.createDiv({ cls: 'CS-icon' }); 63 | //el.appendChild(div); 64 | setIcon(div, item.item); 65 | super.renderSuggestion(item, el); 66 | } 67 | 68 | async onChooseItem(item: string): Promise { 69 | const { id, name } = this.command; 70 | const { settings } = this.plugin; 71 | let centerCounter = 0; 72 | // page header button 73 | if (this.type === 'page') { 74 | let showOnPlatform: Buttons = 'both'; 75 | if (this.index === -1) { 76 | showOnPlatform = 'both'; 77 | } else { 78 | showOnPlatform = 79 | settings.enabledButtons[this.index].showButtons; 80 | } 81 | const settingsObject = { 82 | id: id, 83 | icon: item, 84 | name: name, 85 | showButtons: showOnPlatform, 86 | }; 87 | if (this.index === -1) { 88 | settings.enabledButtons.push(settingsObject); 89 | } else { 90 | settings.enabledButtons[this.index] = settingsObject; 91 | new Notice('This change will take effect for new panes only.'); 92 | } 93 | // title bar button 94 | } else { 95 | const settingsObject = { 96 | id: id, 97 | icon: item, 98 | name: name, 99 | }; 100 | // a button is added at the end, `-1` is the default 101 | if (this.index === -1) { 102 | if (this.type === 'title-left') { 103 | settings.titleLeft.push(settingsObject); 104 | } else if (this.type === 'title-right') { 105 | settings.titleRight.push(settingsObject); 106 | } else { 107 | const length = settings.titleCenter.length; 108 | if (length !== 0) { 109 | centerCounter = length; 110 | } 111 | settings.titleCenter.push(settingsObject); 112 | } 113 | // an icon is changed from within the settings 114 | } else { 115 | if (this.type === 'title-left') { 116 | settings.titleLeft[this.index] = settingsObject; 117 | } else if (this.type === 'title-right') { 118 | settings.titleRight[this.index] = settingsObject; 119 | } else { 120 | const length = settings.titleCenter.length; 121 | if (length !== 0) { 122 | centerCounter = length; 123 | } 124 | settings.titleCenter[this.index] = settingsObject; 125 | } 126 | } 127 | } 128 | 129 | await this.plugin.saveSettings(); 130 | 131 | if (this.type === 'title-left') { 132 | for (let i = 0; i < this.plugin.windows.length; i++) { 133 | removeLeftTitleBarButtons(this.plugin.windows[i]); 134 | this.plugin.addLeftTitleBarButtons(this.plugin.windows[i]); 135 | } 136 | } else if (this.type === 'title-right') { 137 | for (let i = 0; i < this.plugin.windows.length; i++) { 138 | removeRightTitleBarButtons(this.plugin.windows[i]); 139 | this.plugin.addRightTitleBarButtons(this.plugin.windows[i]); 140 | } 141 | } else { 142 | // initial button is added 143 | if (centerCounter === 0) { 144 | this.plugin.titlebarText = []; 145 | for (let i = 0; i < this.plugin.windows.length; i++) { 146 | const doc = this.plugin.windows[i]; 147 | this.plugin.titlebarText.push(getTitlebarText(doc)); 148 | removeTitlebarText(doc); 149 | // could be passed; maybe for next refactoring 150 | const newActions = exchangeCenterTitleBar(doc); 151 | this.plugin.addCenterTitleBarButtons(doc); 152 | } 153 | } else if (settings.titleCenter.length === 0) { 154 | for (let i = 0; i < this.plugin.windows.length; i++) { 155 | restoreCenterTitlebar( 156 | this.plugin.titlebarText[i], 157 | this.plugin.windows[i] 158 | ); 159 | } 160 | } else { 161 | for (let i = 0; i < this.plugin.windows.length; i++) { 162 | removeCenterTitleBarButtons(this.plugin.windows[i]); 163 | this.plugin.addCenterTitleBarButtons( 164 | this.plugin.windows[i] 165 | ); 166 | } 167 | } 168 | } 169 | 170 | setTimeout(() => { 171 | dispatchEvent(new Event('TopBar-addedCommand')); 172 | }, 100); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Platform, setIcon } from 'obsidian'; 2 | import { 3 | PLUGIN_CLASS_NAME, 4 | TITLEBAR_CENTER, 5 | TITLEBAR_CLASS, 6 | } from './constants'; 7 | 8 | // General purpose utility functions 9 | 10 | function getTooltip(name: string) { 11 | if (name.includes(':')) { 12 | return name.split(':')[1].trim(); 13 | } else { 14 | return name; 15 | } 16 | } 17 | 18 | export function getIconSize() { 19 | let iconSize = 24; 20 | if (Platform.isMobile) { 21 | iconSize = 24; 22 | } else if (Platform.isDesktop) { 23 | iconSize = 18; 24 | } 25 | return iconSize; 26 | } 27 | 28 | /** 29 | * 30 | * @param name - Full command name, needs to be cleaned up 31 | * @param id - Command id 32 | * @param icon - String of the assigned icon 33 | * @param iconSize - Icon size, relevant for either mobile/desktop or the titlebar 34 | * @param classes - The CSS classes which are assigned so that the native Obsidian styling can be applied 35 | * @param tag - 'a' is the HTML tag for the page header buttons, 'div' for the titlebar buttons 36 | */ 37 | export function getButtonIcon( 38 | name: string, 39 | id: string, 40 | icon: string, 41 | iconSize: number, 42 | classes: string[], 43 | tag: 'a' | 'div' = 'a' 44 | ) { 45 | const tooltip = getTooltip(name); 46 | const buttonClasses = classes.concat([id]); 47 | 48 | const buttonIcon = createEl(tag, { 49 | cls: buttonClasses, 50 | attr: { 'aria-label-position': 'bottom', 'aria-label': tooltip }, 51 | }); 52 | setIcon(buttonIcon, icon, iconSize); 53 | return buttonIcon; 54 | } 55 | 56 | export function removeElements(element: HTMLCollectionOf): void { 57 | for (let i = element.length; i >= 0; i--) { 58 | if (element[i]) { 59 | element[i].remove(); 60 | } 61 | } 62 | } 63 | 64 | export function removeSingleButton( 65 | htmlElement: Element, 66 | buttonId: string, 67 | className: string 68 | ) { 69 | const element = htmlElement.getElementsByClassName( 70 | `${className} ${PLUGIN_CLASS_NAME} ${buttonId}` 71 | ); 72 | if (element[0]) { 73 | element[0].remove(); 74 | } 75 | } 76 | 77 | // Center title bar utility functions 78 | 79 | export function getTitlebarText(doc: Document) { 80 | return doc.getElementsByClassName('titlebar-text')[0]; 81 | } 82 | 83 | export function removeTitlebarText(doc: Document) { 84 | const titlebarText = doc.getElementsByClassName('titlebar-text')[0]; 85 | if (titlebarText) { 86 | titlebarText.detach(); 87 | } 88 | } 89 | 90 | export function restoreCenterTitlebar(titlebarText: Element, doc: Document) { 91 | if (doc.body.classList.contains('is-hidden-frameless')) return; 92 | const centerTitlebar = doc.getElementsByClassName( 93 | `${PLUGIN_CLASS_NAME} ${TITLEBAR_CENTER}` 94 | )[0]; 95 | // needed for ununload if no center buttons are defined 96 | if (centerTitlebar) { 97 | const inner = centerTitlebar.parentElement; 98 | centerTitlebar.detach(); 99 | if (inner) { 100 | inner.insertBefore(titlebarText, inner.children[0]); 101 | } 102 | } 103 | } 104 | 105 | export function removeCenterTitleBarButtons(doc: Document) { 106 | if (doc.body.classList.contains('is-hidden-frameless')) return; 107 | const centerTitlebar = getCenterTitleBar(doc); 108 | if (!centerTitlebar) return; 109 | const buttons = centerTitlebar.getElementsByClassName( 110 | `${PLUGIN_CLASS_NAME} ${TITLEBAR_CLASS}` 111 | ); 112 | removeElements(buttons); 113 | } 114 | 115 | export function exchangeCenterTitleBar(doc: Document): Element | undefined { 116 | if (doc.body.classList.contains('is-hidden-frameless')) return; 117 | const centerTitleBar = createDiv({ 118 | cls: [PLUGIN_CLASS_NAME, 'titlebar-center'], 119 | }); 120 | const parent = doc.getElementsByClassName('titlebar-inner')[0]; 121 | if (parent) { 122 | parent.insertBefore(centerTitleBar, parent.children[0]); 123 | } 124 | return centerTitleBar; 125 | } 126 | 127 | // Page header utility functions 128 | 129 | export function removeAllPageHeaderButtons() { 130 | /* 131 | const activeLeaves = document.getElementsByClassName( 132 | 'workspace-leaf-content' 133 | ); 134 | */ 135 | const activeLeaves: HTMLElement[] = []; 136 | app.workspace.iterateAllLeaves((leaf) => { 137 | activeLeaves.push(leaf.view.containerEl); 138 | }); 139 | for (let i = 0; i < activeLeaves.length; i++) { 140 | const leaf = activeLeaves[i]; 141 | const element = leaf.getElementsByClassName(PLUGIN_CLASS_NAME); 142 | if (element.length > 0) { 143 | removeElements(element); 144 | } 145 | } 146 | } 147 | 148 | export function removePageHeaderButton(buttonId: string) { 149 | /* 150 | const activeLeaves = document.getElementsByClassName( 151 | 'workspace-leaf-content' 152 | ); 153 | */ 154 | const activeLeaves: HTMLElement[] = []; 155 | app.workspace.iterateAllLeaves((leaf) => { 156 | activeLeaves.push(leaf.view.containerEl); 157 | }); 158 | for (let i = 0; i < activeLeaves.length; i++) { 159 | const leaf = activeLeaves[i]; 160 | removeSingleButton(leaf, buttonId, 'view-action'); 161 | } 162 | } 163 | 164 | // Left and right title bar utility functions 165 | 166 | export function removeAllTitleBarButtons(doc: Document) { 167 | if (doc.body.classList.contains('is-hidden-frameless')) return; 168 | removeLeftTitleBarButtons(doc); 169 | removeRightTitleBarButtons(doc); 170 | } 171 | 172 | // remove all left and right title bar buttons 173 | 174 | export function removeLeftTitleBarButtons(doc: Document) { 175 | if (doc.body.classList.contains('is-hidden-frameless')) return; 176 | const leftContainer = getLeftTitleBar(doc); 177 | if (!leftContainer) return; 178 | const leftElements = 179 | leftContainer.getElementsByClassName(PLUGIN_CLASS_NAME); 180 | if (leftElements.length > 0) { 181 | removeElements(leftElements); 182 | } 183 | } 184 | 185 | export function removeRightTitleBarButtons(doc: Document) { 186 | if (doc.body.classList.contains('is-hidden-frameless')) return; 187 | const rightContainer = getRightTitleBar(doc); 188 | if (!rightContainer) return; 189 | const rightElements = 190 | rightContainer.getElementsByClassName(PLUGIN_CLASS_NAME); 191 | if (rightElements.length > 0) { 192 | removeElements(rightElements); 193 | } 194 | } 195 | 196 | // remove single title bar button 197 | 198 | export function removeLeftTitleBarButton(buttonId: string, doc: Document) { 199 | if (doc.body.classList.contains('is-hidden-frameless')) return; 200 | const leftContainer = getLeftTitleBar(doc); 201 | if (!leftContainer) return; 202 | removeSingleButton(leftContainer, buttonId, TITLEBAR_CLASS); 203 | } 204 | 205 | export function removeRightTitleBarButton(buttonId: string, doc: Document) { 206 | if (doc.body.classList.contains('is-hidden-frameless')) return; 207 | const rightContainer = getRightTitleBar(doc); 208 | if (!rightContainer) return; 209 | removeSingleButton(rightContainer, buttonId, TITLEBAR_CLASS); 210 | } 211 | 212 | export function removeCenterTitleBarButton(buttonId: string, doc: Document) { 213 | if (doc.body.classList.contains('is-hidden-frameless')) return; 214 | const centerContainer = getCenterTitleBar(doc); 215 | if (!centerContainer) return; 216 | removeSingleButton(centerContainer, buttonId, TITLEBAR_CLASS); 217 | } 218 | 219 | // get HTML Elements 220 | 221 | export function getLeftTitleBar(doc: Document): Element { 222 | return doc.getElementsByClassName('titlebar-button-container mod-left')[0]; 223 | } 224 | 225 | export function getRightTitleBar(doc: Document): Element { 226 | return doc.getElementsByClassName('titlebar-button-container mod-right')[0]; 227 | } 228 | 229 | export function getCenterTitleBar(doc: Document): Element { 230 | return doc.getElementsByClassName( 231 | `${PLUGIN_CLASS_NAME} ${TITLEBAR_CENTER}` 232 | )[0]; 233 | } 234 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "importsNotUsedAsValues":"error", 13 | "strict": true, 14 | "lib": [ 15 | "dom", 16 | "scripthost", 17 | "es6", 18 | ] 19 | }, 20 | "include": [ 21 | "src/" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.1": "0.12.19", 3 | "3.1.2": "0.12.19", 4 | "4.0.0": "0.13.31", 5 | "4.5.1": "0.14.15", 6 | "4.5.2": "0.15.2", 7 | "4.6.0": "0.15.4", 8 | "4.6.2": "0.15.4", 9 | "4.6.3": "0.16.0" 10 | } 11 | --------------------------------------------------------------------------------