├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── esbuild.config.mjs ├── main.css ├── main.ts ├── manifest.json ├── package-lock.json ├── package.json ├── settings └── settings.ts ├── styles.css ├── suggests ├── action-suggest.ts └── suggest.ts ├── tsconfig.json ├── utils ├── Action.ts ├── ToolBarBuilder.ts └── Utils.ts ├── version-bump.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | PLUGIN_NAME: obsidian-text-toolbar # Change this to match the id of your plugin. 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '16.x' 21 | 22 | - name: Build 23 | id: build 24 | run: | 25 | npm install 26 | npm run build 27 | mkdir ${{ env.PLUGIN_NAME }} 28 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} 29 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 30 | ls 31 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 32 | 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | VERSION: ${{ github.ref }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | 45 | - name: Upload zip file 46 | id: upload-zip 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 54 | asset_content_type: application/zip 55 | 56 | - name: Upload main.js 57 | id: upload-main 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} 63 | asset_path: ./main.js 64 | asset_name: main.js 65 | asset_content_type: text/javascript 66 | 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: ./manifest.json 75 | asset_name: manifest.json 76 | asset_content_type: application/json 77 | 78 | - name: Upload styles.css 79 | id: upload-css 80 | uses: actions/upload-release-asset@v1 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | with: 84 | upload_url: ${{ steps.create_release.outputs.upload_url }} 85 | asset_path: ./styles.css 86 | asset_name: styles.css 87 | asset_content_type: text/css 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 faru 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 | ## Obsidian Text Toolbar Plugin 2 | 3 | A customizable toolbar that allows CSS snippets, markdowns, and built-in macros to be applied to selected text. 4 | 5 | ![00001](https://user-images.githubusercontent.com/33874906/160287593-ad28ef68-d8d7-410d-806f-d22d2dbce46b.gif) 6 | 7 | 8 | ## How to use 9 | 10 | To begin, register your required actions in the toolbar. 11 | 12 | Selecting text in the editor view will show your customized toolbar. 13 | 14 | Click the button to apply CSS snippets, markdowns, built-in macros, etc. to the selected text. 15 | 16 | Selecting multiple texts with the Alt and Shift keys allows you to apply the same action at once. 17 | 18 | --- 19 | ### Key and mouse operation 20 | 21 | Tab key: Focus on toolbar or next button 22 | 23 | Shift+Tab: Focus on previous button 24 | 25 | Cursor key: Move button focus 26 | 27 | Esc key: Hide toolbar 28 | 29 | --- 30 | ## Settings 31 | 32 | ### Toolbar Structure 33 | 34 | Three different toolbar structures can be configured for different Obsidian window sizes. 35 | 36 | The toolbar structure, such as the number of buttons, rows, etc., can be configured according to the screen, such as desktop, tablet, or cell phone. 37 | 38 | ### Toolbar Appearance 39 | 40 | Toolbar and button colors, borders, opacity, etc. can be set. 41 | 42 | --- 43 | ## Action Setting 44 | ![Obsidian_HZWECBnz5C](https://user-images.githubusercontent.com/33874906/160280018-efa9b224-2098-4ff5-92ee-10ffea339a97.png) 45 | 46 | Each action is added to the toolbar in turn as a single button. 47 | 48 | ### Action: 49 | 50 | There are three types of actions. 51 | 52 | 1. Actions with the prefix 'TAG' add HTML tags. 53 | 54 | You can apply styles to text by combining it with your CSS snippets. 55 | ![02](https://user-images.githubusercontent.com/33874906/160281089-9ceec2a7-235f-4280-acb4-28811faefa3c.gif) 56 | 57 | Tag Example: 58 | 59 | span: `selected text` 60 | 61 | div*2: `
selected text
` 62 | 63 | div span /%s/: `
selected text
` 64 | 65 | 2. Actions with the prefix 'MD' add Markdown elements. 66 | ![03](https://user-images.githubusercontent.com/33874906/160281579-b927c374-1e2e-4a91-a24e-c95226b42504.gif) 67 | 68 | 69 | Some actions add Markdown elements and HTML tags at the same time. 70 | 71 | 3. Actions with the prefix 'MAC' execute built-in Macros. 72 | ![04](https://user-images.githubusercontent.com/33874906/160282147-b17aae3d-8388-423e-bc97-7634843c14f5.gif) 73 | --- 74 | ### Label: 75 | 76 | The label that appears on the button. 77 | 78 | If the CSS class is set, a simplified style is applied to the label. 79 | 80 | Font size is reduced to fit the button, so fewer characters on the label are preferred. 81 | 82 | Emoji and symbols can be used. 83 | 84 | --- 85 | ### Class: 86 | 87 | CSS class to be applied to HTML tags. 88 | 89 | It must be registered in your CSS snippet. 90 | 91 | This is not required for Markdowns or Macros, so it can be left blank. 92 | 93 | --- 94 | ## Attribution 95 | suggest.ts is the copyrighted work of Liam Cain (https://github.com/liamcain) obsidian-periodic-notes (https://github.com/liamcain/obsidian-periodic-notes). 96 | 97 | popper.js https://popper.js.org/ 98 | 99 | Dummy text from https://www.blindtextgenerator.com/ 100 | 101 | CSS Reference: 102 | - https://baigie.me/officialblog/2021/02/25/css-tips-1/ 103 | - https://saruwakakun.com/html-css/reference/buttons 104 | - https://0edition.net/archives/1448 105 | - https://jajaaan.co.jp/css/css-headline/ 106 | 107 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/closebrackets', 25 | '@codemirror/collab', 26 | '@codemirror/commands', 27 | '@codemirror/comment', 28 | '@codemirror/fold', 29 | '@codemirror/gutter', 30 | '@codemirror/highlight', 31 | '@codemirror/history', 32 | '@codemirror/language', 33 | '@codemirror/lint', 34 | '@codemirror/matchbrackets', 35 | '@codemirror/panel', 36 | '@codemirror/rangeset', 37 | '@codemirror/rectangular-selection', 38 | '@codemirror/search', 39 | '@codemirror/state', 40 | '@codemirror/stream-parser', 41 | '@codemirror/text', 42 | '@codemirror/tooltip', 43 | '@codemirror/view', 44 | ...builtins], 45 | format: 'cjs', 46 | watch: !prod, 47 | target: 'es2016', 48 | logLevel: "info", 49 | sourcemap: prod ? false : 'inline', 50 | treeShaking: true, 51 | outfile: 'main.js', 52 | }).catch(() => process.exit(1)); 53 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | /* node_modules/@melloware/coloris/dist/coloris.css */ 2 | .clr-picker { 3 | display: none; 4 | flex-wrap: wrap; 5 | position: absolute; 6 | width: 200px; 7 | z-index: 1000; 8 | border-radius: 10px; 9 | background-color: #fff; 10 | justify-content: space-between; 11 | box-shadow: 0 0 5px rgba(0, 0, 0, .05), 0 5px 20px rgba(0, 0, 0, .1); 12 | -moz-user-select: none; 13 | -webkit-user-select: none; 14 | user-select: none; 15 | } 16 | .clr-picker.clr-open { 17 | display: flex; 18 | } 19 | .clr-gradient { 20 | position: relative; 21 | width: 100%; 22 | height: 100px; 23 | margin-bottom: 15px; 24 | border-radius: 3px 3px 0 0; 25 | background-image: linear-gradient(rgba(0, 0, 0, 0), #000), linear-gradient(90deg, #fff, currentColor); 26 | cursor: pointer; 27 | } 28 | .clr-marker { 29 | position: absolute; 30 | width: 12px; 31 | height: 12px; 32 | margin: -6px 0 0 -6px; 33 | border: 1px solid #fff; 34 | border-radius: 50%; 35 | background-color: currentColor; 36 | cursor: pointer; 37 | } 38 | .clr-picker input[type=range]::-webkit-slider-runnable-track { 39 | width: 100%; 40 | height: 8px; 41 | } 42 | .clr-picker input[type=range]::-webkit-slider-thumb { 43 | width: 8px; 44 | height: 8px; 45 | -webkit-appearance: none; 46 | } 47 | .clr-picker input[type=range]::-moz-range-track { 48 | width: 100%; 49 | height: 8px; 50 | border: 0; 51 | } 52 | .clr-picker input[type=range]::-moz-range-thumb { 53 | width: 8px; 54 | height: 8px; 55 | border: 0; 56 | } 57 | .clr-hue { 58 | background-image: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%); 59 | } 60 | .clr-hue, 61 | .clr-alpha { 62 | position: relative; 63 | width: calc(100% - 40px); 64 | height: 8px; 65 | margin: 5px 20px; 66 | border-radius: 4px; 67 | } 68 | .clr-alpha span { 69 | display: block; 70 | height: 100%; 71 | width: 100%; 72 | border-radius: inherit; 73 | background-image: linear-gradient(90deg, rgba(0, 0, 0, 0), currentColor); 74 | } 75 | .clr-hue input, 76 | .clr-alpha input { 77 | position: absolute; 78 | width: calc(100% + 16px); 79 | height: 16px; 80 | left: -8px; 81 | top: -4px; 82 | margin: 0; 83 | background-color: transparent; 84 | opacity: 0; 85 | cursor: pointer; 86 | appearance: none; 87 | -webkit-appearance: none; 88 | } 89 | .clr-hue div, 90 | .clr-alpha div { 91 | position: absolute; 92 | width: 16px; 93 | height: 16px; 94 | left: 0; 95 | top: 50%; 96 | margin-left: -8px; 97 | transform: translateY(-50%); 98 | border: 2px solid #fff; 99 | border-radius: 50%; 100 | background-color: currentColor; 101 | box-shadow: 0 0 1px #888; 102 | pointer-events: none; 103 | } 104 | .clr-alpha div:before { 105 | content: ""; 106 | position: absolute; 107 | height: 100%; 108 | width: 100%; 109 | left: 0; 110 | top: 0; 111 | border-radius: 50%; 112 | background-color: currentColor; 113 | } 114 | .clr-format { 115 | display: none; 116 | order: 1; 117 | width: calc(100% - 40px); 118 | margin: 0 20px 20px; 119 | } 120 | .clr-segmented { 121 | display: flex; 122 | position: relative; 123 | width: 100%; 124 | margin: 0; 125 | padding: 0; 126 | border: 1px solid #ddd; 127 | border-radius: 15px; 128 | box-sizing: border-box; 129 | color: #999; 130 | font-size: 12px; 131 | } 132 | .clr-segmented input, 133 | .clr-segmented legend { 134 | position: absolute; 135 | width: 100%; 136 | height: 100%; 137 | margin: 0; 138 | padding: 0; 139 | border: 0; 140 | left: 0; 141 | top: 0; 142 | opacity: 0; 143 | pointer-events: none; 144 | } 145 | .clr-segmented label { 146 | flex-grow: 1; 147 | padding: 4px 0; 148 | text-align: center; 149 | cursor: pointer; 150 | } 151 | .clr-segmented label:first-of-type { 152 | border-radius: 10px 0 0 10px; 153 | } 154 | .clr-segmented label:last-of-type { 155 | border-radius: 0 10px 10px 0; 156 | } 157 | .clr-segmented input:checked + label { 158 | color: #fff; 159 | background-color: #666; 160 | } 161 | .clr-swatches { 162 | order: 2; 163 | width: calc(100% - 32px); 164 | margin: 0 16px; 165 | } 166 | .clr-swatches div { 167 | display: flex; 168 | flex-wrap: wrap; 169 | padding-bottom: 12px; 170 | justify-content: center; 171 | } 172 | .clr-swatches button { 173 | position: relative; 174 | width: 20px; 175 | height: 20px; 176 | margin: 0 4px 6px 4px; 177 | border: 0; 178 | border-radius: 50%; 179 | color: inherit; 180 | text-indent: -1000px; 181 | white-space: nowrap; 182 | overflow: hidden; 183 | cursor: pointer; 184 | } 185 | .clr-swatches button:after { 186 | content: ""; 187 | display: block; 188 | position: absolute; 189 | width: 100%; 190 | height: 100%; 191 | left: 0; 192 | top: 0; 193 | border-radius: inherit; 194 | background-color: currentColor; 195 | box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1); 196 | } 197 | input.clr-color { 198 | order: 1; 199 | width: calc(100% - 80px); 200 | height: 32px; 201 | margin: 15px 20px 20px 0; 202 | padding: 0 10px; 203 | border: 1px solid #ddd; 204 | border-radius: 16px; 205 | color: #444; 206 | background-color: #fff; 207 | font-family: sans-serif; 208 | font-size: 14px; 209 | text-align: center; 210 | box-shadow: none; 211 | } 212 | input.clr-color:focus { 213 | outline: none; 214 | border: 1px solid #1e90ff; 215 | } 216 | .clr-clear { 217 | display: none; 218 | order: 2; 219 | height: 24px; 220 | margin: 0 20px 20px auto; 221 | padding: 0 20px; 222 | border: 0; 223 | border-radius: 12px; 224 | color: #fff; 225 | background-color: #666; 226 | font-family: inherit; 227 | font-size: 12px; 228 | font-weight: 400; 229 | cursor: pointer; 230 | } 231 | .clr-preview { 232 | position: relative; 233 | width: 32px; 234 | height: 32px; 235 | margin: 15px 0 20px 20px; 236 | border: 0; 237 | border-radius: 50%; 238 | overflow: hidden; 239 | cursor: pointer; 240 | } 241 | .clr-preview:before, 242 | .clr-preview:after { 243 | content: ""; 244 | position: absolute; 245 | height: 100%; 246 | width: 100%; 247 | left: 0; 248 | top: 0; 249 | border: 1px solid #fff; 250 | border-radius: 50%; 251 | } 252 | .clr-preview:after { 253 | border: 0; 254 | background-color: currentColor; 255 | box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1); 256 | } 257 | .clr-marker, 258 | .clr-hue div, 259 | .clr-alpha div, 260 | .clr-color { 261 | box-sizing: border-box; 262 | } 263 | .clr-field { 264 | display: inline-block; 265 | position: relative; 266 | color: transparent; 267 | } 268 | .clr-field button { 269 | position: absolute; 270 | width: 30px; 271 | height: 100%; 272 | right: 0; 273 | top: 50%; 274 | transform: translateY(-50%); 275 | border: 0; 276 | color: inherit; 277 | text-indent: -1000px; 278 | white-space: nowrap; 279 | overflow: hidden; 280 | pointer-events: none; 281 | } 282 | .clr-field button:after { 283 | content: ""; 284 | display: block; 285 | position: absolute; 286 | width: 100%; 287 | height: 100%; 288 | left: 0; 289 | top: 0; 290 | border-radius: inherit; 291 | background-color: currentColor; 292 | box-shadow: inset 0 0 1px rgba(0, 0, 0, .5); 293 | } 294 | .clr-alpha, 295 | .clr-alpha div, 296 | .clr-swatches button, 297 | .clr-preview:before, 298 | .clr-field button { 299 | background-image: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); 300 | background-position: 0 0, 4px 4px; 301 | background-size: 8px 8px; 302 | } 303 | .clr-marker:focus { 304 | outline: none; 305 | } 306 | .clr-keyboard-nav .clr-marker:focus, 307 | .clr-keyboard-nav .clr-hue input:focus + div, 308 | .clr-keyboard-nav .clr-alpha input:focus + div, 309 | .clr-keyboard-nav .clr-segmented input:focus + label { 310 | outline: none; 311 | box-shadow: 0 0 0 2px #1e90ff, 0 0 2px 2px #fff; 312 | } 313 | .clr-picker[data-alpha=false] .clr-alpha { 314 | display: none; 315 | } 316 | .clr-picker[data-minimal=true] { 317 | padding-top: 16px; 318 | } 319 | .clr-picker[data-minimal=true] .clr-gradient, 320 | .clr-picker[data-minimal=true] .clr-hue, 321 | .clr-picker[data-minimal=true] .clr-alpha, 322 | .clr-picker[data-minimal=true] .clr-color, 323 | .clr-picker[data-minimal=true] .clr-preview { 324 | display: none; 325 | } 326 | .clr-dark { 327 | background-color: #444; 328 | } 329 | .clr-dark .clr-segmented { 330 | border-color: #777; 331 | } 332 | .clr-dark .clr-swatches button:after { 333 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3); 334 | } 335 | .clr-dark input.clr-color { 336 | color: #fff; 337 | border-color: #777; 338 | background-color: #555; 339 | } 340 | .clr-dark input.clr-color:focus { 341 | border-color: #1e90ff; 342 | } 343 | .clr-dark .clr-preview:after { 344 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .5); 345 | } 346 | .clr-dark .clr-alpha, 347 | .clr-dark .clr-alpha div, 348 | .clr-dark .clr-swatches button, 349 | .clr-dark .clr-preview:before { 350 | background-image: repeating-linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #888 75%, #888), repeating-linear-gradient(45deg, #888 25%, #444 25%, #444 75%, #888 75%, #888); 351 | } 352 | .clr-picker.clr-polaroid { 353 | border-radius: 6px; 354 | box-shadow: 0 0 5px rgba(0, 0, 0, .1), 0 5px 30px rgba(0, 0, 0, .2); 355 | } 356 | .clr-picker.clr-polaroid:before { 357 | content: ""; 358 | display: block; 359 | position: absolute; 360 | width: 16px; 361 | height: 10px; 362 | left: 20px; 363 | top: -10px; 364 | border: solid transparent; 365 | border-width: 0 8px 10px 8px; 366 | border-bottom-color: currentColor; 367 | box-sizing: border-box; 368 | color: #fff; 369 | filter: drop-shadow(0 -4px 3px rgba(0, 0, 0, .1)); 370 | pointer-events: none; 371 | } 372 | .clr-picker.clr-polaroid.clr-dark:before { 373 | color: #444; 374 | } 375 | .clr-picker.clr-polaroid.clr-left:before { 376 | left: auto; 377 | right: 20px; 378 | } 379 | .clr-picker.clr-polaroid.clr-top:before { 380 | top: auto; 381 | bottom: -10px; 382 | transform: rotateZ(180deg); 383 | } 384 | .clr-polaroid .clr-gradient { 385 | width: calc(100% - 20px); 386 | height: 120px; 387 | margin: 10px; 388 | border-radius: 3px; 389 | } 390 | .clr-polaroid .clr-hue, 391 | .clr-polaroid .clr-alpha { 392 | width: calc(100% - 30px); 393 | height: 10px; 394 | margin: 6px 15px; 395 | border-radius: 5px; 396 | } 397 | .clr-polaroid .clr-hue div, 398 | .clr-polaroid .clr-alpha div { 399 | box-shadow: 0 0 5px rgba(0, 0, 0, .2); 400 | } 401 | .clr-polaroid .clr-format { 402 | width: calc(100% - 20px); 403 | margin: 0 10px 15px; 404 | } 405 | .clr-polaroid .clr-swatches { 406 | width: calc(100% - 12px); 407 | margin: 0 6px; 408 | } 409 | .clr-polaroid .clr-swatches div { 410 | padding-bottom: 10px; 411 | } 412 | .clr-polaroid .clr-swatches button { 413 | width: 22px; 414 | height: 22px; 415 | } 416 | .clr-polaroid input.clr-color { 417 | width: calc(100% - 60px); 418 | margin: 10px 10px 15px 0; 419 | } 420 | .clr-polaroid .clr-clear { 421 | margin: 0 10px 15px auto; 422 | } 423 | .clr-polaroid .clr-preview { 424 | margin: 10px 0 15px 10px; 425 | } 426 | .clr-picker.clr-large { 427 | width: 275px; 428 | } 429 | .clr-large .clr-gradient { 430 | height: 150px; 431 | } 432 | .clr-large .clr-swatches button { 433 | width: 22px; 434 | height: 22px; 435 | } 436 | /*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibm9kZV9tb2R1bGVzL0BtZWxsb3dhcmUvY29sb3Jpcy9kaXN0L2NvbG9yaXMuY3NzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIuY2xyLXBpY2tlciB7XHJcbiAgZGlzcGxheTogbm9uZTtcclxuICBmbGV4LXdyYXA6IHdyYXA7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHdpZHRoOiAyMDBweDtcclxuICB6LWluZGV4OiAxMDAwO1xyXG4gIGJvcmRlci1yYWRpdXM6IDEwcHg7XHJcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcclxuICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47XHJcbiAgYm94LXNoYWRvdzogMCAwIDVweCByZ2JhKDAsMCwwLC4wNSksIDAgNXB4IDIwcHggcmdiYSgwLDAsMCwuMSk7XHJcbiAgLW1vei11c2VyLXNlbGVjdDogbm9uZTtcclxuICAtd2Via2l0LXVzZXItc2VsZWN0OiBub25lO1xyXG4gIHVzZXItc2VsZWN0OiBub25lO1xyXG59XHJcblxyXG4uY2xyLXBpY2tlci5jbHItb3BlbiB7XHJcbiAgZGlzcGxheTogZmxleDtcclxufVxyXG5cclxuLmNsci1ncmFkaWVudCB7XHJcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xyXG4gIHdpZHRoOiAxMDAlO1xyXG4gIGhlaWdodDogMTAwcHg7XHJcbiAgbWFyZ2luLWJvdHRvbTogMTVweDtcclxuICBib3JkZXItcmFkaXVzOiAzcHggM3B4IDAgMDtcclxuICBiYWNrZ3JvdW5kLWltYWdlOiBsaW5lYXItZ3JhZGllbnQocmdiYSgwLDAsMCwwKSwgIzAwMCksIGxpbmVhci1ncmFkaWVudCg5MGRlZywgI2ZmZiwgY3VycmVudENvbG9yKTtcclxuICBjdXJzb3I6IHBvaW50ZXI7XHJcbn1cclxuXHJcbi5jbHItbWFya2VyIHtcclxuICBwb3NpdGlvbjogYWJzb2x1dGU7XHJcbiAgd2lkdGg6IDEycHg7XHJcbiAgaGVpZ2h0OiAxMnB4O1xyXG4gIG1hcmdpbjogLTZweCAwIDAgLTZweDtcclxuICBib3JkZXI6IDFweCBzb2xpZCAjZmZmO1xyXG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiBjdXJyZW50Q29sb3I7XHJcbiAgY3Vyc29yOiBwb2ludGVyO1xyXG59XHJcblxyXG4uY2xyLXBpY2tlciBpbnB1dFt0eXBlPVwicmFuZ2VcIl06Oi13ZWJraXQtc2xpZGVyLXJ1bm5hYmxlLXRyYWNrIHtcclxuICB3aWR0aDogMTAwJTtcclxuICBoZWlnaHQ6IDhweDtcclxufVxyXG5cclxuLmNsci1waWNrZXIgaW5wdXRbdHlwZT1cInJhbmdlXCJdOjotd2Via2l0LXNsaWRlci10aHVtYiB7XHJcbiAgd2lkdGg6IDhweDtcclxuICBoZWlnaHQ6IDhweDtcclxuICAtd2Via2l0LWFwcGVhcmFuY2U6IG5vbmU7XHJcbn1cclxuXHJcbi5jbHItcGlja2VyIGlucHV0W3R5cGU9XCJyYW5nZVwiXTo6LW1vei1yYW5nZS10cmFjayB7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbiAgaGVpZ2h0OiA4cHg7XHJcbiAgYm9yZGVyOiAwO1xyXG59XHJcblxyXG4uY2xyLXBpY2tlciBpbnB1dFt0eXBlPVwicmFuZ2VcIl06Oi1tb3otcmFuZ2UtdGh1bWIge1xyXG4gIHdpZHRoOiA4cHg7XHJcbiAgaGVpZ2h0OiA4cHg7XHJcbiAgYm9yZGVyOiAwO1xyXG59XHJcblxyXG4uY2xyLWh1ZSB7XHJcbiAgYmFja2dyb3VuZC1pbWFnZTogbGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LCAjZjAwIDAlLCAjZmYwIDE2LjY2JSwgIzBmMCAzMy4zMyUsICMwZmYgNTAlLCAjMDBmIDY2LjY2JSwgI2YwZiA4My4zMyUsICNmMDAgMTAwJSk7XHJcbn1cclxuXHJcbi5jbHItaHVlLFxyXG4uY2xyLWFscGhhIHtcclxuICBwb3NpdGlvbjogcmVsYXRpdmU7XHJcbiAgd2lkdGg6IGNhbGMoMTAwJSAtIDQwcHgpO1xyXG4gIGhlaWdodDogOHB4O1xyXG4gIG1hcmdpbjogNXB4IDIwcHg7XHJcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xyXG59XHJcblxyXG4uY2xyLWFscGhhIHNwYW4ge1xyXG4gIGRpc3BsYXk6IGJsb2NrO1xyXG4gIGhlaWdodDogMTAwJTtcclxuICB3aWR0aDogMTAwJTtcclxuICBib3JkZXItcmFkaXVzOiBpbmhlcml0O1xyXG4gIGJhY2tncm91bmQtaW1hZ2U6IGxpbmVhci1ncmFkaWVudCg5MGRlZywgcmdiYSgwLDAsMCwwKSwgY3VycmVudENvbG9yKTtcclxufVxyXG5cclxuLmNsci1odWUgaW5wdXQsXHJcbi5jbHItYWxwaGEgaW5wdXQge1xyXG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcclxuICB3aWR0aDogY2FsYygxMDAlICsgMTZweCk7XHJcbiAgaGVpZ2h0OiAxNnB4O1xyXG4gIGxlZnQ6IC04cHg7XHJcbiAgdG9wOiAtNHB4O1xyXG4gIG1hcmdpbjogMDtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcclxuICBvcGFjaXR5OiAwO1xyXG4gIGN1cnNvcjogcG9pbnRlcjtcclxuICBhcHBlYXJhbmNlOiBub25lO1xyXG4gIC13ZWJraXQtYXBwZWFyYW5jZTogbm9uZTtcclxufVxyXG5cclxuLmNsci1odWUgZGl2LFxyXG4uY2xyLWFscGhhIGRpdiB7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHdpZHRoOiAxNnB4O1xyXG4gIGhlaWdodDogMTZweDtcclxuICBsZWZ0OiAwO1xyXG4gIHRvcDogNTAlO1xyXG4gIG1hcmdpbi1sZWZ0OiAtOHB4O1xyXG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTAlKTtcclxuICBib3JkZXI6IDJweCBzb2xpZCAjZmZmO1xyXG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiBjdXJyZW50Q29sb3I7XHJcbiAgYm94LXNoYWRvdzogMCAwIDFweCAjODg4O1xyXG4gIHBvaW50ZXItZXZlbnRzOiBub25lO1xyXG59XHJcblxyXG4uY2xyLWFscGhhIGRpdjpiZWZvcmUge1xyXG4gIGNvbnRlbnQ6ICcnO1xyXG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcclxuICBoZWlnaHQ6IDEwMCU7XHJcbiAgd2lkdGg6IDEwMCU7XHJcbiAgbGVmdDogMDtcclxuICB0b3A6IDA7XHJcbiAgYm9yZGVyLXJhZGl1czogNTAlO1xyXG4gIGJhY2tncm91bmQtY29sb3I6IGN1cnJlbnRDb2xvcjtcclxufVxyXG5cclxuLmNsci1mb3JtYXQge1xyXG4gIGRpc3BsYXk6IG5vbmU7XHJcbiAgb3JkZXI6IDE7XHJcbiAgd2lkdGg6IGNhbGMoMTAwJSAtIDQwcHgpO1xyXG4gIG1hcmdpbjogMCAyMHB4IDIwcHg7XHJcbn1cclxuXHJcbi5jbHItc2VnbWVudGVkIHtcclxuICBkaXNwbGF5OiBmbGV4O1xyXG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcclxuICB3aWR0aDogMTAwJTtcclxuICBtYXJnaW46IDA7XHJcbiAgcGFkZGluZzogMDtcclxuICBib3JkZXI6IDFweCBzb2xpZCAjZGRkO1xyXG4gIGJvcmRlci1yYWRpdXM6IDE1cHg7XHJcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDtcclxuICBjb2xvcjogIzk5OTtcclxuICBmb250LXNpemU6IDEycHg7XHJcbn1cclxuXHJcbi5jbHItc2VnbWVudGVkIGlucHV0LFxyXG4uY2xyLXNlZ21lbnRlZCBsZWdlbmQge1xyXG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcclxuICB3aWR0aDogMTAwJTtcclxuICBoZWlnaHQ6IDEwMCU7XHJcbiAgbWFyZ2luOiAwO1xyXG4gIHBhZGRpbmc6IDA7XHJcbiAgYm9yZGVyOiAwO1xyXG4gIGxlZnQ6IDA7XHJcbiAgdG9wOiAwO1xyXG4gIG9wYWNpdHk6IDA7XHJcbiAgcG9pbnRlci1ldmVudHM6IG5vbmU7XHJcbn1cclxuXHJcbi5jbHItc2VnbWVudGVkIGxhYmVsIHtcclxuICBmbGV4LWdyb3c6IDE7XHJcbiAgcGFkZGluZzogNHB4IDA7XHJcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xyXG4gIGN1cnNvcjogcG9pbnRlcjtcclxufVxyXG5cclxuLmNsci1zZWdtZW50ZWQgbGFiZWw6Zmlyc3Qtb2YtdHlwZSB7XHJcbiAgYm9yZGVyLXJhZGl1czogMTBweCAwIDAgMTBweDtcclxufVxyXG5cclxuLmNsci1zZWdtZW50ZWQgbGFiZWw6bGFzdC1vZi10eXBlIHtcclxuICBib3JkZXItcmFkaXVzOiAwIDEwcHggMTBweCAwO1xyXG59XHJcblxyXG4uY2xyLXNlZ21lbnRlZCBpbnB1dDpjaGVja2VkICsgbGFiZWwge1xyXG4gIGNvbG9yOiAjZmZmO1xyXG4gIGJhY2tncm91bmQtY29sb3I6ICM2NjY7XHJcbn1cclxuXHJcbi5jbHItc3dhdGNoZXMge1xyXG4gIG9yZGVyOiAyO1xyXG4gIHdpZHRoOiBjYWxjKDEwMCUgLSAzMnB4KTtcclxuICBtYXJnaW46IDAgMTZweDtcclxufVxyXG5cclxuLmNsci1zd2F0Y2hlcyBkaXYge1xyXG4gIGRpc3BsYXk6IGZsZXg7XHJcbiAgZmxleC13cmFwOiB3cmFwO1xyXG4gIHBhZGRpbmctYm90dG9tOiAxMnB4O1xyXG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xyXG59XHJcblxyXG4uY2xyLXN3YXRjaGVzIGJ1dHRvbiB7XHJcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xyXG4gIHdpZHRoOiAyMHB4O1xyXG4gIGhlaWdodDogMjBweDtcclxuICBtYXJnaW46IDAgNHB4IDZweCA0cHg7XHJcbiAgYm9yZGVyOiAwO1xyXG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcclxuICBjb2xvcjogaW5oZXJpdDtcclxuICB0ZXh0LWluZGVudDogLTEwMDBweDtcclxuICB3aGl0ZS1zcGFjZTogbm93cmFwO1xyXG4gIG92ZXJmbG93OiBoaWRkZW47XHJcbiAgY3Vyc29yOiBwb2ludGVyO1xyXG59XHJcblxyXG4uY2xyLXN3YXRjaGVzIGJ1dHRvbjphZnRlciB7XHJcbiAgY29udGVudDogJyc7XHJcbiAgZGlzcGxheTogYmxvY2s7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHdpZHRoOiAxMDAlO1xyXG4gIGhlaWdodDogMTAwJTtcclxuICBsZWZ0OiAwO1xyXG4gIHRvcDogMDtcclxuICBib3JkZXItcmFkaXVzOiBpbmhlcml0O1xyXG4gIGJhY2tncm91bmQtY29sb3I6IGN1cnJlbnRDb2xvcjtcclxuICBib3gtc2hhZG93OiBpbnNldCAwIDAgMCAxcHggcmdiYSgwLDAsMCwuMSk7XHJcbn1cclxuXHJcbmlucHV0LmNsci1jb2xvciB7XHJcbiAgb3JkZXI6IDE7XHJcbiAgd2lkdGg6IGNhbGMoMTAwJSAtIDgwcHgpO1xyXG4gIGhlaWdodDogMzJweDtcclxuICBtYXJnaW46IDE1cHggMjBweCAyMHB4IDA7XHJcbiAgcGFkZGluZzogMCAxMHB4O1xyXG4gIGJvcmRlcjogMXB4IHNvbGlkICNkZGQ7XHJcbiAgYm9yZGVyLXJhZGl1czogMTZweDtcclxuICBjb2xvcjogIzQ0NDtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmO1xyXG4gIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmO1xyXG4gIGZvbnQtc2l6ZTogMTRweDtcclxuICB0ZXh0LWFsaWduOiBjZW50ZXI7XHJcbiAgYm94LXNoYWRvdzogbm9uZTtcclxufVxyXG5cclxuaW5wdXQuY2xyLWNvbG9yOmZvY3VzIHtcclxuICBvdXRsaW5lOiBub25lO1xyXG4gIGJvcmRlcjogMXB4IHNvbGlkICMxZTkwZmY7XHJcbn1cclxuXHJcbi5jbHItY2xlYXIge1xyXG4gIGRpc3BsYXk6IG5vbmU7XHJcbiAgb3JkZXI6IDI7XHJcbiAgaGVpZ2h0OiAyNHB4O1xyXG4gIG1hcmdpbjogMCAyMHB4IDIwcHggYXV0bztcclxuICBwYWRkaW5nOiAwIDIwcHg7XHJcbiAgYm9yZGVyOiAwO1xyXG4gIGJvcmRlci1yYWRpdXM6IDEycHg7XHJcbiAgY29sb3I6ICNmZmY7XHJcbiAgYmFja2dyb3VuZC1jb2xvcjogIzY2NjtcclxuICBmb250LWZhbWlseTogaW5oZXJpdDtcclxuICBmb250LXNpemU6IDEycHg7XHJcbiAgZm9udC13ZWlnaHQ6IDQwMDtcclxuICBjdXJzb3I6IHBvaW50ZXI7XHJcbn1cclxuXHJcbi5jbHItcHJldmlldyB7XHJcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xyXG4gIHdpZHRoOiAzMnB4O1xyXG4gIGhlaWdodDogMzJweDtcclxuICBtYXJnaW46IDE1cHggMCAyMHB4IDIwcHg7XHJcbiAgYm9yZGVyOiAwO1xyXG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcclxuICBvdmVyZmxvdzogaGlkZGVuO1xyXG4gIGN1cnNvcjogcG9pbnRlcjtcclxufVxyXG5cclxuLmNsci1wcmV2aWV3OmJlZm9yZSxcclxuLmNsci1wcmV2aWV3OmFmdGVyIHtcclxuICBjb250ZW50OiAnJztcclxuICBwb3NpdGlvbjogYWJzb2x1dGU7XHJcbiAgaGVpZ2h0OiAxMDAlO1xyXG4gIHdpZHRoOiAxMDAlO1xyXG4gIGxlZnQ6IDA7XHJcbiAgdG9wOiAwO1xyXG4gIGJvcmRlcjogMXB4IHNvbGlkICNmZmY7XHJcbiAgYm9yZGVyLXJhZGl1czogNTAlO1xyXG59XHJcblxyXG4uY2xyLXByZXZpZXc6YWZ0ZXIge1xyXG4gIGJvcmRlcjogMDtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiBjdXJyZW50Q29sb3I7XHJcbiAgYm94LXNoYWRvdzogaW5zZXQgMCAwIDAgMXB4IHJnYmEoMCwwLDAsLjEpO1xyXG59XHJcblxyXG4uY2xyLW1hcmtlcixcclxuLmNsci1odWUgZGl2LFxyXG4uY2xyLWFscGhhIGRpdixcclxuLmNsci1jb2xvciB7XHJcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDtcclxufVxyXG5cclxuLmNsci1maWVsZCB7XHJcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xyXG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcclxuICBjb2xvcjogdHJhbnNwYXJlbnQ7XHJcbn1cclxuXHJcbi5jbHItZmllbGQgYnV0dG9uIHtcclxuICBwb3NpdGlvbjogYWJzb2x1dGU7XHJcbiAgd2lkdGg6IDMwcHg7XHJcbiAgaGVpZ2h0OiAxMDAlO1xyXG4gIHJpZ2h0OiAwO1xyXG4gIHRvcDogNTAlO1xyXG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTAlKTtcclxuICBib3JkZXI6IDA7XHJcbiAgY29sb3I6IGluaGVyaXQ7XHJcbiAgdGV4dC1pbmRlbnQ6IC0xMDAwcHg7XHJcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcclxuICBvdmVyZmxvdzogaGlkZGVuO1xyXG4gIHBvaW50ZXItZXZlbnRzOiBub25lO1xyXG59XHJcblxyXG4uY2xyLWZpZWxkIGJ1dHRvbjphZnRlciB7XHJcbiAgY29udGVudDogJyc7XHJcbiAgZGlzcGxheTogYmxvY2s7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHdpZHRoOiAxMDAlO1xyXG4gIGhlaWdodDogMTAwJTtcclxuICBsZWZ0OiAwO1xyXG4gIHRvcDogMDtcclxuICBib3JkZXItcmFkaXVzOiBpbmhlcml0O1xyXG4gIGJhY2tncm91bmQtY29sb3I6IGN1cnJlbnRDb2xvcjtcclxuICBib3gtc2hhZG93OiBpbnNldCAwIDAgMXB4IHJnYmEoMCwwLDAsLjUpO1xyXG59XHJcblxyXG4uY2xyLWFscGhhLFxyXG4uY2xyLWFscGhhIGRpdixcclxuLmNsci1zd2F0Y2hlcyBidXR0b24sXHJcbi5jbHItcHJldmlldzpiZWZvcmUsXHJcbi5jbHItZmllbGQgYnV0dG9uIHtcclxuICBiYWNrZ3JvdW5kLWltYWdlOiByZXBlYXRpbmctbGluZWFyLWdyYWRpZW50KDQ1ZGVnLCAjYWFhIDI1JSwgdHJhbnNwYXJlbnQgMjUlLCB0cmFuc3BhcmVudCA3NSUsICNhYWEgNzUlLCAjYWFhKSwgcmVwZWF0aW5nLWxpbmVhci1ncmFkaWVudCg0NWRlZywgI2FhYSAyNSUsICNmZmYgMjUlLCAjZmZmIDc1JSwgI2FhYSA3NSUsICNhYWEpO1xyXG4gIGJhY2tncm91bmQtcG9zaXRpb246IDAgMCwgNHB4IDRweDtcclxuICBiYWNrZ3JvdW5kLXNpemU6IDhweCA4cHg7XHJcbn1cclxuXHJcbi5jbHItbWFya2VyOmZvY3VzIHtcclxuICBvdXRsaW5lOiBub25lO1xyXG59XHJcblxyXG4uY2xyLWtleWJvYXJkLW5hdiAuY2xyLW1hcmtlcjpmb2N1cyxcclxuLmNsci1rZXlib2FyZC1uYXYgLmNsci1odWUgaW5wdXQ6Zm9jdXMgKyBkaXYsXHJcbi5jbHIta2V5Ym9hcmQtbmF2IC5jbHItYWxwaGEgaW5wdXQ6Zm9jdXMgKyBkaXYsXHJcbi5jbHIta2V5Ym9hcmQtbmF2IC5jbHItc2VnbWVudGVkIGlucHV0OmZvY3VzICsgbGFiZWwge1xyXG4gIG91dGxpbmU6IG5vbmU7XHJcbiAgYm94LXNoYWRvdzogMCAwIDAgMnB4ICMxZTkwZmYsIDAgMCAycHggMnB4ICNmZmY7XHJcbn1cclxuXHJcbi5jbHItcGlja2VyW2RhdGEtYWxwaGE9XCJmYWxzZVwiXSAuY2xyLWFscGhhIHtcclxuICBkaXNwbGF5OiBub25lO1xyXG59XHJcblxyXG4uY2xyLXBpY2tlcltkYXRhLW1pbmltYWw9XCJ0cnVlXCJdIHtcclxuICBwYWRkaW5nLXRvcDogMTZweDtcclxufVxyXG5cclxuLmNsci1waWNrZXJbZGF0YS1taW5pbWFsPVwidHJ1ZVwiXSAuY2xyLWdyYWRpZW50LFxyXG4uY2xyLXBpY2tlcltkYXRhLW1pbmltYWw9XCJ0cnVlXCJdIC5jbHItaHVlLFxyXG4uY2xyLXBpY2tlcltkYXRhLW1pbmltYWw9XCJ0cnVlXCJdIC5jbHItYWxwaGEsXHJcbi5jbHItcGlja2VyW2RhdGEtbWluaW1hbD1cInRydWVcIl0gLmNsci1jb2xvcixcclxuLmNsci1waWNrZXJbZGF0YS1taW5pbWFsPVwidHJ1ZVwiXSAuY2xyLXByZXZpZXcge1xyXG4gIGRpc3BsYXk6IG5vbmU7XHJcbn1cclxuXHJcbi8qKiBEYXJrIHRoZW1lICoqL1xyXG5cclxuLmNsci1kYXJrIHtcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiAjNDQ0O1xyXG59XHJcblxyXG4uY2xyLWRhcmsgLmNsci1zZWdtZW50ZWQge1xyXG4gIGJvcmRlci1jb2xvcjogIzc3NztcclxufVxyXG5cclxuLmNsci1kYXJrIC5jbHItc3dhdGNoZXMgYnV0dG9uOmFmdGVyIHtcclxuICBib3gtc2hhZG93OiBpbnNldCAwIDAgMCAxcHggcmdiYSgyNTUsMjU1LDI1NSwuMyk7XHJcbn1cclxuXHJcbi5jbHItZGFyayBpbnB1dC5jbHItY29sb3Ige1xyXG4gIGNvbG9yOiAjZmZmO1xyXG4gIGJvcmRlci1jb2xvcjogIzc3NztcclxuICBiYWNrZ3JvdW5kLWNvbG9yOiAjNTU1O1xyXG59XHJcblxyXG4uY2xyLWRhcmsgaW5wdXQuY2xyLWNvbG9yOmZvY3VzIHtcclxuICBib3JkZXItY29sb3I6ICMxZTkwZmY7XHJcbn1cclxuXHJcbi5jbHItZGFyayAuY2xyLXByZXZpZXc6YWZ0ZXIge1xyXG4gIGJveC1zaGFkb3c6IGluc2V0IDAgMCAwIDFweCByZ2JhKDI1NSwyNTUsMjU1LC41KTtcclxufVxyXG5cclxuLmNsci1kYXJrIC5jbHItYWxwaGEsXHJcbi5jbHItZGFyayAuY2xyLWFscGhhIGRpdixcclxuLmNsci1kYXJrIC5jbHItc3dhdGNoZXMgYnV0dG9uLFxyXG4uY2xyLWRhcmsgLmNsci1wcmV2aWV3OmJlZm9yZSB7XHJcbiAgYmFja2dyb3VuZC1pbWFnZTogcmVwZWF0aW5nLWxpbmVhci1ncmFkaWVudCg0NWRlZywgIzY2NiAyNSUsIHRyYW5zcGFyZW50IDI1JSwgdHJhbnNwYXJlbnQgNzUlLCAjODg4IDc1JSwgIzg4OCksIHJlcGVhdGluZy1saW5lYXItZ3JhZGllbnQoNDVkZWcsICM4ODggMjUlLCAjNDQ0IDI1JSwgIzQ0NCA3NSUsICM4ODggNzUlLCAjODg4KTtcclxufVxyXG5cclxuLyoqIFBvbGFyb2lkIHRoZW1lICoqL1xyXG5cclxuLmNsci1waWNrZXIuY2xyLXBvbGFyb2lkIHtcclxuICBib3JkZXItcmFkaXVzOiA2cHg7XHJcbiAgYm94LXNoYWRvdzogMCAwIDVweCByZ2JhKDAsMCwwLC4xKSwgMCA1cHggMzBweCByZ2JhKDAsMCwwLC4yKTtcclxufVxyXG5cclxuLmNsci1waWNrZXIuY2xyLXBvbGFyb2lkOmJlZm9yZSB7XHJcbiAgY29udGVudDogJyc7XHJcbiAgZGlzcGxheTogYmxvY2s7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHdpZHRoOiAxNnB4O1xyXG4gIGhlaWdodDogMTBweDtcclxuICBsZWZ0OiAyMHB4O1xyXG4gIHRvcDogLTEwcHg7XHJcbiAgYm9yZGVyOiBzb2xpZCB0cmFuc3BhcmVudDtcclxuICBib3JkZXItd2lkdGg6IDAgOHB4IDEwcHggOHB4O1xyXG4gIGJvcmRlci1ib3R0b20tY29sb3I6IGN1cnJlbnRDb2xvcjtcclxuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xyXG4gIGNvbG9yOiAjZmZmO1xyXG4gIGZpbHRlcjogZHJvcC1zaGFkb3coMCAtNHB4IDNweCByZ2JhKDAsMCwwLC4xKSk7XHJcbiAgcG9pbnRlci1ldmVudHM6IG5vbmU7XHJcbn1cclxuXHJcbi5jbHItcGlja2VyLmNsci1wb2xhcm9pZC5jbHItZGFyazpiZWZvcmUge1xyXG4gIGNvbG9yOiAjNDQ0O1xyXG59XHJcblxyXG4uY2xyLXBpY2tlci5jbHItcG9sYXJvaWQuY2xyLWxlZnQ6YmVmb3JlIHtcclxuICBsZWZ0OiBhdXRvO1xyXG4gIHJpZ2h0OiAyMHB4O1xyXG59XHJcblxyXG4uY2xyLXBpY2tlci5jbHItcG9sYXJvaWQuY2xyLXRvcDpiZWZvcmUge1xyXG4gIHRvcDogYXV0bztcclxuICBib3R0b206IC0xMHB4O1xyXG4gIHRyYW5zZm9ybTogcm90YXRlWigxODBkZWcpO1xyXG59XHJcblxyXG4uY2xyLXBvbGFyb2lkIC5jbHItZ3JhZGllbnQge1xyXG4gIHdpZHRoOiBjYWxjKDEwMCUgLSAyMHB4KTtcclxuICBoZWlnaHQ6IDEyMHB4O1xyXG4gIG1hcmdpbjogMTBweDtcclxuICBib3JkZXItcmFkaXVzOiAzcHg7XHJcbn1cclxuXHJcbi5jbHItcG9sYXJvaWQgLmNsci1odWUsXHJcbi5jbHItcG9sYXJvaWQgLmNsci1hbHBoYSB7XHJcbiAgd2lkdGg6IGNhbGMoMTAwJSAtIDMwcHgpO1xyXG4gIGhlaWdodDogMTBweDtcclxuICBtYXJnaW46IDZweCAxNXB4O1xyXG4gIGJvcmRlci1yYWRpdXM6IDVweDtcclxufVxyXG5cclxuLmNsci1wb2xhcm9pZCAuY2xyLWh1ZSBkaXYsXHJcbi5jbHItcG9sYXJvaWQgLmNsci1hbHBoYSBkaXYge1xyXG4gIGJveC1zaGFkb3c6IDAgMCA1cHggcmdiYSgwLDAsMCwuMik7XHJcbn1cclxuXHJcbi5jbHItcG9sYXJvaWQgLmNsci1mb3JtYXQge1xyXG4gIHdpZHRoOiBjYWxjKDEwMCUgLSAyMHB4KTtcclxuICBtYXJnaW46IDAgMTBweCAxNXB4O1xyXG59XHJcblxyXG4uY2xyLXBvbGFyb2lkIC5jbHItc3dhdGNoZXMge1xyXG4gIHdpZHRoOiBjYWxjKDEwMCUgLSAxMnB4KTtcclxuICBtYXJnaW46IDAgNnB4O1xyXG59XHJcbi5jbHItcG9sYXJvaWQgLmNsci1zd2F0Y2hlcyBkaXYge1xyXG4gIHBhZGRpbmctYm90dG9tOiAxMHB4O1xyXG59XHJcblxyXG4uY2xyLXBvbGFyb2lkIC5jbHItc3dhdGNoZXMgYnV0dG9uIHtcclxuICB3aWR0aDogMjJweDtcclxuICBoZWlnaHQ6IDIycHg7XHJcbn1cclxuXHJcbi5jbHItcG9sYXJvaWQgaW5wdXQuY2xyLWNvbG9yIHtcclxuICB3aWR0aDogY2FsYygxMDAlIC0gNjBweCk7XHJcbiAgbWFyZ2luOiAxMHB4IDEwcHggMTVweCAwO1xyXG59XHJcblxyXG4uY2xyLXBvbGFyb2lkIC5jbHItY2xlYXIge1xyXG4gIG1hcmdpbjogMCAxMHB4IDE1cHggYXV0bztcclxufVxyXG5cclxuLmNsci1wb2xhcm9pZCAuY2xyLXByZXZpZXcge1xyXG4gIG1hcmdpbjogMTBweCAwIDE1cHggMTBweDtcclxufVxyXG5cclxuLyoqIExhcmdlIHRoZW1lICoqL1xyXG5cclxuLmNsci1waWNrZXIuY2xyLWxhcmdlIHtcclxuICB3aWR0aDogMjc1cHg7XHJcbn1cclxuXHJcbi5jbHItbGFyZ2UgLmNsci1ncmFkaWVudCB7XHJcbiAgaGVpZ2h0OiAxNTBweDtcclxufVxyXG5cclxuLmNsci1sYXJnZSAuY2xyLXN3YXRjaGVzIGJ1dHRvbiB7XHJcbiAgd2lkdGg6IDIycHg7XHJcbiAgaGVpZ2h0OiAyMnB4O1xyXG59Il0sCiAgIm1hcHBpbmdzIjogIjtBQUFBO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFBQTtBQUVFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFBQTtBQUVFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQUE7QUFFRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFBQTtBQUVFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUFBO0FBRUU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQUE7QUFBQTtBQUFBO0FBSUU7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUtFO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFBQTtBQUFBO0FBQUE7QUFJRTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBS0U7QUFBQTtBQUtGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUFBO0FBQUE7QUFBQTtBQUlFO0FBQUE7QUFLRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFBQTtBQUVFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUFBO0FBRUU7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQUE7QUFFRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBS0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQUE7IiwKICAibmFtZXMiOiBbXQp9Cg== */ 437 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Editor, MarkdownView, Plugin } from 'obsidian'; 2 | import { DEFAULT_SETTINGS, TextToolbarSettings, TextToolbarSettingTab } from 'settings/settings'; 3 | import { ToolBarBuilder } from 'utils/ToolBarBuilder'; 4 | import { detachToolBar, toolBarPosInit, WheelScroll, KeyScroll, fontSizeAdjust } from 'utils/Utils'; 5 | 6 | export default class TextToolbar extends Plugin { 7 | settings: TextToolbarSettings; 8 | static toolBar: HTMLDivElement; 9 | static iwSize: { iw: number; ih: number }; 10 | static wheelScroll: WheelScroll; 11 | static keyScroll: KeyScroll; 12 | 13 | async onload() { 14 | await this.loadSettings(); 15 | 16 | const newToolBar = new ToolBarBuilder(this.app, this.manifest, this.settings); 17 | TextToolbar.toolBar = newToolBar.get; 18 | TextToolbar.wheelScroll = new WheelScroll(this.app, this.manifest, this.settings); 19 | TextToolbar.wheelScroll.handler = (ev: WheelEvent) => { 20 | ev.preventDefault(); 21 | TextToolbar.wheelScroll.snap(ev); 22 | }; 23 | TextToolbar.keyScroll = new KeyScroll(this.app, this.manifest, this.settings); 24 | TextToolbar.keyScroll.handler = (ev: KeyboardEvent) => { 25 | if (ev.key === 'Tab') { 26 | ev.preventDefault(); 27 | } 28 | TextToolbar.keyScroll.snap(ev); 29 | }; 30 | 31 | const showToolBar = (caller: string) => { 32 | if (this.settings.trigger_auto_manual === 'Manual' && caller === 'selectionchange') return; 33 | 34 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 35 | 36 | if (view && view.getState().mode === 'source') { 37 | const selection = view.editor.getSelection(); 38 | 39 | if (selection && !document.getElementsByClassName('text-tool-bar')[0]) { 40 | document.querySelector('body').appendChild(TextToolbar.toolBar); 41 | 42 | document 43 | .getElementsByClassName('text-tool-bar')[0] 44 | .addEventListener('wheel', TextToolbar.wheelScroll.handler, { 45 | passive: false, 46 | capture: true, 47 | }); 48 | document.addEventListener('keydown', TextToolbar.keyScroll.handler, true); 49 | 50 | toolBarPosInit(TextToolbar.toolBar, this.settings); 51 | fontSizeAdjust('text-tool-bar-label'); 52 | } else if (!selection) { 53 | detachToolBar(TextToolbar.toolBar); 54 | } 55 | } 56 | }; 57 | TextToolbar.iwSize = { iw: window.innerWidth, ih: window.innerHeight }; 58 | 59 | const rebuildToolBar = () => { 60 | detachToolBar(TextToolbar.toolBar); 61 | delete TextToolbar.toolBar; 62 | const newToolBar = new ToolBarBuilder(this.app, this.manifest, this.settings); 63 | TextToolbar.toolBar = newToolBar.get; 64 | TextToolbar.iwSize = { iw: window.innerWidth, ih: window.innerHeight }; 65 | }; 66 | 67 | this.app.workspace.onLayoutReady(() => { 68 | this.registerDomEvent(document, 'selectionchange', (ev) => showToolBar(ev.type)); 69 | this.registerEvent(this.app.workspace.on('active-leaf-change', () => detachToolBar(TextToolbar.toolBar))); 70 | this.registerEvent(this.app.workspace.on('layout-change', () => detachToolBar(TextToolbar.toolBar))); 71 | this.registerEvent(this.app.workspace.on('editor-menu', () => detachToolBar(TextToolbar.toolBar))); 72 | this.registerEvent(this.app.workspace.on('quit', () => detachToolBar(TextToolbar.toolBar))); 73 | this.registerDomEvent(window, 'resize', () => rebuildToolBar()); 74 | }); 75 | 76 | this.addCommand({ 77 | id: 'show-tool-bar', 78 | name: 'Show Tool Bar', 79 | editorCallback: (editor: Editor, view: MarkdownView) => { 80 | showToolBar('cmd'); 81 | }, 82 | }); 83 | 84 | this.addSettingTab(new TextToolbarSettingTab(this.app, this)); 85 | } 86 | 87 | onunload() { 88 | detachToolBar(TextToolbar.toolBar); 89 | } 90 | 91 | async loadSettings() { 92 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 93 | } 94 | 95 | async saveSettings() { 96 | await this.saveData(this.settings); 97 | detachToolBar(TextToolbar.toolBar); 98 | const newToolBar = new ToolBarBuilder(this.app, this.manifest, this.settings); 99 | TextToolbar.toolBar = newToolBar.get; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-text-toolbar", 3 | "name": "Text Toolbar", 4 | "version": "1.0.1", 5 | "minAppVersion": "0.12.0", 6 | "description": "A customizable toolbar that allows CSS snippets, markdowns, and built-in macros to be applied to selected text.", 7 | "author": "faru", 8 | "authorUrl": "https://github.com/farux", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-text-toolbar", 3 | "version": "1.0.1", 4 | "description": "A customizable toolbar that allows CSS snippets, markdowns, and built-in macros to be applied to selected text.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "^5.2.0", 17 | "@typescript-eslint/parser": "^5.2.0", 18 | "builtin-modules": "^3.2.0", 19 | "esbuild": "0.13.12", 20 | "obsidian": "^0.12.17", 21 | "tslib": "2.3.1", 22 | "typescript": "4.4.4" 23 | }, 24 | "dependencies": { 25 | "@popperjs/core": "^2.11.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /settings/settings.ts: -------------------------------------------------------------------------------- 1 | import TextToolbar from 'main'; 2 | import { App, PluginSettingTab, Setting, ButtonComponent } from 'obsidian'; 3 | 4 | import { actionSuggest } from 'suggests/action-suggest'; 5 | import { arrayMove } from 'utils/Utils'; 6 | 7 | export interface ActionSet { 8 | action: string; 9 | label: string; 10 | class: string; 11 | } 12 | 13 | export interface TextToolbarSettings { 14 | trigger_auto_manual: string; 15 | l_window_width_thresholds: string; 16 | l_button_size: string; 17 | l_button_spacing: string; 18 | l_button_font_size: string; 19 | l_toolbar_margin: string; 20 | l_toolbar_number_of_buttons_per_line: string; 21 | l_toolbar_number_of_rows: string; 22 | l_toolbar_display_position: string; 23 | l_toolbar_vertical_offset: string; 24 | l_toolbar_horizontal_offset: string; 25 | m_button_size: string; 26 | m_button_spacing: string; 27 | m_button_font_size: string; 28 | m_toolbar_margin: string; 29 | m_toolbar_number_of_buttons_per_line: string; 30 | m_toolbar_number_of_rows: string; 31 | m_toolbar_display_position: string; 32 | m_toolbar_vertical_offset: string; 33 | m_toolbar_horizontal_offset: string; 34 | s_window_width_thresholds: string; 35 | s_button_size: string; 36 | s_button_spacing: string; 37 | s_button_font_size: string; 38 | s_toolbar_margin: string; 39 | s_toolbar_number_of_buttons_per_line: string; 40 | s_toolbar_number_of_rows: string; 41 | s_toolbar_display_position: string; 42 | s_toolbar_vertical_offset: string; 43 | s_toolbar_horizontal_offset: string; 44 | button_default_font_color: string; 45 | button_focus_color: string; 46 | button_hover_color: string; 47 | button_border_radius: number; 48 | button_border_width: number; 49 | button_border_focus_color: string; 50 | button_border_hover_color: string; 51 | button_border_default_color: string; 52 | button_default_color: string; 53 | action_set: Array; 54 | toolbar_background_blur: number; 55 | toolbar_background_color: string; 56 | toolbar_background_opacity: number; 57 | toolbar_border_color: string; 58 | toolbar_border_radius: number; 59 | toolbar_border_width: number; 60 | } 61 | 62 | export const DEFAULT_SETTINGS: TextToolbarSettings = { 63 | action_set: [{ action: '', label: '', class: '' }], 64 | trigger_auto_manual: 'Automatic', 65 | l_window_width_thresholds: '1000', 66 | l_button_size: '50', 67 | l_button_spacing: '15', 68 | l_button_font_size: '30', 69 | l_toolbar_margin: '15', 70 | l_toolbar_number_of_buttons_per_line: '8', 71 | l_toolbar_number_of_rows: '1', 72 | l_toolbar_display_position: 'bottom', 73 | l_toolbar_vertical_offset: '0', 74 | l_toolbar_horizontal_offset: '0', 75 | m_button_size: '50', 76 | m_button_spacing: '15', 77 | m_button_font_size: '30', 78 | m_toolbar_margin: '15', 79 | m_toolbar_number_of_buttons_per_line: '8', 80 | m_toolbar_number_of_rows: '1', 81 | m_toolbar_display_position: 'bottom', 82 | m_toolbar_vertical_offset: '0', 83 | m_toolbar_horizontal_offset: '0', 84 | s_window_width_thresholds: '500', 85 | s_button_size: '50', 86 | s_button_spacing: '15', 87 | s_button_font_size: '30', 88 | s_toolbar_margin: '15', 89 | s_toolbar_number_of_buttons_per_line: '8', 90 | s_toolbar_number_of_rows: '1', 91 | s_toolbar_display_position: 'bottom', 92 | s_toolbar_vertical_offset: '0', 93 | s_toolbar_horizontal_offset: '0', 94 | toolbar_background_blur: 2, 95 | toolbar_background_color: '#666666', 96 | toolbar_background_opacity: 75, 97 | toolbar_border_color: '#666666', 98 | toolbar_border_radius: 6, 99 | toolbar_border_width: 0, 100 | button_default_color: '#202020', 101 | button_border_default_color: '#202020', 102 | button_border_focus_color: '#7F6DF2', 103 | button_border_hover_color: '#7F6DF2', 104 | button_border_width: 4, 105 | button_border_radius: 6, 106 | button_focus_color: '#202020', 107 | button_hover_color: '#404040', 108 | button_default_font_color: '#DCDDDE', 109 | }; 110 | 111 | export class TextToolbarSettingTab extends PluginSettingTab { 112 | plugin: TextToolbar; 113 | 114 | constructor(app: App, plugin: TextToolbar) { 115 | super(app, plugin); 116 | this.plugin = plugin; 117 | } 118 | 119 | display(): void { 120 | const { containerEl } = this; 121 | 122 | containerEl.empty(); 123 | this.containerEl.empty(); 124 | this.add_text_toolbar_setting(); 125 | } 126 | 127 | add_text_toolbar_setting(): void { 128 | this.containerEl.createEl('h2', { text: 'Text Toolbar' }); 129 | 130 | const descEl = document.createDocumentFragment(); 131 | 132 | const instructionsDesc = document.createDocumentFragment(); 133 | instructionsDesc.append( 134 | descEl.createEl('strong', { text: 'Instructions ' }), 135 | descEl.createEl('br'), 136 | 'Tab key: Focus on toolbar or next button', 137 | descEl.createEl('br'), 138 | `Shift+Tab: Focus on previous button`, 139 | descEl.createEl('br'), 140 | `Cursor key: Move button focus`, 141 | descEl.createEl('br'), 142 | `Esc key: Hide toolbar` 143 | ); 144 | 145 | new Setting(this.containerEl).setDesc(instructionsDesc); 146 | 147 | const triggerDesc = document.createDocumentFragment(); 148 | triggerDesc.append( 149 | 'Select how to activate the toolbar.', 150 | descEl.createEl('br'), 151 | descEl.createEl('strong', { text: 'Automatic ' }), 152 | 'will automatically show a toolbar when text is selected on the edit view and will be hidden when an action is executed or text is deselected.', 153 | descEl.createEl('br'), 154 | descEl.createEl('strong', { text: 'Manual ' }), 155 | ' does not automatically show/hide the toolbar.', 156 | descEl.createEl('br'), 157 | 'The toolbar can be activated from a command with text selected and hidden with the ESC key.' 158 | ); 159 | new Setting(this.containerEl) 160 | .setName('Trigger') 161 | .setDesc(triggerDesc) 162 | .addDropdown((dropDown) => 163 | dropDown 164 | .addOption('Automatic', 'Automatic') 165 | .addOption('Manual', 'Manual') 166 | .setValue(this.plugin.settings.trigger_auto_manual) 167 | .onChange(async (value: string) => { 168 | this.plugin.settings.trigger_auto_manual = value; 169 | await this.plugin.saveSettings(); 170 | this.display(); 171 | }) 172 | ); 173 | 174 | const structureDesc = document.createDocumentFragment(); 175 | structureDesc.append( 176 | 'One of three different structure toolbars will appear based on the Obsidian window size.', 177 | descEl.createEl('br'), 178 | 'It can be set to match the screen of your desktop, tablet, smartphone, etc.', 179 | descEl.createEl('br'), 180 | ' To configure settings for each screen size, switch between the following tabs' 181 | ); 182 | new Setting(this.containerEl).setDesc(structureDesc).setClass('tt-setting-structure-desc'); 183 | 184 | this.containerEl.createEl('details', { cls: 'setting-item tt-setting-structure-details' }); 185 | this.containerEl.createEl('summary', { cls: 'tt-setting-structure-summary' }); 186 | this.containerEl.createEl('span', { 187 | cls: 'tt-setting-structure-iw', 188 | text: 'Current window size ( width: ' + TextToolbar.iwSize.iw + ', height: ' + TextToolbar.iwSize.ih + ' )', 189 | }); 190 | this.containerEl.createEl('div', { cls: 'tt-setting-structure-wrap' }); 191 | this.containerEl.createEl('input', { cls: 'tt-setting-tab-switch', type: 'radio' }); 192 | this.containerEl.createEl('input', { cls: 'tt-setting-tab-switch', type: 'radio' }); 193 | this.containerEl.createEl('input', { cls: 'tt-setting-tab-switch', type: 'radio' }); 194 | this.containerEl.createEl('label', { cls: 'tt-setting-tab-label', text: 'Large' }); 195 | this.containerEl.createEl('label', { cls: 'tt-setting-tab-label', text: 'Middle' }); 196 | this.containerEl.createEl('label', { cls: 'tt-setting-tab-label', text: 'Small' }); 197 | this.containerEl.createEl('div', { cls: 'tt-setting-tab-content' }); 198 | this.containerEl.createEl('div', { cls: 'tt-setting-tab-content' }); 199 | this.containerEl.createEl('div', { cls: 'tt-setting-tab-content' }); 200 | const tabDetails = document.querySelector('details.tt-setting-structure-details'); 201 | const tabSummary = document.querySelector('summary.tt-setting-structure-summary'); 202 | tabSummary.innerHTML = `Toolbar Structure`; 203 | const tabDesc = document.querySelector('div.tt-setting-structure-desc'); 204 | const tabIw = document.querySelector('span.tt-setting-structure-iw'); 205 | const tabWrap = document.querySelector('div.tt-setting-structure-wrap'); 206 | const tabSwitch = document.getElementsByClassName('tt-setting-tab-switch'); 207 | const tabLabel = document.getElementsByClassName('tt-setting-tab-label'); 208 | const tabContent = document.getElementsByClassName('tt-setting-tab-content'); 209 | const tabSwitchL = tabSwitch[0]; 210 | const tabSwitchM = tabSwitch[1]; 211 | const tabSwitchS = tabSwitch[2]; 212 | const tabLabelL = tabLabel[0]; 213 | const tabLabelM = tabLabel[1]; 214 | const tabLabelS = tabLabel[2]; 215 | tabLabelL.htmlFor = 'tt-setting-tab-switch-L'; 216 | tabLabelM.htmlFor = 'tt-setting-tab-switch-M'; 217 | tabLabelS.htmlFor = 'tt-setting-tab-switch-S'; 218 | tabSwitchL.name = 'TAB'; 219 | tabSwitchL.id = 'tt-setting-tab-switch-L'; 220 | tabSwitchL.checked = true; 221 | tabSwitchM.name = 'TAB'; 222 | tabSwitchM.id = 'tt-setting-tab-switch-M'; 223 | tabSwitchS.name = 'TAB'; 224 | tabSwitchS.id = 'tt-setting-tab-switch-S'; 225 | tabDetails.appendChild(tabSummary); 226 | tabDetails.appendChild(tabDesc); 227 | tabDetails.appendChild(tabIw); 228 | tabDetails.appendChild(tabWrap); 229 | tabWrap.appendChild(tabSwitch[0]); 230 | tabWrap.appendChild(tabSwitch[1]); 231 | tabWrap.appendChild(tabSwitch[2]); 232 | tabSwitch[0].after(tabLabel[0]); 233 | tabSwitch[1].after(tabLabel[1]); 234 | tabSwitch[2].after(tabLabel[2]); 235 | tabLabel[0].after(tabContent[0]); 236 | tabLabel[1].after(tabContent[1]); 237 | tabLabel[2].after(tabContent[2]); 238 | 239 | //Large Display 240 | 241 | const lWindowWidthThresholdsDesc = document.createDocumentFragment(); 242 | lWindowWidthThresholdsDesc.append( 243 | 'Obsidian windows with a width of this value or wider will be recognized as a Large size.', 244 | descEl.createEl('br'), 245 | 'Default is 1000' 246 | ); 247 | 248 | new Setting(this.containerEl) 249 | .setClass('tt-setting-large') 250 | .setName('Large window width threshold') 251 | .setDesc(lWindowWidthThresholdsDesc) 252 | .addText((cb) => { 253 | cb.setPlaceholder('1000') 254 | .setValue(this.plugin.settings.l_window_width_thresholds) 255 | .onChange(async (value) => { 256 | this.plugin.settings.l_window_width_thresholds = value.trim(); 257 | await this.plugin.saveSettings(); 258 | }); 259 | }); 260 | 261 | const buttonSizeDesc = document.createDocumentFragment(); 262 | buttonSizeDesc.append('Set the size of the button as a number.', descEl.createEl('br'), 'Default is 50'); 263 | 264 | new Setting(this.containerEl) 265 | .setClass('tt-setting-large') 266 | .setName('Button Size') 267 | .setDesc(buttonSizeDesc) 268 | .addText((cb) => { 269 | cb.setPlaceholder('50') 270 | .setValue(this.plugin.settings.l_button_size) 271 | .onChange(async (value) => { 272 | this.plugin.settings.l_button_size = value.trim(); 273 | await this.plugin.saveSettings(); 274 | }); 275 | }); 276 | 277 | const buttonSpacingDesc = document.createDocumentFragment(); 278 | buttonSpacingDesc.append('Set the button spacing as a number.', descEl.createEl('br'), 'Default is 15'); 279 | 280 | new Setting(this.containerEl) 281 | .setClass('tt-setting-large') 282 | .setName('Button spacing') 283 | .setDesc(buttonSpacingDesc) 284 | .addText((cb) => { 285 | cb.setPlaceholder('15') 286 | .setValue(this.plugin.settings.l_button_spacing) 287 | .onChange(async (value) => { 288 | this.plugin.settings.l_button_spacing = value.trim(); 289 | await this.plugin.saveSettings(); 290 | }); 291 | }); 292 | 293 | const buttonFontSizeDesc = document.createDocumentFragment(); 294 | buttonFontSizeDesc.append('Set the basic label font size as a number.', descEl.createEl('br'), 'Default is 30'); 295 | 296 | new Setting(this.containerEl) 297 | .setClass('tt-setting-large') 298 | .setName('Basic label font size') 299 | .setDesc(buttonFontSizeDesc) 300 | .addText((cb) => { 301 | cb.setPlaceholder('30') 302 | .setValue(this.plugin.settings.l_button_font_size) 303 | .onChange(async (value) => { 304 | this.plugin.settings.l_button_font_size = value.trim(); 305 | await this.plugin.saveSettings(); 306 | }); 307 | }); 308 | 309 | const toolbarMarginDesc = document.createDocumentFragment(); 310 | toolbarMarginDesc.append('Set toolbar Margin as a number.', descEl.createEl('br'), 'Default is 15'); 311 | 312 | new Setting(this.containerEl) 313 | .setClass('tt-setting-large') 314 | .setName('Toolbar margin') 315 | .setDesc(toolbarMarginDesc) 316 | .addText((cb) => { 317 | cb.setPlaceholder('15') 318 | .setValue(this.plugin.settings.l_toolbar_margin) 319 | .onChange(async (value) => { 320 | this.plugin.settings.l_toolbar_margin = value.trim(); 321 | await this.plugin.saveSettings(); 322 | }); 323 | }); 324 | 325 | const numberOfButtonsPerLineDesc = document.createDocumentFragment(); 326 | numberOfButtonsPerLineDesc.append('Set the number of buttons per line.', descEl.createEl('br'), 'Default is 8'); 327 | 328 | new Setting(this.containerEl) 329 | .setClass('tt-setting-large') 330 | .setName('Number of buttons per line') 331 | .setDesc(numberOfButtonsPerLineDesc) 332 | .addText((cb) => { 333 | cb.setPlaceholder('8') 334 | .setValue(this.plugin.settings.l_toolbar_number_of_buttons_per_line) 335 | .onChange(async (value) => { 336 | this.plugin.settings.l_toolbar_number_of_buttons_per_line = value.trim(); 337 | await this.plugin.saveSettings(); 338 | }); 339 | }); 340 | 341 | const numberOfRowsInTheToolbarDesc = document.createDocumentFragment(); 342 | numberOfRowsInTheToolbarDesc.append( 343 | 'Set the number of rows in the toolbar.', 344 | descEl.createEl('br'), 345 | 'Default is 1' 346 | ); 347 | 348 | new Setting(this.containerEl) 349 | .setClass('tt-setting-large') 350 | .setName('Number of rows in the toolbar') 351 | .setDesc(numberOfRowsInTheToolbarDesc) 352 | .addText((cb) => { 353 | cb.setPlaceholder('1') 354 | .setValue(this.plugin.settings.l_toolbar_number_of_rows) 355 | .onChange(async (value) => { 356 | this.plugin.settings.l_toolbar_number_of_rows = value.trim(); 357 | await this.plugin.saveSettings(); 358 | }); 359 | }); 360 | 361 | const displayPositionDesc = document.createDocumentFragment(); 362 | displayPositionDesc.append('Set the toolbar display position.', descEl.createEl('br'), 'Default is Bottom'); 363 | 364 | new Setting(this.containerEl) 365 | .setClass('tt-setting-large') 366 | .setName('Display position') 367 | .setDesc(displayPositionDesc) 368 | .addDropdown((cb) => { 369 | cb.addOption('top', 'Top') 370 | .addOption('bottom', 'Bottom') 371 | .addOption('left', 'Left') 372 | .addOption('right', 'Right') 373 | .setValue(this.plugin.settings.l_toolbar_display_position) 374 | .onChange(async (value) => { 375 | this.plugin.settings.l_toolbar_display_position = value.trim(); 376 | await this.plugin.saveSettings(); 377 | }); 378 | }); 379 | 380 | const verticalOffsetDesc = document.createDocumentFragment(); 381 | verticalOffsetDesc.append( 382 | 'Set the vertical offset as a number.', 383 | descEl.createEl('br'), 384 | 'The effect of the numbers depends on the display position.', 385 | descEl.createEl('br'), 386 | 'Top,Left,Right: Moves down for positive numbers, up for negative numbers.', 387 | descEl.createEl('br'), 388 | 'Bottom: Moves up for positive numbers, down for negative numbers.', 389 | descEl.createEl('br'), 390 | 'Default is 0' 391 | ); 392 | 393 | new Setting(this.containerEl) 394 | .setClass('tt-setting-large') 395 | .setName('Vertical offset') 396 | .setDesc(verticalOffsetDesc) 397 | .addText((cb) => { 398 | cb.setPlaceholder('0') 399 | .setValue(this.plugin.settings.l_toolbar_vertical_offset) 400 | .onChange(async (value) => { 401 | this.plugin.settings.l_toolbar_vertical_offset = value.trim(); 402 | await this.plugin.saveSettings(); 403 | }); 404 | }); 405 | 406 | const horizontalOffsetDesc = document.createDocumentFragment(); 407 | horizontalOffsetDesc.append( 408 | 'Set the horizontal offset as a number.', 409 | descEl.createEl('br'), 410 | 'The effect of the numbers depends on the display position.', 411 | descEl.createEl('br'), 412 | 'Top,Bottom,Left: Move to the right for positive numbers, to the left for negative numbers.', 413 | descEl.createEl('br'), 414 | 'Right: Move to the left for positive numbers, to the right for negative numbers.', 415 | descEl.createEl('br'), 416 | 'Default is 0' 417 | ); 418 | 419 | new Setting(this.containerEl) 420 | .setClass('tt-setting-large') 421 | .setName('Horizontal offset') 422 | .setDesc(horizontalOffsetDesc) 423 | .addText((cb) => { 424 | cb.setPlaceholder('0') 425 | .setValue(this.plugin.settings.l_toolbar_horizontal_offset) 426 | .onChange(async (value) => { 427 | this.plugin.settings.l_toolbar_horizontal_offset = value.trim(); 428 | await this.plugin.saveSettings(); 429 | }); 430 | }); 431 | 432 | const largeSettings = document.getElementsByClassName('tt-setting-large'); 433 | for (let i = 0; i < largeSettings.length; i++) { 434 | tabContent[0].appendChild(largeSettings[i]); 435 | } 436 | 437 | //Medium Display 438 | 439 | const mButtonSizeDesc = document.createDocumentFragment(); 440 | mButtonSizeDesc.append('Set the size of the button as a number.', descEl.createEl('br'), 'Default is 50'); 441 | 442 | new Setting(this.containerEl) 443 | .setClass('tt-setting-medium') 444 | .setName('Button Size') 445 | .setDesc(mButtonSizeDesc) 446 | .addText((cb) => { 447 | cb.setPlaceholder('50') 448 | .setValue(this.plugin.settings.m_button_size) 449 | .onChange(async (value) => { 450 | this.plugin.settings.m_button_size = value.trim(); 451 | await this.plugin.saveSettings(); 452 | }); 453 | }); 454 | 455 | const mButtonSpacingDesc = document.createDocumentFragment(); 456 | mButtonSpacingDesc.append('Set the button spacing as a number.', descEl.createEl('br'), 'Default is 15'); 457 | 458 | new Setting(this.containerEl) 459 | .setClass('tt-setting-medium') 460 | .setName('Button spacing') 461 | .setDesc(mButtonSpacingDesc) 462 | .addText((cb) => { 463 | cb.setPlaceholder('15') 464 | .setValue(this.plugin.settings.m_button_spacing) 465 | .onChange(async (value) => { 466 | this.plugin.settings.m_button_spacing = value.trim(); 467 | await this.plugin.saveSettings(); 468 | }); 469 | }); 470 | 471 | const mButtonFontSizeDesc = document.createDocumentFragment(); 472 | mButtonFontSizeDesc.append( 473 | 'Set the basic label font size as a number.', 474 | descEl.createEl('br'), 475 | 'Default is 30' 476 | ); 477 | 478 | new Setting(this.containerEl) 479 | .setClass('tt-setting-medium') 480 | .setName('Basic label font size') 481 | .setDesc(mButtonFontSizeDesc) 482 | .addText((cb) => { 483 | cb.setPlaceholder('30') 484 | .setValue(this.plugin.settings.m_button_font_size) 485 | .onChange(async (value) => { 486 | this.plugin.settings.m_button_font_size = value.trim(); 487 | await this.plugin.saveSettings(); 488 | }); 489 | }); 490 | 491 | const mToolbarMarginDesc = document.createDocumentFragment(); 492 | mToolbarMarginDesc.append('Set toolbar Margin as a number.', descEl.createEl('br'), 'Default is 15'); 493 | 494 | new Setting(this.containerEl) 495 | .setClass('tt-setting-medium') 496 | .setName('Toolbar margin') 497 | .setDesc(mToolbarMarginDesc) 498 | .addText((cb) => { 499 | cb.setPlaceholder('15') 500 | .setValue(this.plugin.settings.m_toolbar_margin) 501 | .onChange(async (value) => { 502 | this.plugin.settings.m_toolbar_margin = value.trim(); 503 | await this.plugin.saveSettings(); 504 | }); 505 | }); 506 | 507 | const mNumberOfButtonsPerLineDesc = document.createDocumentFragment(); 508 | mNumberOfButtonsPerLineDesc.append( 509 | 'Set the number of buttons per line.', 510 | descEl.createEl('br'), 511 | 'Default is 8' 512 | ); 513 | 514 | new Setting(this.containerEl) 515 | .setClass('tt-setting-medium') 516 | .setName('Number of buttons per line') 517 | .setDesc(mNumberOfButtonsPerLineDesc) 518 | .addText((cb) => { 519 | cb.setPlaceholder('8') 520 | .setValue(this.plugin.settings.m_toolbar_number_of_buttons_per_line) 521 | .onChange(async (value) => { 522 | this.plugin.settings.m_toolbar_number_of_buttons_per_line = value.trim(); 523 | await this.plugin.saveSettings(); 524 | }); 525 | }); 526 | 527 | const mNumberOfRowsInTheToolbarDesc = document.createDocumentFragment(); 528 | mNumberOfRowsInTheToolbarDesc.append( 529 | 'Set the number of rows in the toolbar.', 530 | descEl.createEl('br'), 531 | 'Default is 1' 532 | ); 533 | 534 | new Setting(this.containerEl) 535 | .setClass('tt-setting-medium') 536 | .setName('Number of rows in the toolbar') 537 | .setDesc(mNumberOfRowsInTheToolbarDesc) 538 | .addText((cb) => { 539 | cb.setPlaceholder('1') 540 | .setValue(this.plugin.settings.m_toolbar_number_of_rows) 541 | .onChange(async (value) => { 542 | this.plugin.settings.m_toolbar_number_of_rows = value.trim(); 543 | await this.plugin.saveSettings(); 544 | }); 545 | }); 546 | 547 | const mDisplayPositionDesc = document.createDocumentFragment(); 548 | mDisplayPositionDesc.append('Set the toolbar display position.', descEl.createEl('br'), 'Default is Bottom'); 549 | 550 | new Setting(this.containerEl) 551 | .setClass('tt-setting-medium') 552 | .setName('Display position') 553 | .setDesc(mDisplayPositionDesc) 554 | .addDropdown((cb) => { 555 | cb.addOption('top', 'Top') 556 | .addOption('bottom', 'Bottom') 557 | .addOption('left', 'Left') 558 | .addOption('right', 'Right') 559 | .setValue(this.plugin.settings.m_toolbar_display_position) 560 | .onChange(async (value) => { 561 | this.plugin.settings.m_toolbar_display_position = value.trim(); 562 | await this.plugin.saveSettings(); 563 | }); 564 | }); 565 | 566 | const mVerticalOffsetDesc = document.createDocumentFragment(); 567 | mVerticalOffsetDesc.append( 568 | 'Set the vertical offset as a number.', 569 | descEl.createEl('br'), 570 | 'The effect of the numbers depends on the display position.', 571 | descEl.createEl('br'), 572 | 'Top,Left,Right: Moves down for positive numbers, up for negative numbers.', 573 | descEl.createEl('br'), 574 | 'Bottom: Moves up for positive numbers, down for negative numbers.', 575 | descEl.createEl('br'), 576 | 'Default is 0' 577 | ); 578 | 579 | new Setting(this.containerEl) 580 | .setClass('tt-setting-medium') 581 | .setName('Vertical offset') 582 | .setDesc(mVerticalOffsetDesc) 583 | .addText((cb) => { 584 | cb.setPlaceholder('0') 585 | .setValue(this.plugin.settings.m_toolbar_vertical_offset) 586 | .onChange(async (value) => { 587 | this.plugin.settings.m_toolbar_vertical_offset = value.trim(); 588 | await this.plugin.saveSettings(); 589 | }); 590 | }); 591 | 592 | const mHorizontalOffsetDesc = document.createDocumentFragment(); 593 | mHorizontalOffsetDesc.append( 594 | 'Set the horizontal offset as a number.', 595 | descEl.createEl('br'), 596 | 'The effect of the numbers depends on the display position.', 597 | descEl.createEl('br'), 598 | 'Top,Bottom,Left: Move to the right for positive numbers, to the left for negative numbers.', 599 | descEl.createEl('br'), 600 | 'Right: Move to the left for positive numbers, to the right for negative numbers.', 601 | descEl.createEl('br'), 602 | 'Default is 0' 603 | ); 604 | 605 | new Setting(this.containerEl) 606 | .setClass('tt-setting-medium') 607 | .setName('Horizontal offset') 608 | .setDesc(mHorizontalOffsetDesc) 609 | .addText((cb) => { 610 | cb.setPlaceholder('0') 611 | .setValue(this.plugin.settings.m_toolbar_horizontal_offset) 612 | .onChange(async (value) => { 613 | this.plugin.settings.m_toolbar_horizontal_offset = value.trim(); 614 | await this.plugin.saveSettings(); 615 | }); 616 | }); 617 | 618 | const mediumSettings = document.getElementsByClassName('tt-setting-medium'); 619 | for (let i = 0; i < mediumSettings.length; i++) { 620 | tabContent[1].appendChild(mediumSettings[i]); 621 | } 622 | 623 | //Small Window 624 | 625 | const sWindowWidthThresholdsDesc = document.createDocumentFragment(); 626 | sWindowWidthThresholdsDesc.append( 627 | 'Obsidian windows with a width of this value or narrower are recognized as Small size.', 628 | descEl.createEl('br'), 629 | 'Default is 500' 630 | ); 631 | 632 | new Setting(this.containerEl) 633 | .setClass('tt-setting-small') 634 | .setName('Small window width threshold') 635 | .setDesc(sWindowWidthThresholdsDesc) 636 | .addText((cb) => { 637 | cb.setPlaceholder('500') 638 | .setValue(this.plugin.settings.s_window_width_thresholds) 639 | .onChange(async (value) => { 640 | this.plugin.settings.s_window_width_thresholds = value.trim(); 641 | await this.plugin.saveSettings(); 642 | }); 643 | }); 644 | 645 | const sButtonSizeDesc = document.createDocumentFragment(); 646 | sButtonSizeDesc.append('Set the size of the button as a number.', descEl.createEl('br'), 'Default is 50'); 647 | 648 | new Setting(this.containerEl) 649 | .setClass('tt-setting-small') 650 | .setName('Button Size') 651 | .setDesc(sButtonSizeDesc) 652 | .addText((cb) => { 653 | cb.setPlaceholder('50') 654 | .setValue(this.plugin.settings.s_button_size) 655 | .onChange(async (value) => { 656 | this.plugin.settings.s_button_size = value.trim(); 657 | await this.plugin.saveSettings(); 658 | }); 659 | }); 660 | 661 | const sButtonSpacingDesc = document.createDocumentFragment(); 662 | sButtonSpacingDesc.append('Set the button spacing as a number.', descEl.createEl('br'), 'Default is 15'); 663 | 664 | new Setting(this.containerEl) 665 | .setClass('tt-setting-small') 666 | .setName('Button spacing') 667 | .setDesc(sButtonSpacingDesc) 668 | .addText((cb) => { 669 | cb.setPlaceholder('15') 670 | .setValue(this.plugin.settings.s_button_spacing) 671 | .onChange(async (value) => { 672 | this.plugin.settings.s_button_spacing = value.trim(); 673 | await this.plugin.saveSettings(); 674 | }); 675 | }); 676 | 677 | const sButtonFontSizeDesc = document.createDocumentFragment(); 678 | sButtonFontSizeDesc.append( 679 | 'Set the basic label font size as a number.', 680 | descEl.createEl('br'), 681 | 'Default is 30' 682 | ); 683 | 684 | new Setting(this.containerEl) 685 | .setClass('tt-setting-small') 686 | .setName('Basic label font size') 687 | .setDesc(sButtonFontSizeDesc) 688 | .addText((cb) => { 689 | cb.setPlaceholder('30') 690 | .setValue(this.plugin.settings.s_button_font_size) 691 | .onChange(async (value) => { 692 | this.plugin.settings.s_button_font_size = value.trim(); 693 | await this.plugin.saveSettings(); 694 | }); 695 | }); 696 | 697 | const sToolbarMarginDesc = document.createDocumentFragment(); 698 | sToolbarMarginDesc.append('Set toolbar Margin as a number.', descEl.createEl('br'), 'Default is 15'); 699 | 700 | new Setting(this.containerEl) 701 | .setClass('tt-setting-small') 702 | .setName('Toolbar margin') 703 | .setDesc(sToolbarMarginDesc) 704 | .addText((cb) => { 705 | cb.setPlaceholder('15') 706 | .setValue(this.plugin.settings.s_toolbar_margin) 707 | .onChange(async (value) => { 708 | this.plugin.settings.s_toolbar_margin = value.trim(); 709 | await this.plugin.saveSettings(); 710 | }); 711 | }); 712 | 713 | const sNumberOfButtonsPerLineDesc = document.createDocumentFragment(); 714 | sNumberOfButtonsPerLineDesc.append( 715 | 'Set the number of buttons per line.', 716 | descEl.createEl('br'), 717 | 'Default is 8' 718 | ); 719 | 720 | new Setting(this.containerEl) 721 | .setClass('tt-setting-small') 722 | .setName('Number of buttons per line') 723 | .setDesc(sNumberOfButtonsPerLineDesc) 724 | .addText((cb) => { 725 | cb.setPlaceholder('8') 726 | .setValue(this.plugin.settings.s_toolbar_number_of_buttons_per_line) 727 | .onChange(async (value) => { 728 | this.plugin.settings.s_toolbar_number_of_buttons_per_line = value.trim(); 729 | await this.plugin.saveSettings(); 730 | }); 731 | }); 732 | 733 | const sNumberOfRowsInTheToolbarDesc = document.createDocumentFragment(); 734 | sNumberOfRowsInTheToolbarDesc.append( 735 | 'Set the number of rows in the toolbar.', 736 | descEl.createEl('br'), 737 | 'Default is 1' 738 | ); 739 | 740 | new Setting(this.containerEl) 741 | .setClass('tt-setting-small') 742 | .setName('Number of rows in the toolbar') 743 | .setDesc(sNumberOfRowsInTheToolbarDesc) 744 | .addText((cb) => { 745 | cb.setPlaceholder('1') 746 | .setValue(this.plugin.settings.s_toolbar_number_of_rows) 747 | .onChange(async (value) => { 748 | this.plugin.settings.s_toolbar_number_of_rows = value.trim(); 749 | await this.plugin.saveSettings(); 750 | }); 751 | }); 752 | 753 | const sDisplayPositionDesc = document.createDocumentFragment(); 754 | sDisplayPositionDesc.append('Set the toolbar display position.', descEl.createEl('br'), 'Default is Bottom'); 755 | 756 | new Setting(this.containerEl) 757 | .setClass('tt-setting-small') 758 | .setName('Display position') 759 | .setDesc(sDisplayPositionDesc) 760 | .addDropdown((cb) => { 761 | cb.addOption('top', 'Top') 762 | .addOption('bottom', 'Bottom') 763 | .addOption('left', 'Left') 764 | .addOption('right', 'Right') 765 | .setValue(this.plugin.settings.s_toolbar_display_position) 766 | .onChange(async (value) => { 767 | this.plugin.settings.s_toolbar_display_position = value.trim(); 768 | await this.plugin.saveSettings(); 769 | }); 770 | }); 771 | 772 | const sVerticalOffsetDesc = document.createDocumentFragment(); 773 | sVerticalOffsetDesc.append( 774 | 'Set the vertical offset as a number.', 775 | descEl.createEl('br'), 776 | 'The effect of the numbers depends on the display position.', 777 | descEl.createEl('br'), 778 | 'Top,Left,Right: Moves down for positive numbers, up for negative numbers.', 779 | descEl.createEl('br'), 780 | 'Bottom: Moves up for positive numbers, down for negative numbers.', 781 | descEl.createEl('br'), 782 | 'Default is 0' 783 | ); 784 | 785 | new Setting(this.containerEl) 786 | .setClass('tt-setting-small') 787 | .setName('Vertical offset') 788 | .setDesc(sVerticalOffsetDesc) 789 | .addText((cb) => { 790 | cb.setPlaceholder('0') 791 | .setValue(this.plugin.settings.s_toolbar_vertical_offset) 792 | .onChange(async (value) => { 793 | this.plugin.settings.s_toolbar_vertical_offset = value.trim(); 794 | await this.plugin.saveSettings(); 795 | }); 796 | }); 797 | 798 | const sHorizontalOffsetDesc = document.createDocumentFragment(); 799 | sHorizontalOffsetDesc.append( 800 | 'Set the horizontal offset as a number.', 801 | descEl.createEl('br'), 802 | 'The effect of the numbers depends on the display position.', 803 | descEl.createEl('br'), 804 | 'Top,Bottom,Left: Move to the right for positive numbers, to the left for negative numbers.', 805 | descEl.createEl('br'), 806 | 'Right: Move to the left for positive numbers, to the right for negative numbers.', 807 | descEl.createEl('br'), 808 | 'Default is 0' 809 | ); 810 | 811 | new Setting(this.containerEl) 812 | .setClass('tt-setting-small') 813 | .setName('Horizontal offset') 814 | .setDesc(sHorizontalOffsetDesc) 815 | .addText((cb) => { 816 | cb.setPlaceholder('0') 817 | .setValue(this.plugin.settings.s_toolbar_horizontal_offset) 818 | .onChange(async (value) => { 819 | this.plugin.settings.s_toolbar_horizontal_offset = value.trim(); 820 | await this.plugin.saveSettings(); 821 | }); 822 | }); 823 | 824 | const smallSettings = document.getElementsByClassName('tt-setting-small'); 825 | for (let i = 0; i < smallSettings.length; i++) { 826 | tabContent[2].appendChild(smallSettings[i]); 827 | } 828 | 829 | //Appearance 830 | //Toolbar Appearance 831 | this.containerEl.createEl('details', { cls: 'setting-item tt-setting-appearance-tb-details' }); 832 | this.containerEl.createEl('summary', { cls: 'tt-setting-appearance-tb-summary' }); 833 | this.containerEl.createEl('div', { cls: 'tt-setting-appearance-tb-wrap' }); 834 | const apTbDetails = document.querySelector('details.tt-setting-appearance-tb-details'); 835 | const apTbSummary = document.querySelector('summary.tt-setting-appearance-tb-summary'); 836 | apTbSummary.innerHTML = `Toolbar Appearance`; 837 | const apTbWrap = document.querySelector('div.tt-setting-appearance-tb-wrap'); 838 | 839 | apTbDetails.appendChild(apTbSummary); 840 | apTbDetails.appendChild(apTbWrap); 841 | 842 | const toolbarBgColorDesc = document.createDocumentFragment(); 843 | toolbarBgColorDesc.append( 844 | 'Default value', 845 | descEl.createEl('br'), 846 | 'Background color: #666666', 847 | descEl.createEl('br'), 848 | 'Opacity: 75', 849 | descEl.createEl('br'), 850 | 'Blur: 2', 851 | descEl.createEl('br'), 852 | 'Border color: #666666', 853 | descEl.createEl('br'), 854 | 'Border width: 0', 855 | descEl.createEl('br'), 856 | 'Border radius: 6' 857 | ); 858 | 859 | new Setting(this.containerEl) 860 | .setClass('text-toolbar-background-color-setting') 861 | .setName('Toolbar Appearance') 862 | .setDesc(toolbarBgColorDesc) 863 | .addText((cb) => { 864 | cb.setPlaceholder('#666666') 865 | .setValue(this.plugin.settings.toolbar_background_color) 866 | .onChange(async (value) => { 867 | this.plugin.settings.toolbar_background_color = value.trim(); 868 | await this.plugin.saveSettings(); 869 | }); 870 | }) 871 | .addSlider((cb) => { 872 | cb.setLimits(0, 100, 1) 873 | .setValue(this.plugin.settings.toolbar_background_opacity) 874 | .setDynamicTooltip() 875 | .onChange(async (value) => { 876 | this.plugin.settings.toolbar_background_opacity = value; 877 | await this.plugin.saveSettings(); 878 | }); 879 | }) 880 | .addSlider((cb) => { 881 | cb.setLimits(0, 10, 1) 882 | .setValue(this.plugin.settings.toolbar_background_blur) 883 | .setDynamicTooltip() 884 | .onChange(async (value) => { 885 | this.plugin.settings.toolbar_background_blur = value; 886 | await this.plugin.saveSettings(); 887 | }); 888 | }) 889 | .addText((cb) => { 890 | cb.setPlaceholder('#666666') 891 | .setValue(this.plugin.settings.toolbar_border_color) 892 | .onChange(async (value) => { 893 | this.plugin.settings.toolbar_border_color = value.trim(); 894 | await this.plugin.saveSettings(); 895 | }); 896 | }) 897 | .addSlider((cb) => { 898 | cb.setLimits(0, 100, 1) 899 | .setValue(this.plugin.settings.toolbar_border_width) 900 | .setDynamicTooltip() 901 | .onChange(async (value) => { 902 | this.plugin.settings.toolbar_border_width = value; 903 | await this.plugin.saveSettings(); 904 | }); 905 | }) 906 | .addSlider((cb) => { 907 | cb.setLimits(0, 100, 1) 908 | .setValue(this.plugin.settings.toolbar_border_radius) 909 | .setDynamicTooltip() 910 | .onChange(async (value) => { 911 | this.plugin.settings.toolbar_border_radius = value; 912 | await this.plugin.saveSettings(); 913 | }); 914 | }); 915 | 916 | const toolbarBgColor = ( 917 | document.querySelector('div.text-toolbar-background-color-setting > div.setting-item-control') 918 | ); 919 | toolbarBgColor.style.display = 'grid'; 920 | toolbarBgColor.style.gap = '2em'; 921 | toolbarBgColor.style.gridTemplateColumns = '20em 1fr'; 922 | const toolbarBgColorItem1 = ( 923 | document.querySelector( 924 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(1)' 925 | ) 926 | ); 927 | const toolbarBgColorItem2 = ( 928 | document.querySelector( 929 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(2)' 930 | ) 931 | ); 932 | const toolbarBgColorItem3 = ( 933 | document.querySelector( 934 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(3)' 935 | ) 936 | ); 937 | const toolbarBgColorItem4 = ( 938 | document.querySelector( 939 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(4)' 940 | ) 941 | ); 942 | const toolbarBgColorItem5 = ( 943 | document.querySelector( 944 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(5)' 945 | ) 946 | ); 947 | const toolbarBgColorItem6 = ( 948 | document.querySelector( 949 | 'div.text-toolbar-background-color-setting > div.setting-item-control > :nth-child(6)' 950 | ) 951 | ); 952 | 953 | const bgColorLabel1 = document.createElement('label'); 954 | const bgColorLabel2 = document.createElement('label'); 955 | const bgColorLabel3 = document.createElement('label'); 956 | const bgColorLabel4 = document.createElement('label'); 957 | const bgColorLabel5 = document.createElement('label'); 958 | const bgColorLabel6 = document.createElement('label'); 959 | bgColorLabel1.classList.add('text-toolbar-settings-label'); 960 | bgColorLabel1.innerText = 'Background color : '; 961 | toolbarBgColorItem1.before(bgColorLabel1); 962 | bgColorLabel2.classList.add('text-toolbar-settings-label'); 963 | bgColorLabel2.innerText = 'Opacity : '; 964 | toolbarBgColorItem2.before(bgColorLabel2); 965 | bgColorLabel3.classList.add('text-toolbar-settings-label'); 966 | bgColorLabel3.innerText = 'Blur :'; 967 | toolbarBgColorItem3.before(bgColorLabel3); 968 | bgColorLabel4.classList.add('text-toolbar-settings-label'); 969 | bgColorLabel4.innerText = 'Border color : '; 970 | toolbarBgColorItem4.before(bgColorLabel4); 971 | bgColorLabel5.classList.add('text-toolbar-settings-label'); 972 | bgColorLabel5.innerText = 'Border width : '; 973 | toolbarBgColorItem5.before(bgColorLabel5); 974 | bgColorLabel6.classList.add('text-toolbar-settings-label'); 975 | bgColorLabel6.innerText = 'Border radius : '; 976 | toolbarBgColorItem6.before(bgColorLabel6); 977 | 978 | toolbarBgColorItem1.type = 'color'; 979 | toolbarBgColorItem4.type = 'color'; 980 | 981 | const apTbSetting = document.querySelector('div.text-toolbar-background-color-setting'); 982 | apTbWrap.appendChild(apTbSetting); 983 | 984 | //Button Appearance 985 | 986 | this.containerEl.createEl('details', { cls: 'setting-item tt-setting-appearance-btn-details' }); 987 | this.containerEl.createEl('summary', { cls: 'tt-setting-appearance-btn-summary' }); 988 | this.containerEl.createEl('div', { cls: 'tt-setting-appearance-btn-wrap' }); 989 | const apBtnDetails = document.querySelector('details.tt-setting-appearance-btn-details'); 990 | const apBtnSummary = document.querySelector('summary.tt-setting-appearance-btn-summary'); 991 | apBtnSummary.innerHTML = `Button Appearance`; 992 | const apBtnWrap = document.querySelector('div.tt-setting-appearance-btn-wrap'); 993 | 994 | apBtnDetails.appendChild(apBtnSummary); 995 | apBtnDetails.appendChild(apBtnWrap); 996 | 997 | const toolbarBtnColorDesc = document.createDocumentFragment(); 998 | toolbarBtnColorDesc.append( 999 | 'Default value', 1000 | descEl.createEl('br'), 1001 | 'Default button color: #202020', 1002 | descEl.createEl('br'), 1003 | 'Focus button color: #202020', 1004 | descEl.createEl('br'), 1005 | 'Hover button color: #404040', 1006 | descEl.createEl('br'), 1007 | 'Default border color: #202020', 1008 | descEl.createEl('br'), 1009 | 'Focus border color: #7F6DF2', 1010 | descEl.createEl('br'), 1011 | 'Hover border color: #7F6DF2', 1012 | descEl.createEl('br'), 1013 | 'Border width: 4', 1014 | descEl.createEl('br'), 1015 | 'Border radius: 6', 1016 | descEl.createEl('br'), 1017 | 'Default label color: #DCDDDE' 1018 | ); 1019 | 1020 | new Setting(this.containerEl) 1021 | .setClass('text-toolbar-button-color-setting') 1022 | .setName('Button Appearance') 1023 | .setDesc(toolbarBtnColorDesc) 1024 | .addText((cb) => { 1025 | cb.setPlaceholder('#202020') 1026 | .setValue(this.plugin.settings.button_default_color) 1027 | .onChange(async (value) => { 1028 | this.plugin.settings.button_default_color = value.trim(); 1029 | await this.plugin.saveSettings(); 1030 | }); 1031 | }) 1032 | .addText((cb) => { 1033 | cb.setPlaceholder('#202020') 1034 | .setValue(this.plugin.settings.button_focus_color) 1035 | .onChange(async (value) => { 1036 | this.plugin.settings.button_focus_color = value.trim(); 1037 | await this.plugin.saveSettings(); 1038 | }); 1039 | }) 1040 | .addText((cb) => { 1041 | cb.setPlaceholder('#404040') 1042 | .setValue(this.plugin.settings.button_hover_color) 1043 | .onChange(async (value) => { 1044 | this.plugin.settings.button_hover_color = value.trim(); 1045 | await this.plugin.saveSettings(); 1046 | }); 1047 | }) 1048 | .addText((cb) => { 1049 | cb.setPlaceholder('#202020') 1050 | .setValue(this.plugin.settings.button_border_default_color) 1051 | .onChange(async (value) => { 1052 | this.plugin.settings.button_border_default_color = value.trim(); 1053 | await this.plugin.saveSettings(); 1054 | }); 1055 | }) 1056 | .addText((cb) => { 1057 | cb.setPlaceholder('#7F6DF2') 1058 | .setValue(this.plugin.settings.button_border_focus_color) 1059 | .onChange(async (value) => { 1060 | this.plugin.settings.button_border_focus_color = value.trim(); 1061 | await this.plugin.saveSettings(); 1062 | }); 1063 | }) 1064 | .addText((cb) => { 1065 | cb.setPlaceholder('#7F6DF2') 1066 | .setValue(this.plugin.settings.button_border_hover_color) 1067 | .onChange(async (value) => { 1068 | this.plugin.settings.button_border_hover_color = value.trim(); 1069 | await this.plugin.saveSettings(); 1070 | }); 1071 | }) 1072 | .addSlider((cb) => { 1073 | cb.setLimits(0, 10, 1) 1074 | .setValue(this.plugin.settings.button_border_width) 1075 | .setDynamicTooltip() 1076 | .onChange(async (value) => { 1077 | this.plugin.settings.button_border_width = value; 1078 | await this.plugin.saveSettings(); 1079 | }); 1080 | }) 1081 | .addSlider((cb) => { 1082 | cb.setLimits(0, 100, 1) 1083 | .setValue(this.plugin.settings.button_border_radius) 1084 | .setDynamicTooltip() 1085 | .onChange(async (value) => { 1086 | this.plugin.settings.button_border_radius = value; 1087 | await this.plugin.saveSettings(); 1088 | }); 1089 | }) 1090 | .addText((cb) => { 1091 | cb.setPlaceholder('#DCDDDE') 1092 | .setValue(this.plugin.settings.button_default_font_color) 1093 | .onChange(async (value) => { 1094 | this.plugin.settings.button_default_font_color = value.trim(); 1095 | await this.plugin.saveSettings(); 1096 | }); 1097 | }); 1098 | const toolbarBtnColor = ( 1099 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control') 1100 | ); 1101 | toolbarBtnColor.style.display = 'grid'; 1102 | toolbarBtnColor.style.gap = '2em'; 1103 | toolbarBtnColor.style.gridTemplateColumns = '20em 1fr'; 1104 | 1105 | const toolbarBtnColorItem1 = ( 1106 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(1)') 1107 | ); 1108 | const toolbarBtnColorItem2 = ( 1109 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(2)') 1110 | ); 1111 | const toolbarBtnColorItem3 = ( 1112 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(3)') 1113 | ); 1114 | const toolbarBtnColorItem4 = ( 1115 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(4)') 1116 | ); 1117 | const toolbarBtnColorItem5 = ( 1118 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(5)') 1119 | ); 1120 | const toolbarBtnColorItem6 = ( 1121 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(6)') 1122 | ); 1123 | const toolbarBtnColorItem7 = ( 1124 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(7)') 1125 | ); 1126 | const toolbarBtnColorItem8 = ( 1127 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(8)') 1128 | ); 1129 | const toolbarBtnColorItem9 = ( 1130 | document.querySelector('div.text-toolbar-button-color-setting > div.setting-item-control > :nth-child(9)') 1131 | ); 1132 | const btnColorLabel1 = document.createElement('label'); 1133 | const btnColorLabel2 = document.createElement('label'); 1134 | const btnColorLabel3 = document.createElement('label'); 1135 | const btnColorLabel4 = document.createElement('label'); 1136 | const btnColorLabel5 = document.createElement('label'); 1137 | const btnColorLabel6 = document.createElement('label'); 1138 | const btnColorLabel7 = document.createElement('label'); 1139 | const btnColorLabel8 = document.createElement('label'); 1140 | const btnColorLabel9 = document.createElement('label'); 1141 | 1142 | btnColorLabel1.classList.add('text-toolbar-settings-label'); 1143 | btnColorLabel1.innerText = 'Default button color : '; 1144 | toolbarBtnColorItem1.before(btnColorLabel1); 1145 | btnColorLabel2.classList.add('text-toolbar-settings-label'); 1146 | btnColorLabel2.innerText = 'Focus button color : '; 1147 | toolbarBtnColorItem2.before(btnColorLabel2); 1148 | btnColorLabel3.classList.add('text-toolbar-settings-label'); 1149 | btnColorLabel3.innerText = 'Hover button color : '; 1150 | toolbarBtnColorItem3.before(btnColorLabel3); 1151 | btnColorLabel4.classList.add('text-toolbar-settings-label'); 1152 | btnColorLabel4.innerText = 'Default border color: '; 1153 | toolbarBtnColorItem4.before(btnColorLabel4); 1154 | btnColorLabel5.classList.add('text-toolbar-settings-label'); 1155 | btnColorLabel5.innerText = 'Focus border color : '; 1156 | toolbarBtnColorItem5.before(btnColorLabel5); 1157 | btnColorLabel6.classList.add('text-toolbar-settings-label'); 1158 | btnColorLabel6.innerText = 'Hover border color : '; 1159 | toolbarBtnColorItem6.before(btnColorLabel6); 1160 | btnColorLabel7.classList.add('text-toolbar-settings-label'); 1161 | btnColorLabel7.innerText = 'Border width : '; 1162 | toolbarBtnColorItem7.before(btnColorLabel7); 1163 | btnColorLabel8.classList.add('text-toolbar-settings-label'); 1164 | btnColorLabel8.innerText = 'Border radius : '; 1165 | toolbarBtnColorItem8.before(btnColorLabel8); 1166 | btnColorLabel9.classList.add('text-toolbar-settings-label'); 1167 | btnColorLabel9.innerText = 'Default label color : '; 1168 | toolbarBtnColorItem9.before(btnColorLabel9); 1169 | 1170 | toolbarBtnColorItem1.type = 'color'; 1171 | toolbarBtnColorItem2.type = 'color'; 1172 | toolbarBtnColorItem3.type = 'color'; 1173 | toolbarBtnColorItem4.type = 'color'; 1174 | toolbarBtnColorItem5.type = 'color'; 1175 | toolbarBtnColorItem6.type = 'color'; 1176 | toolbarBtnColorItem9.type = 'color'; 1177 | 1178 | const apBtnSetting = document.querySelector('div.text-toolbar-button-color-setting'); 1179 | apBtnWrap.appendChild(apBtnSetting); 1180 | 1181 | //Action 1182 | this.containerEl.createEl('h2', { text: 'Action' }); 1183 | const actionDesc = document.createDocumentFragment(); 1184 | actionDesc.append( 1185 | descEl.createEl('br'), 1186 | descEl.createEl('strong', { text: ' Action: ' }), 1187 | descEl.createEl('br'), 1188 | `There are three types of actions.`, 1189 | descEl.createEl('br'), 1190 | `Actions with the prefix 'TAG' add HTML tags.`, 1191 | descEl.createEl('br'), 1192 | `Actions with the prefix 'MD' add Markdown elements.`, 1193 | descEl.createEl('br'), 1194 | `Some actions add Markdown elements and HTML tags at the same time.`, 1195 | descEl.createEl('br'), 1196 | `Actions with the prefix 'MAC' execute Macros.`, 1197 | descEl.createEl('br'), 1198 | descEl.createEl('strong', { text: ' Tag Example: ' }), 1199 | descEl.createEl('br'), 1200 | `span: selected text`, 1201 | descEl.createEl('br'), 1202 | `div*2:
selected text
`, 1203 | descEl.createEl('br'), 1204 | `div span /%s/:
selected text
`, 1205 | descEl.createEl('br'), 1206 | descEl.createEl('br'), 1207 | descEl.createEl('strong', { text: ' Label: ' }), 1208 | descEl.createEl('br'), 1209 | `The label that appears on the button.`, 1210 | descEl.createEl('br'), 1211 | `If the CSS class is set, a simplified style is applied to the label.`, 1212 | descEl.createEl('br'), 1213 | `Font size is reduced to fit the button, so fewer characters on the label are preferred.`, 1214 | descEl.createEl('br'), 1215 | `Emoji and symbols can be used.`, 1216 | descEl.createEl('br'), 1217 | descEl.createEl('br'), 1218 | descEl.createEl('strong', { text: ' Class: ' }), 1219 | descEl.createEl('br'), 1220 | `CSS class to be applied to HTML tags.`, 1221 | descEl.createEl('br'), 1222 | `It must be registered in your CSS snippet.`, 1223 | descEl.createEl('br'), 1224 | `This is not required for Markdowns or Macros, so it can be left blank.` 1225 | ); 1226 | 1227 | this.containerEl.createEl('details', { cls: 'setting-item-description tt-setting-action-desc-details' }); 1228 | this.containerEl.createEl('summary', { cls: 'tt-setting-action-desc-summary' }); 1229 | const actionDescDetails = document.querySelector('details.tt-setting-action-desc-details'); 1230 | const actionDescSummary = document.querySelector('summary.tt-setting-action-desc-summary'); 1231 | actionDescSummary.innerHTML = `Action Description`; 1232 | actionDescDetails.appendChild(actionDescSummary); 1233 | actionDescDetails.appendChild(actionDesc); 1234 | actionDescDetails.setAttribute; 1235 | 1236 | const addActionDesc = document.createDocumentFragment(); 1237 | addActionDesc.append(`Each action is added to the toolbar in turn as a single button.`); 1238 | new Setting(this.containerEl) 1239 | 1240 | .setName('Add new action') 1241 | .setDesc(addActionDesc) 1242 | .addButton((button: ButtonComponent) => { 1243 | button 1244 | .setTooltip('Add new action') 1245 | .setButtonText('+') 1246 | .setCta() 1247 | .onClick(async () => { 1248 | this.plugin.settings.action_set.push({ 1249 | action: '', 1250 | label: '', 1251 | class: '', 1252 | }); 1253 | await this.plugin.saveSettings(); 1254 | this.display(); 1255 | }); 1256 | }); 1257 | 1258 | this.plugin.settings.action_set.forEach((action_set, index) => { 1259 | const s = new Setting(this.containerEl) 1260 | .addSearch((cb) => { 1261 | new actionSuggest(this.app, cb.inputEl); 1262 | cb.setPlaceholder('Action') 1263 | .setValue(action_set.action) 1264 | .onChange(async (newaction) => { 1265 | this.plugin.settings.action_set[index].action = newaction.trim(); 1266 | await this.plugin.saveSettings(); 1267 | }); 1268 | }) 1269 | 1270 | .addSearch((cb) => { 1271 | cb.setPlaceholder('Label') 1272 | .setValue(action_set.label) 1273 | .onChange(async (newClass) => { 1274 | this.plugin.settings.action_set[index].label = newClass.trim(); 1275 | await this.plugin.saveSettings(); 1276 | }); 1277 | }) 1278 | 1279 | .addSearch((cb) => { 1280 | cb.setPlaceholder('Class') 1281 | .setValue(action_set.class) 1282 | .onChange(async (newClass) => { 1283 | this.plugin.settings.action_set[index].class = newClass.trim(); 1284 | await this.plugin.saveSettings(); 1285 | }); 1286 | }) 1287 | 1288 | .addExtraButton((cb) => { 1289 | cb.setIcon('up-chevron-glyph') 1290 | .setTooltip('Move up') 1291 | .onClick(async () => { 1292 | arrayMove(this.plugin.settings.action_set, index, index - 1); 1293 | await this.plugin.saveSettings(); 1294 | this.display(); 1295 | }); 1296 | }) 1297 | .addExtraButton((cb) => { 1298 | cb.setIcon('down-chevron-glyph') 1299 | .setTooltip('Move down') 1300 | .onClick(async () => { 1301 | arrayMove(this.plugin.settings.action_set, index, index + 1); 1302 | await this.plugin.saveSettings(); 1303 | this.display(); 1304 | }); 1305 | }) 1306 | .addExtraButton((cb) => { 1307 | cb.setIcon('cross') 1308 | .setTooltip('Delete') 1309 | .onClick(async () => { 1310 | this.plugin.settings.action_set.splice(index, 1); 1311 | await this.plugin.saveSettings(); 1312 | this.display(); 1313 | }); 1314 | }); 1315 | s.infoEl.remove(); 1316 | }); 1317 | } 1318 | } 1319 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable font-family-name-quotes */ 2 | :root { 3 | --tt-toolbar-bg-color: #666666bf; 4 | --tt-toolbar-blur: blur(2); 5 | --tt-toolbar-border: 0; 6 | --tt-toolbar-border-radius: 6px; 7 | --tt-toolbar-padding: 15px; 8 | --tt-toolbar-buttons-per-line: repeat(8, 1fr); 9 | --tt-toolbar-number-of-rows: 100px; 10 | --tt-toolbar-top: unset; 11 | --tt-toolbar-bottom: 0; 12 | --tt-toolbar-left: unset; 13 | --tt-toolbar-right: unset; 14 | --tt-btn-size: 50px; 15 | --tt-btn-gap: 15px; 16 | --tt-btn-font-size: x-large; 17 | --tt-btn-default-bg-color: #202020; 18 | --tt-btn-focus-bg-color: #202020; 19 | --tt-btn-hover-bg-color: #404040; 20 | --tt-btn-default-border: 0; 21 | --tt-btn-focus-border: 4px solid #7f6df2; 22 | --tt-btn-hover-border: 4px solid #7f6df2; 23 | --tt-btn-border-radius: 6px; 24 | --tt-btn-default-font-color: #dcddde; 25 | } 26 | 27 | .text-tool-bar { 28 | animation: pop-d 200ms forwards ease-in-out; 29 | backdrop-filter: var(--tt-toolbar-blur); 30 | background-color: var(--tt-toolbar-bg-color); 31 | border: var(--tt-toolbar-border); 32 | border-radius: var(--tt-toolbar-border-radius); 33 | bottom: var(--tt-toolbar-bottom); 34 | box-shadow: 0 2px 8px var(--background-modifier-box-shadow); 35 | color: #dcddde; 36 | left: var(--tt-toolbar-left); 37 | padding: var(--tt-toolbar-padding); 38 | 39 | /* z-index: var(--layer-tooltip); */ 40 | position: fixed; 41 | right: var(--tt-toolbar-right); 42 | 43 | /* line-height: 20px; */ 44 | top: var(--tt-toolbar-top); 45 | z-index: 50; 46 | } 47 | 48 | .text-tool-bar-container { 49 | display: grid; 50 | 51 | /* column-gap: var(--tt-btn-gap); */ 52 | gap: var(--tt-btn-gap); 53 | grid-template-columns: var(--tt-toolbar-buttons-per-line); 54 | height: var(--tt-toolbar-number-of-rows); 55 | overflow: auto; 56 | padding: 0; 57 | place-items: center; 58 | scroll-snap-type: y mandatory; 59 | } 60 | 61 | .text-tool-bar-btn { 62 | background-color: var(--tt-btn-default-bg-color); 63 | border: var(--tt-btn-default-border); 64 | border-radius: var(--tt-btn-border-radius); 65 | color: var(--tt-btn-default-font-color); 66 | display: grid; 67 | height: var(--tt-btn-size); 68 | margin: 0; 69 | outline: none !important; 70 | overflow: hidden; 71 | padding: 0; 72 | place-items: center; 73 | 74 | /* scroll-margin: 0; */ 75 | scroll-snap-align: start; 76 | scroll-snap-stop: normal; 77 | text-align: center; 78 | width: var(--tt-btn-size); 79 | } 80 | 81 | .text-tool-bar-btn:focus { 82 | background-color: var(--tt-btn-focus-bg-color); 83 | border: var(--tt-btn-focus-border); 84 | box-shadow: none !important; 85 | outline: none !important; 86 | } 87 | 88 | .text-tool-bar-btn:hover { 89 | background-color: var(--tt-btn-hover-bg-color); 90 | border: var(--tt-btn-hover-border); 91 | box-shadow: none !important; 92 | outline: none !important; 93 | } 94 | 95 | .text-tool-bar-label-container { 96 | box-sizing: border-box; 97 | display: grid; 98 | font-size: var(--tt-btn-font-size); 99 | height: 100%; 100 | overflow: hidden; 101 | place-items: center; 102 | width: 100%; 103 | } 104 | 105 | .text-tool-bar-label-container:focus { 106 | background-color: transparent; 107 | box-shadow: 0; 108 | outline: none !important; 109 | } 110 | 111 | .text-tool-bar-label { 112 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; 113 | font-size: var(--tt-btn-font-size); 114 | white-space: nowrap; 115 | } 116 | 117 | .text-tool-bar-label:focus { 118 | background-color: transparent; 119 | box-shadow: 0; 120 | outline: none !important; 121 | } 122 | 123 | .text-tool-bar-label::before, 124 | .text-tool-bar-label::after { 125 | z-index: -1 !important; 126 | } 127 | @keyframes pop-d { 128 | 0% { 129 | opacity: 0; 130 | transform: scale(1); 131 | } 132 | 133 | 20% { 134 | opacity: 0.7; 135 | transform: scale(1.02); 136 | } 137 | 138 | 40% { 139 | opacity: 1; 140 | transform: scale(1.05); 141 | } 142 | 143 | 100% { 144 | opacity: 1; 145 | transform: scale(1); 146 | } 147 | } 148 | 149 | .text-toolbar-settings-label { 150 | color: var(--text-muted); 151 | font-size: var(--font-settings-small); 152 | } 153 | 154 | .tt-setting-structure-wrap { 155 | border: 1px solid var(--background-modifier-border); 156 | display: flex; 157 | flex-wrap: wrap; 158 | padding: 1em; 159 | } 160 | 161 | .tt-setting-tab-label { 162 | background: var(--text-muted); 163 | color: var(--text-on-accent); 164 | margin-right: 5px; 165 | order: -1; 166 | padding: 3px 12px; 167 | } 168 | 169 | .tt-setting-tab-content { 170 | display: none; 171 | margin-top: 1em; 172 | width: 100%; 173 | } 174 | 175 | /* アクティブなタブ */ 176 | .tt-setting-tab-switch:checked + .tt-setting-tab-label { 177 | background-color: var(--interactive-accent); 178 | color: var(--text-on-accent); 179 | } 180 | 181 | .tt-setting-tab-switch:checked + .tt-setting-tab-label + .tt-setting-tab-content { 182 | display: block; 183 | } 184 | 185 | /* ラジオボタン非表示 */ 186 | .tt-setting-tab-switch { 187 | display: none; 188 | } 189 | -------------------------------------------------------------------------------- /suggests/action-suggest.ts: -------------------------------------------------------------------------------- 1 | import { App, Plugin, PluginManifest } from 'obsidian'; 2 | 3 | import { TextInputSuggest } from './suggest'; 4 | 5 | export class GetAction extends Plugin { 6 | actionList: string[]; 7 | 8 | constructor(app: App, manifest: PluginManifest) { 9 | super(app, manifest); 10 | this.actionList = [ 11 | 'TAG:span', 12 | 'TAG:span*2', 13 | 'TAG:span*2 /%s/', 14 | 'TAG:div', 15 | 'TAG:div span', 16 | 'TAG:div span /%s/', 17 | 'TAG:div*2', 18 | 'TAG:div*2 /%s/', 19 | 'TAG:p', 20 | 'TAG:p span', 21 | 'TAG:p span /%s/', 22 | 'TAG:mark', 23 | 'TAG:mark span', 24 | 'TAG:mark span /%s/', 25 | 'TAG:details', 26 | 'TAG:details open', 27 | 'MD:Internal link', 28 | 'MD:Embeds', 29 | 'MD:h1', 30 | 'MD:h1 TAG:span', 31 | 'MD:h1 TAG:span*2', 32 | 'MD:h1 TAG:span*2 /%s/', 33 | 'MD:h2', 34 | 'MD:h2 TAG:span', 35 | 'MD:h2 TAG:span*2', 36 | 'MD:h2 TAG:span*2 /%s/', 37 | 'MD:h3', 38 | 'MD:h3 TAG:span', 39 | 'MD:h3 TAG:span*2', 40 | 'MD:h3 TAG:span*2 /%s/', 41 | 'MD:h4', 42 | 'MD:h4 TAG:span', 43 | 'MD:h4 TAG:span*2', 44 | 'MD:h4 TAG:span*2 /%s/', 45 | 'MD:h5', 46 | 'MD:h5 TAG:span', 47 | 'MD:h5 TAG:span*2', 48 | 'MD:h5 TAG:span*2 /%s/', 49 | 'MD:h6', 50 | 'MD:h6 TAG:span', 51 | 'MD:h6 TAG:span*2', 52 | 'MD:h6 TAG:span*2 /%s/', 53 | 'MD:*Italic*', 54 | 'MD:_Italic_', 55 | 'MD:**Bold**', 56 | 'MD:__Bold__', 57 | 'MD:Bulleted list', 58 | 'MD:Numbered list', 59 | 'MD:Image', 60 | 'MD:Link', 61 | 'MD:Blockquotes', 62 | 'MD:Inline code', 63 | 'MD:Code blocks', 64 | 'MD:Task list', 65 | 'MD:Strikethrough', 66 | 'MD:Highlight', 67 | 'MD:Footnotes', 68 | 'MD:Inline footnotes', 69 | 'MD:Math', 70 | 'MD:Comments', 71 | 'MD:Mermaid', 72 | 'MAC:UpperCase', 73 | 'MAC:LowerCase', 74 | 'MAC:Ascending order', 75 | 'MAC:Descending order', 76 | 'MAC:Upper Camel Case', 77 | 'MAC:Lower Camel Case', 78 | 'MAC:Snake case', 79 | 'MAC:Kebab case', 80 | 'MAC:Space separated', 81 | 'MAC:Remove blank lines', 82 | 'MAC:Merge Multiple spaces to single space', 83 | 'MAC:Remove HTML tags', 84 | ]; 85 | } 86 | 87 | pull(): string[] { 88 | return this.actionList; 89 | } 90 | } 91 | 92 | export class actionSuggest extends TextInputSuggest { 93 | manifest: PluginManifest; 94 | actionList: GetAction; 95 | actionMatch: string[]; 96 | lowerCaseInputStr: string; 97 | 98 | getSuggestions(inputStr: string): string[] { 99 | this.actionList = new GetAction(this.app, this.manifest); 100 | this.actionMatch = []; 101 | this.lowerCaseInputStr = inputStr.toLowerCase(); 102 | 103 | this.actionList.pull().forEach((action: string) => { 104 | if (action.toLowerCase().contains(this.lowerCaseInputStr)) { 105 | this.actionMatch.push(action); 106 | } 107 | }); 108 | 109 | return this.actionMatch; 110 | } 111 | 112 | renderSuggestion(action: string, el: HTMLElement): void { 113 | el.setText(action); 114 | } 115 | 116 | selectSuggestion(action: string): void { 117 | this.inputEl.value = action; 118 | this.inputEl.trigger('input'); 119 | this.close(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /suggests/suggest.ts: -------------------------------------------------------------------------------- 1 | // *************************************************************************************** 2 | // * Title: obsidian-periodic-notes/suggest.ts 3 | // * Author: Liam Cain https://github.com/liamcain 4 | // * Date: 2021 5 | // * Code version: Latest commit 427ebb2 on 4 Feb 2021 6 | // * Availability: https://github.com/liamcain/obsidian-periodic-notes 7 | // * 8 | // *************************************************************************************** 9 | import { App, ISuggestOwner, Scope } from 'obsidian'; 10 | import { createPopper, Instance as PopperInstance } from '@popperjs/core'; 11 | 12 | export const wrapAround = (value: number, size: number): number => { 13 | return ((value % size) + size) % size; 14 | }; 15 | 16 | class Suggest { 17 | private owner: ISuggestOwner; 18 | private values: T[]; 19 | private suggestions: HTMLDivElement[]; 20 | private selectedItem: number; 21 | private containerEl: HTMLElement; 22 | 23 | constructor(owner: ISuggestOwner, containerEl: HTMLElement, scope: Scope) { 24 | this.owner = owner; 25 | this.containerEl = containerEl; 26 | 27 | containerEl.on('click', '.suggestion-item', this.onSuggestionClick.bind(this)); 28 | containerEl.on('mousemove', '.suggestion-item', this.onSuggestionMouseover.bind(this)); 29 | 30 | scope.register([], 'ArrowUp', (event) => { 31 | if (!event.isComposing) { 32 | this.setSelectedItem(this.selectedItem - 1, true); 33 | return false; 34 | } 35 | }); 36 | 37 | scope.register([], 'ArrowDown', (event) => { 38 | if (!event.isComposing) { 39 | this.setSelectedItem(this.selectedItem + 1, true); 40 | return false; 41 | } 42 | }); 43 | 44 | scope.register([], 'Enter', (event) => { 45 | if (!event.isComposing) { 46 | this.useSelectedItem(event); 47 | return false; 48 | } 49 | }); 50 | } 51 | 52 | onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void { 53 | event.preventDefault(); 54 | 55 | const item = this.suggestions.indexOf(el); 56 | this.setSelectedItem(item, false); 57 | this.useSelectedItem(event); 58 | } 59 | 60 | onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void { 61 | const item = this.suggestions.indexOf(el); 62 | this.setSelectedItem(item, false); 63 | } 64 | 65 | setSuggestions(values: T[]) { 66 | this.containerEl.empty(); 67 | const suggestionEls: HTMLDivElement[] = []; 68 | 69 | values.forEach((value) => { 70 | const suggestionEl = this.containerEl.createDiv('suggestion-item'); 71 | this.owner.renderSuggestion(value, suggestionEl); 72 | suggestionEls.push(suggestionEl); 73 | }); 74 | 75 | this.values = values; 76 | this.suggestions = suggestionEls; 77 | this.setSelectedItem(0, false); 78 | } 79 | 80 | useSelectedItem(event: MouseEvent | KeyboardEvent) { 81 | const currentValue = this.values[this.selectedItem]; 82 | if (currentValue) { 83 | this.owner.selectSuggestion(currentValue, event); 84 | } 85 | } 86 | 87 | setSelectedItem(selectedIndex: number, scrollIntoView: boolean) { 88 | const normalizedIndex = wrapAround(selectedIndex, this.suggestions.length); 89 | const prevSelectedSuggestion = this.suggestions[this.selectedItem]; 90 | const selectedSuggestion = this.suggestions[normalizedIndex]; 91 | 92 | prevSelectedSuggestion?.removeClass('is-selected'); 93 | selectedSuggestion?.addClass('is-selected'); 94 | 95 | this.selectedItem = normalizedIndex; 96 | 97 | if (scrollIntoView) { 98 | selectedSuggestion.scrollIntoView(false); 99 | } 100 | } 101 | } 102 | 103 | export abstract class TextInputSuggest implements ISuggestOwner { 104 | protected app: App; 105 | protected inputEl: HTMLInputElement; 106 | 107 | private popper: PopperInstance; 108 | private scope: Scope; 109 | private suggestEl: HTMLElement; 110 | private suggest: Suggest; 111 | 112 | constructor(app: App, inputEl: HTMLInputElement) { 113 | this.app = app; 114 | this.inputEl = inputEl; 115 | this.scope = new Scope(); 116 | 117 | this.suggestEl = createDiv('suggestion-container'); 118 | const suggestion = this.suggestEl.createDiv('suggestion'); 119 | this.suggest = new Suggest(this, suggestion, this.scope); 120 | 121 | this.scope.register([], 'Escape', this.close.bind(this)); 122 | 123 | this.inputEl.addEventListener('input', this.onInputChanged.bind(this)); 124 | this.inputEl.addEventListener('focus', this.onInputChanged.bind(this)); 125 | this.inputEl.addEventListener('blur', this.close.bind(this)); 126 | this.suggestEl.on('mousedown', '.suggestion-container', (event: MouseEvent) => { 127 | event.preventDefault(); 128 | }); 129 | } 130 | 131 | onInputChanged(): void { 132 | const inputStr = this.inputEl.value; 133 | const suggestions = this.getSuggestions(inputStr); 134 | 135 | if (suggestions.length > 0) { 136 | this.suggest.setSuggestions(suggestions); 137 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 138 | this.open((this.app).dom.appContainerEl, this.inputEl); 139 | } 140 | } 141 | 142 | open(container: HTMLElement, inputEl: HTMLElement): void { 143 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 144 | (this.app).keymap.pushScope(this.scope); 145 | 146 | container.appendChild(this.suggestEl); 147 | this.popper = createPopper(inputEl, this.suggestEl, { 148 | placement: 'bottom-start', 149 | modifiers: [ 150 | { 151 | name: 'sameWidth', 152 | enabled: true, 153 | fn: ({ state, instance }) => { 154 | // Note: positioning needs to be calculated twice - 155 | // first pass - positioning it according to the width of the popper 156 | // second pass - position it with the width bound to the reference element 157 | // we need to early exit to avoid an infinite loop 158 | const targetWidth = `${state.rects.reference.width}px`; 159 | if (state.styles.popper.width === targetWidth) { 160 | return; 161 | } 162 | state.styles.popper.width = targetWidth; 163 | instance.update(); 164 | }, 165 | phase: 'beforeWrite', 166 | requires: ['computeStyles'], 167 | }, 168 | ], 169 | }); 170 | } 171 | 172 | close(): void { 173 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 174 | (this.app).keymap.popScope(this.scope); 175 | 176 | this.suggest.setSuggestions([]); 177 | this.popper.destroy(); 178 | this.suggestEl.detach(); 179 | } 180 | 181 | abstract getSuggestions(inputStr: string): T[]; 182 | abstract renderSuggestion(item: T, el: HTMLElement): void; 183 | abstract selectSuggestion(item: T): void; 184 | } 185 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "lib": [ 14 | "DOM", 15 | "ES5", 16 | "ES6", 17 | "ES7" 18 | ] 19 | }, 20 | "include": [ 21 | "**/*.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /utils/Action.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownView, Plugin } from 'obsidian'; 2 | import { TextToolbarSettings } from 'settings/settings'; 3 | import { detachToolBar } from './Utils'; 4 | 5 | export class ttAction extends Plugin { 6 | replace(ev: MouseEvent, settings: TextToolbarSettings, toolBar: HTMLDivElement) { 7 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 8 | const btnNum = parseInt((ev.currentTarget).name); 9 | const action = settings.action_set[btnNum].action; 10 | const cssCls = settings.action_set[btnNum].class; 11 | 12 | let selectionList = view.editor.listSelections(); 13 | let curPos; 14 | let curCh; 15 | let curLineOffset = 0; 16 | let curChOffset = 0; 17 | for (let i = 0; i < selectionList.length; i++) { 18 | const selectionRange = (() => { 19 | //If backward selection, re-select forward. 20 | if ( 21 | selectionList[i].anchor.line > selectionList[i].head.line || 22 | (selectionList[i].anchor.line === selectionList[i].head.line && 23 | selectionList[i].anchor.ch > selectionList[i].head.ch) 24 | ) { 25 | const tmp = selectionList[i]; 26 | const tmpAnchor = selectionList[i].head; 27 | const tmpHead = selectionList[i].anchor; 28 | tmp.anchor = tmpAnchor; 29 | tmp.head = tmpHead; 30 | view.editor.setSelection(tmp.anchor, tmp.head); 31 | return tmp; 32 | //forward selection 33 | } else { 34 | return selectionList[i]; 35 | } 36 | })(); 37 | 38 | const lineBreak = (() => { 39 | if (selectionRange.anchor.ch !== 0) { 40 | return '\n'; 41 | } else { 42 | return ''; 43 | } 44 | })(); 45 | 46 | const selectionTxt = view.editor.getRange(selectionRange.anchor, selectionRange.head); 47 | 48 | const bolReg = /^/gim; 49 | const urlReg = /:\/\//gim; 50 | const spaceReg = /\s/gim; 51 | let actionTxt; 52 | switch (action) { 53 | case 'TAG:span': 54 | actionTxt = `${selectionTxt}`; 55 | break; 56 | case 'TAG:span*2': 57 | actionTxt = `${selectionTxt}`; 58 | break; 59 | case 'TAG:span*2 /%s/': 60 | actionTxt = `${selectionTxt}`; 61 | curPos = 'bol'; 62 | curChOffset = 21 + cssCls.length; 63 | break; 64 | case 'TAG:div': 65 | actionTxt = `
${selectionTxt}
`; 66 | break; 67 | case 'TAG:div span': 68 | actionTxt = `
${selectionTxt}
`; 69 | break; 70 | case 'TAG:div span /%s/': 71 | actionTxt = `
${selectionTxt}
`; 72 | curPos = 'bol'; 73 | curChOffset = 20 + cssCls.length; 74 | break; 75 | case 'TAG:div*2': 76 | actionTxt = `
${selectionTxt}
`; 77 | break; 78 | case 'TAG:div*2 /%s/': 79 | actionTxt = `
\n${selectionTxt}
`; 80 | curPos = 'bol'; 81 | curChOffset = 19 + cssCls.length; 82 | break; 83 | case 'TAG:p': 84 | actionTxt = `

${selectionTxt}

`; 85 | break; 86 | case 'TAG:p span': 87 | actionTxt = `

${selectionTxt}

`; 88 | break; 89 | case 'TAG:p span /%s/': 90 | actionTxt = `

${selectionTxt}

`; 91 | curPos = 'bol'; 92 | curChOffset = 18 + cssCls.length; 93 | break; 94 | case 'TAG:mark': 95 | actionTxt = `${selectionTxt}`; 96 | break; 97 | case 'TAG:mark span': 98 | actionTxt = `${selectionTxt}`; 99 | break; 100 | case 'TAG:mark span /%s/': 101 | actionTxt = `${selectionTxt}`; 102 | curPos = 'bol'; 103 | curChOffset = 21 + cssCls.length; 104 | break; 105 | case 'TAG:details': 106 | actionTxt = `${lineBreak}
\n ${selectionTxt}\n \n
`; 107 | curLineOffset = -1; 108 | curCh = 2; 109 | break; 110 | case 'TAG:details open': 111 | actionTxt = `${lineBreak}
\n ${selectionTxt}\n \n
`; 112 | curLineOffset = -1; 113 | curCh = 2; 114 | break; 115 | case 'MD:Internal link': 116 | actionTxt = `[[${selectionTxt}]]`; 117 | break; 118 | case 'MD:Embeds': 119 | actionTxt = `![[${selectionTxt}]]`; 120 | break; 121 | case 'MD:h1': 122 | actionTxt = `${lineBreak}# ${selectionTxt}`; 123 | break; 124 | case 'MD:h1 TAG:span': 125 | actionTxt = `${lineBreak}# ${selectionTxt}`; 126 | break; 127 | case 'MD:h1 TAG:span*2': 128 | actionTxt = `${lineBreak}# ${selectionTxt}`; 129 | break; 130 | case 'MD:h1 TAG:span*2 /%s/': 131 | actionTxt = `${lineBreak}# ${selectionTxt}`; 132 | curPos = 'bol'; 133 | curChOffset = (() => { 134 | if (lineBreak) { 135 | return 24 + cssCls.length; 136 | } else { 137 | return 23 + cssCls.length; 138 | } 139 | })(); 140 | break; 141 | case 'MD:h2': 142 | actionTxt = `${lineBreak}## ${selectionTxt}`; 143 | break; 144 | case 'MD:h2 TAG:span': 145 | actionTxt = `${lineBreak}## ${selectionTxt}`; 146 | break; 147 | case 'MD:h2 TAG:span*2': 148 | actionTxt = `${lineBreak}## ${selectionTxt}`; 149 | break; 150 | case 'MD:h2 TAG:span*2 /%s/': 151 | actionTxt = `${lineBreak}## ${selectionTxt}`; 152 | curPos = 'bol'; 153 | curChOffset = (() => { 154 | if (lineBreak) { 155 | return 25 + cssCls.length; 156 | } else { 157 | return 24 + cssCls.length; 158 | } 159 | })(); 160 | break; 161 | case 'MD:h3': 162 | actionTxt = `${lineBreak}### ${selectionTxt}`; 163 | break; 164 | case 'MD:h3 TAG:span': 165 | actionTxt = `${lineBreak}### ${selectionTxt}`; 166 | break; 167 | case 'MD:h3 TAG:span*2': 168 | actionTxt = `${lineBreak}### ${selectionTxt}`; 169 | break; 170 | case 'MD:h3 TAG:span*2 /%s/': 171 | actionTxt = `${lineBreak}### ${selectionTxt}`; 172 | curPos = 'bol'; 173 | curChOffset = (() => { 174 | if (lineBreak) { 175 | return 26 + cssCls.length; 176 | } else { 177 | return 25 + cssCls.length; 178 | } 179 | })(); 180 | break; 181 | case 'MD:h4': 182 | actionTxt = `${lineBreak}#### ${selectionTxt}`; 183 | break; 184 | case 'MD:h4 TAG:span': 185 | actionTxt = `${lineBreak}#### ${selectionTxt}`; 186 | break; 187 | case 'MD:h4 TAG:span*2': 188 | actionTxt = `${lineBreak}#### ${selectionTxt}`; 189 | break; 190 | case 'MD:h4 TAG:span*2 /%s/': 191 | actionTxt = `${lineBreak}#### ${selectionTxt}`; 192 | curPos = 'bol'; 193 | curChOffset = (() => { 194 | if (lineBreak) { 195 | return 27 + cssCls.length; 196 | } else { 197 | return 26 + cssCls.length; 198 | } 199 | })(); 200 | break; 201 | case 'MD:h5': 202 | actionTxt = `${lineBreak}##### ${selectionTxt}`; 203 | break; 204 | case 'MD:h5 TAG:span': 205 | actionTxt = `${lineBreak}##### ${selectionTxt}`; 206 | break; 207 | case 'MD:h5 TAG:span*2': 208 | actionTxt = `${lineBreak}##### ${selectionTxt}`; 209 | break; 210 | case 'MD:h5 TAG:span*2 /%s/': 211 | actionTxt = `${lineBreak}##### ${selectionTxt}`; 212 | curPos = 'bol'; 213 | curChOffset = (() => { 214 | if (lineBreak) { 215 | return 28 + cssCls.length; 216 | } else { 217 | return 27 + cssCls.length; 218 | } 219 | })(); 220 | break; 221 | case 'MD:h6': 222 | actionTxt = `${lineBreak}###### ${selectionTxt}`; 223 | break; 224 | case 'MD:h6 TAG:span': 225 | actionTxt = `${lineBreak}###### ${selectionTxt}`; 226 | break; 227 | case 'MD:h6 TAG:span*2': 228 | actionTxt = `${lineBreak}###### ${selectionTxt}`; 229 | break; 230 | case 'MD:h6 TAG:span*2 /%s/': 231 | actionTxt = `${lineBreak}###### ${selectionTxt}`; 232 | curPos = 'bol'; 233 | curChOffset = (() => { 234 | if (lineBreak) { 235 | return 29 + cssCls.length; 236 | } else { 237 | return 28 + cssCls.length; 238 | } 239 | })(); 240 | break; 241 | case 'MD:*Italic*': 242 | actionTxt = `*${selectionTxt}*`; 243 | break; 244 | case 'MD:_Italic_': 245 | actionTxt = `_${selectionTxt}_`; 246 | break; 247 | case 'MD:**Bold**': 248 | actionTxt = `**${selectionTxt}**`; 249 | break; 250 | case 'MD:__Bold__': 251 | actionTxt = `__${selectionTxt}__`; 252 | break; 253 | case 'MD:Bulleted list': 254 | actionTxt = lineBreak + selectionTxt.replace(bolReg, '- '); 255 | break; 256 | case 'MD:Numbered list': 257 | actionTxt = lineBreak + selectionTxt.replace(bolReg, '1. '); 258 | break; 259 | case 'MD:Image': 260 | if (urlReg.test(selectionTxt)) { 261 | const linkTxt = selectionTxt.replace(spaceReg, '%20'); 262 | actionTxt = `![|100](${linkTxt})`; 263 | curPos = 'bol'; 264 | curChOffset = 2; 265 | } else { 266 | actionTxt = `![${selectionTxt}|100]()`; 267 | curChOffset = -1; 268 | } 269 | break; 270 | case 'MD:Link': 271 | if (urlReg.test(selectionTxt)) { 272 | const linkTxt = selectionTxt.replace(spaceReg, '%20'); 273 | actionTxt = `[](${linkTxt})`; 274 | curPos = 'bol'; 275 | curChOffset = 1; 276 | } else { 277 | actionTxt = `[${selectionTxt}]()`; 278 | curChOffset = -1; 279 | } 280 | break; 281 | case 'MD:Blockquotes': 282 | actionTxt = `${lineBreak}> ${selectionTxt}\n\n\\- `; 283 | break; 284 | case 'MD:Inline code': 285 | actionTxt = `\`${selectionTxt}\``; 286 | break; 287 | case 'MD:Code blocks': 288 | actionTxt = `${lineBreak}\`\`\`\n${selectionTxt}\n\`\`\``; 289 | curPos = 'bol'; 290 | curChOffset = (() => { 291 | if (lineBreak) { 292 | return 4; 293 | } else { 294 | return 3; 295 | } 296 | })(); 297 | break; 298 | case 'MD:Task list': 299 | actionTxt = lineBreak + selectionTxt.replace(bolReg, '- [ ] '); 300 | break; 301 | case 'MD:Strikethrough': 302 | actionTxt = `~~${selectionTxt}~~`; 303 | break; 304 | case 'MD:Highlight': 305 | actionTxt = `==${selectionTxt}==`; 306 | break; 307 | case 'MD:Footnotes': 308 | const txt = view.editor.getValue(); 309 | const foots = txt.match(/\[\^\d+\]/gim); 310 | const footNumArr = foots.map((value) => { 311 | return parseInt(value.replace(/\[\^|\]/gim, '')); 312 | }); 313 | const existsNum = Array.from(new Set(footNumArr)); 314 | existsNum.sort((a, b) => a - b); 315 | let footNum = 1; 316 | //First unused number 317 | for (let i = 0; i < existsNum.length; i++) { 318 | if (existsNum[i] == footNum) footNum++; 319 | } 320 | actionTxt = `${selectionTxt}[^${footNum}]`; 321 | const footLine = view.editor.lastLine() + 1; 322 | view.editor.setLine(footLine, `\n\n[^${footNum}]:`); 323 | curLineOffset = footLine; 324 | break; 325 | case 'MD:Inline footnotes': 326 | actionTxt = `${selectionTxt}^[]`; 327 | curChOffset = -1; 328 | break; 329 | case 'MD:Math': 330 | actionTxt = `$$\\begin{vmatrix}${selectionTxt}\\end{vmatrix}$$`; 331 | break; 332 | case 'MD:Comments': 333 | actionTxt = `%%${selectionTxt}%%`; 334 | break; 335 | case 'MD:Mermaid': 336 | actionTxt = `${lineBreak}\`\`\`mermaid\n${selectionTxt}\n\`\`\``; 337 | break; 338 | case 'MAC:UpperCase': 339 | actionTxt = selectionTxt.toLocaleUpperCase(); 340 | break; 341 | case 'MAC:LowerCase': 342 | actionTxt = selectionTxt.toLocaleLowerCase(); 343 | break; 344 | case 'MAC:Ascending order': 345 | actionTxt = selectionTxt 346 | .split(/\r|\r\n|\n/gim) 347 | .sort((a, b) => { 348 | const sa = String(a).replace(/(\d+)/g, (m) => m.padStart(30, '0')); 349 | const sb = String(b).replace(/(\d+)/g, (m) => m.padStart(30, '0')); 350 | return sa < sb ? -1 : sa > sb ? 1 : 0; 351 | }) 352 | .join('\n'); 353 | break; 354 | case 'MAC:Descending order': 355 | actionTxt = selectionTxt 356 | .split(/\r|\r\n|\n/gim) 357 | .sort((a, b) => { 358 | const sa = String(a).replace(/(\d+)/g, (m) => m.padStart(30, '0')); 359 | const sb = String(b).replace(/(\d+)/g, (m) => m.padStart(30, '0')); 360 | return sa > sb ? -1 : sa < sb ? 1 : 0; 361 | }) 362 | .join('\n'); 363 | break; 364 | case 'MAC:Upper Camel Case': 365 | actionTxt = selectionTxt 366 | .split(/\s|-|_/gim) 367 | .map((value) => { 368 | return value.charAt(0).toLocaleUpperCase() + value.slice(1).toLocaleLowerCase(); 369 | }) 370 | .join(''); 371 | break; 372 | case 'MAC:Lower Camel Case': 373 | actionTxt = selectionTxt 374 | .split(/\s|-|_/gim) 375 | .map((value, index) => { 376 | if (index === 0) { 377 | return value.toLocaleLowerCase(); 378 | } else { 379 | return value.charAt(0).toLocaleUpperCase() + value.slice(1).toLocaleLowerCase(); 380 | } 381 | }) 382 | .join(''); 383 | break; 384 | case 'MAC:Snake case': 385 | actionTxt = selectionTxt 386 | .replace(/([a-z])([A-Z])/g, '$1_$2') 387 | .split(/\s|-/gim) 388 | .map((value, index, row) => { 389 | const txt = value.toLocaleLowerCase(); 390 | if (index + 1 === row.length) { 391 | return txt; 392 | } else { 393 | return `${txt}_`; 394 | } 395 | }) 396 | .join(''); 397 | break; 398 | case 'MAC:Kebab case': 399 | actionTxt = selectionTxt 400 | .replace(/([a-z])([A-Z])/g, '$1-$2') 401 | .split(/\s|_/gim) 402 | .map((value, index, row) => { 403 | const txt = value.toLocaleLowerCase(); 404 | if (index + 1 === row.length) { 405 | return txt; 406 | } else { 407 | return `${txt}-`; 408 | } 409 | }) 410 | .join(''); 411 | break; 412 | case 'MAC:Space separated': 413 | actionTxt = selectionTxt 414 | .replace(/([a-z])([A-Z])/g, '$1 $2') 415 | .split(/-|_/gim) 416 | .map((value, index, row) => { 417 | if (index + 1 === row.length) { 418 | return value; 419 | } else { 420 | return `${value} `; 421 | } 422 | }) 423 | .join(''); 424 | break; 425 | case 'MAC:Remove blank lines': 426 | actionTxt = selectionTxt.replace(/^$(\r|\r\n|\n)/gim, ''); 427 | break; 428 | case 'MAC:Merge Multiple spaces to single space': 429 | actionTxt = selectionTxt.replace(/ +/gim, ' '); 430 | break; 431 | case 'MAC:Remove HTML tags': 432 | actionTxt = selectionTxt.replace(/<(".*?"|'.*?'|[^'"])*?>/gim, ''); 433 | break; 434 | default: 435 | // actionTxt = `<${action} class="${cssCls}">${selectionTxt}`; 436 | actionTxt = selectionTxt; 437 | break; 438 | } 439 | 440 | view.editor.replaceRange(actionTxt, selectionRange.anchor, selectionRange.head, selectionTxt); 441 | selectionList = view.editor.listSelections(); 442 | } 443 | 444 | const lastCursor = selectionList[selectionList.length - 1]; 445 | view.editor.blur(); 446 | view.editor.focus(); 447 | detachToolBar(toolBar); 448 | 449 | //Set cursor 450 | //bol 451 | if (curPos === 'bol') { 452 | const lastCursorCh = (() => { 453 | if (curCh) { 454 | return curCh; 455 | } else { 456 | return lastCursor.anchor.ch; 457 | } 458 | })(); 459 | view.editor.setCursor(lastCursor.anchor.line + curLineOffset, lastCursorCh + curChOffset); 460 | //eol 461 | } else { 462 | const lastCursorCh = (() => { 463 | if (curCh) { 464 | return curCh; 465 | } else { 466 | return lastCursor.head.ch; 467 | } 468 | })(); 469 | view.editor.setCursor(lastCursor.head.line + curLineOffset, lastCursorCh + curChOffset); 470 | } 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /utils/ToolBarBuilder.ts: -------------------------------------------------------------------------------- 1 | import { App, Editor, Plugin, PluginManifest } from 'obsidian'; 2 | import { TextToolbarSettings } from 'settings/settings'; 3 | import { ttAction } from './Action'; 4 | import { iwCheck } from './Utils'; 5 | 6 | export class ToolBarBuilder extends Plugin { 7 | toolBar: HTMLDivElement; 8 | settings: TextToolbarSettings; 9 | editor: Editor; 10 | constructor(app: App, manifest: PluginManifest, settings: TextToolbarSettings) { 11 | super(app, manifest); 12 | this.settings = settings; 13 | } 14 | get get() { 15 | const toolbar = createEl('div', { cls: 'text-tool-bar' }); 16 | const container = createEl('div', { cls: 'text-tool-bar-container' }); 17 | const toolBarLength = this.settings.action_set.length; 18 | const newttAction = new ttAction(this.app, this.manifest); 19 | 20 | for (let i = 0; i < toolBarLength; i++) { 21 | const btn = createEl('button', { cls: 'text-tool-bar-btn' }); 22 | btn.name = i.toString(); 23 | btn.type = 'button'; 24 | btn.tabIndex = 0; 25 | this.registerDomEvent(btn, 'click', (ev) => newttAction.replace(ev, this.settings, this.toolBar)); 26 | const label = (() => { 27 | if (this.settings.action_set[i].class) { 28 | return `${this.settings.action_set[i].class} text-tool-bar-label`; 29 | } else { 30 | return 'text-tool-bar-label'; 31 | } 32 | })(); 33 | const btnLabelContainer = createEl('div', { cls: 'text-tool-bar-label-container' }); 34 | const btnLabel = createEl('label', { cls: label }); 35 | btnLabel.innerText = this.settings.action_set[i].label; 36 | btnLabelContainer.appendChild(btnLabel); 37 | btn.appendChild(btnLabelContainer); 38 | container.appendChild(btn); 39 | toolbar.appendChild(container); 40 | } 41 | 42 | const wSize = iwCheck(this.settings); 43 | const button_size = parseInt(this.settings[`${wSize}button_size`]); 44 | const button_spacing = parseInt(this.settings[`${wSize}button_spacing`]); 45 | const button_font_size = this.settings[`${wSize}button_font_size`]; 46 | const toolbar_margin = parseInt(this.settings[`${wSize}toolbar_margin`]); 47 | const number_of_buttons_per_line = parseInt(this.settings[`${wSize}toolbar_number_of_buttons_per_line`]); 48 | const number_of_rows_in_the_toolbar = parseInt(this.settings[`${wSize}toolbar_number_of_rows`]); 49 | 50 | const barBgColor = this.settings.toolbar_background_color; 51 | const barBgOpacity = Math.round(this.settings.toolbar_background_opacity * 2.55) 52 | .toString(16) 53 | .padStart(2, '0'); 54 | const toolbarBg = barBgColor + barBgOpacity; 55 | const toolbarBlur = this.settings.toolbar_background_blur; 56 | const toolbarBorder = `${this.settings.toolbar_border_width}px solid ${this.settings.toolbar_border_color}`; 57 | const toolbarBorderRadius = this.settings.toolbar_border_radius; 58 | const btnDefaultBgColor = this.settings.button_default_color; 59 | const btnFocusBgColor = this.settings.button_focus_color; 60 | const btnHoverBgColor = this.settings.button_hover_color; 61 | const btnDefaultBorder = `${this.settings.button_border_width}px solid ${this.settings.button_border_default_color}`; 62 | const btnFocusBorder = `${this.settings.button_border_width}px solid ${this.settings.button_border_focus_color}`; 63 | const btnHoverBorder = `${this.settings.button_border_width}px solid ${this.settings.button_border_hover_color}`; 64 | const btnBorderRadius = this.settings.button_border_radius; 65 | const btnDefaultFontColor = this.settings.button_default_font_color; 66 | document.documentElement.style.setProperty('--tt-btn-size', button_size + 'px'); 67 | document.documentElement.style.setProperty('--tt-btn-gap', button_spacing + 'px'); 68 | document.documentElement.style.setProperty('--tt-btn-font-size', button_font_size + 'px'); 69 | document.documentElement.style.setProperty('--tt-toolbar-padding', toolbar_margin + 'px'); 70 | document.documentElement.style.setProperty('--tt-toolbar-bg-color', toolbarBg); 71 | document.documentElement.style.setProperty('--tt-toolbar-blur', `blur(${toolbarBlur}px)`); 72 | document.documentElement.style.setProperty('--tt-toolbar-border', toolbarBorder); 73 | document.documentElement.style.setProperty('--tt-toolbar-border-radius', `${toolbarBorderRadius}px`); 74 | document.documentElement.style.setProperty('--tt-btn-default-bg-color', btnDefaultBgColor); 75 | document.documentElement.style.setProperty('--tt-btn-focus-bg-color', btnFocusBgColor); 76 | document.documentElement.style.setProperty('--tt-btn-hover-bg-color', btnHoverBgColor); 77 | document.documentElement.style.setProperty('--tt-btn-default-border', btnDefaultBorder); 78 | document.documentElement.style.setProperty('--tt-btn-focus-border', btnFocusBorder); 79 | document.documentElement.style.setProperty('--tt-btn-hover-border', btnHoverBorder); 80 | document.documentElement.style.setProperty('--tt-btn-border-radius', `${btnBorderRadius}px`); 81 | document.documentElement.style.setProperty('--tt-btn-default-font-color', btnDefaultFontColor); 82 | 83 | document.documentElement.style.setProperty( 84 | '--tt-toolbar-buttons-per-line', 85 | 'repeat(' + number_of_buttons_per_line + ', 1fr)' 86 | ); 87 | document.documentElement.style.setProperty( 88 | '--tt-toolbar-number-of-rows', 89 | button_size * number_of_rows_in_the_toolbar + 90 | button_spacing * number_of_rows_in_the_toolbar - 91 | button_spacing + 92 | 'px' 93 | ); 94 | 95 | this.toolBar = toolbar; 96 | 97 | return this.toolBar; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /utils/Utils.ts: -------------------------------------------------------------------------------- 1 | import TextToolbar from 'main'; 2 | import { App, debounce, Editor, Plugin, PluginManifest } from 'obsidian'; 3 | import { TextToolbarSettings } from 'settings/settings'; 4 | 5 | export const arrayMove = (array: T[], fromIndex: number, toIndex: number): void => { 6 | if (toIndex < 0 || toIndex === array.length) { 7 | return; 8 | } 9 | const temp = array[fromIndex]; 10 | array[fromIndex] = array[toIndex]; 11 | array[toIndex] = temp; 12 | }; 13 | 14 | export const iwCheck = (settings: TextToolbarSettings) => { 15 | const iw = window.innerWidth; 16 | const lThreshold = parseInt(settings.l_window_width_thresholds); 17 | const sThreshold = parseInt(settings.s_window_width_thresholds); 18 | const wSize = (() => { 19 | if (iw >= lThreshold) { 20 | return 'l_'; 21 | } else if (iw <= sThreshold) { 22 | return 's_'; 23 | } else { 24 | return 'm_'; 25 | } 26 | })(); 27 | return wSize; 28 | }; 29 | 30 | export const fontSizeAdjust = (Class: string) => { 31 | const el = document.getElementsByClassName(Class); 32 | for (let i = 0; i < el.length; i++) { 33 | const label = el[i]; 34 | 35 | label.style.position = 'relative'; 36 | label.style.boxSizing = 'border-box'; 37 | label.style.margin = '0'; 38 | label.style.padding = '0'; 39 | label.style.maxWidth = '100%'; 40 | label.style.maxHeight = '100%'; 41 | label.style.borderWidth = '1em'; 42 | label.style.zIndex = 'auto'; 43 | 44 | const contentWidth = label.parentElement.clientWidth; 45 | const labelWidth = label.clientWidth; 46 | 47 | if (labelWidth > contentWidth) { 48 | label.style.left = -Math.floor((label.offsetWidth - contentWidth) / 2) + 'px'; 49 | 50 | const newLabelWidth = label.clientWidth; 51 | const scale = Math.fround((contentWidth / newLabelWidth) * 10) / 10; 52 | 53 | label.style.transform = `scale(${scale})`; 54 | } 55 | if (label.offsetHeight > label.parentElement.clientWidth) { 56 | label.style.top = -Math.floor((label.offsetHeight - label.parentElement.clientWidth) / 2) + 'px'; 57 | } 58 | } 59 | }; 60 | 61 | export const toolBarPosInit = (toolBar: HTMLDivElement, settings: TextToolbarSettings) => { 62 | const wSize = iwCheck(settings); 63 | 64 | const display_position = settings[`${wSize}toolbar_display_position`]; 65 | const vertical_offset = parseInt(settings[`${wSize}toolbar_vertical_offset`]); 66 | const horizontal_offset = parseInt(settings[`${wSize}toolbar_horizontal_offset`]); 67 | 68 | if (display_position === 'top') { 69 | const editorContainer = document.querySelector('div.workspace-split.mod-vertical.mod-root'); 70 | const editorPosition = editorContainer.getBoundingClientRect(); 71 | const newPositionTop = vertical_offset + 'px'; 72 | const newPositionLeft = 73 | editorPosition.left + editorContainer.clientWidth / 2 - toolBar.clientWidth / 2 + horizontal_offset + 'px'; 74 | document.documentElement.style.setProperty('--tt-toolbar-top', newPositionTop); 75 | document.documentElement.style.setProperty('--tt-toolbar-bottom', 'unset'); 76 | document.documentElement.style.setProperty('--tt-toolbar-left', newPositionLeft); 77 | document.documentElement.style.setProperty('--tt-toolbar-right', 'unset'); 78 | } else if (display_position === 'bottom') { 79 | const editorContainer = document.querySelector('div.workspace-split.mod-vertical.mod-root'); 80 | const editorPosition = editorContainer.getBoundingClientRect(); 81 | const newPositionBottom = vertical_offset + 'px'; 82 | const newPositionLeft = 83 | editorPosition.left + editorContainer.clientWidth / 2 - toolBar.clientWidth / 2 + horizontal_offset + 'px'; 84 | document.documentElement.style.setProperty('--tt-toolbar-top', 'unset'); 85 | document.documentElement.style.setProperty('--tt-toolbar-bottom', newPositionBottom); 86 | document.documentElement.style.setProperty('--tt-toolbar-left', newPositionLeft); 87 | document.documentElement.style.setProperty('--tt-toolbar-right', 'unset'); 88 | } else if (display_position === 'left') { 89 | const bodyHeight = document.body.clientHeight; 90 | const newPositionTop = bodyHeight / 2 - toolBar.clientHeight / 2 + vertical_offset + 'px'; 91 | const newPositionLeft = horizontal_offset + 'px'; 92 | document.documentElement.style.setProperty('--tt-toolbar-top', newPositionTop); 93 | document.documentElement.style.setProperty('--tt-toolbar-bottom', 'unset'); 94 | document.documentElement.style.setProperty('--tt-toolbar-left', newPositionLeft); 95 | document.documentElement.style.setProperty('--tt-toolbar-right', 'unset'); 96 | } else if (display_position === 'right') { 97 | const bodyHeight = document.body.clientHeight; 98 | const newPositionTop = bodyHeight / 2 - toolBar.clientHeight / 2 + vertical_offset + 'px'; 99 | const newPositionRight = horizontal_offset + 'px'; 100 | document.documentElement.style.setProperty('--tt-toolbar-top', newPositionTop); 101 | document.documentElement.style.setProperty('--tt-toolbar-bottom', 'unset'); 102 | document.documentElement.style.setProperty('--tt-toolbar-left', 'unset'); 103 | document.documentElement.style.setProperty('--tt-toolbar-right', newPositionRight); 104 | } 105 | }; 106 | 107 | export const detachToolBar = (toolBar: HTMLDivElement) => { 108 | if (document.getElementsByClassName('text-tool-bar')[0]) { 109 | document 110 | .getElementsByClassName('text-tool-bar')[0] 111 | .removeEventListener('wheel', TextToolbar.wheelScroll.handler, true); 112 | document.removeEventListener('keydown', TextToolbar.keyScroll.handler, true); 113 | document.querySelector('body').removeChild(toolBar); 114 | const view = document.getElementsByClassName('cm-content')[0]; 115 | if (view) view.focus(); 116 | } 117 | }; 118 | 119 | export class WheelScroll extends Plugin { 120 | toolBar: HTMLDivElement; 121 | settings: TextToolbarSettings; 122 | editor: Editor; 123 | handler: (ev: WheelEvent) => void; 124 | constructor(app: App, manifest: PluginManifest, settings: TextToolbarSettings) { 125 | super(app, manifest); 126 | this.settings = settings; 127 | } 128 | snap(ev: WheelEvent) { 129 | const snap = debounce( 130 | (ev: WheelEvent) => { 131 | const wSize = iwCheck(this.settings); 132 | const container = document.getElementsByClassName('text-tool-bar-container'); 133 | const button_size = parseInt(this.settings[`${wSize}button_size`]); 134 | const button_spacing = parseInt(this.settings[`${wSize}button_spacing`]); 135 | const number_of_rows_in_the_toolbar = parseInt(this.settings[`${wSize}toolbar_number_of_rows`]); 136 | const clientHeight = container[0].clientHeight; 137 | const scrollHeight = container[0].scrollHeight; 138 | const isScrollBottom = () => { 139 | if (scrollHeight - (clientHeight + container[0].scrollTop) <= button_spacing * 1.2) { 140 | return true; 141 | } 142 | }; 143 | const isScrollTop = () => { 144 | if (container[0].scrollTop <= button_spacing * 1.2) return true; 145 | }; 146 | //down 147 | if (ev.deltaY > 50) { 148 | if (isScrollBottom()) { 149 | container[0].scrollTo({ 150 | top: 0, 151 | left: 0, 152 | behavior: 'smooth', 153 | }); 154 | } else if (!isScrollBottom()) { 155 | container[0].scrollBy({ 156 | top: (button_size + button_spacing) * number_of_rows_in_the_toolbar, 157 | left: 0, 158 | behavior: 'smooth', 159 | }); 160 | } 161 | //up 162 | } else if (ev.deltaY < -50) { 163 | if (isScrollTop()) { 164 | container[0].scrollTo({ 165 | top: scrollHeight, 166 | left: 0, 167 | behavior: 'smooth', 168 | }); 169 | } else if (!isScrollTop()) { 170 | container[0].scrollBy({ 171 | top: -(button_size + button_spacing) * number_of_rows_in_the_toolbar, 172 | left: 0, 173 | behavior: 'smooth', 174 | }); 175 | } 176 | } 177 | }, 178 | 100, 179 | true 180 | ); 181 | snap(ev); 182 | } 183 | } 184 | 185 | export class KeyScroll extends Plugin { 186 | settings: TextToolbarSettings; 187 | editor: Editor; 188 | handler: (ev: KeyboardEvent) => void; 189 | isFromBtm: boolean; 190 | constructor(app: App, manifest: PluginManifest, settings: TextToolbarSettings) { 191 | super(app, manifest); 192 | this.settings = settings; 193 | } 194 | 195 | snap(ev: KeyboardEvent) { 196 | const wSize = iwCheck(this.settings); 197 | const button_size = parseInt(this.settings[`${wSize}button_size`]); 198 | const button_spacing = parseInt(this.settings[`${wSize}button_spacing`]); 199 | const number_of_buttons_per_line = parseInt(this.settings[`${wSize}toolbar_number_of_buttons_per_line`]); 200 | const number_of_rows_in_the_toolbar = parseInt(this.settings[`${wSize}toolbar_number_of_rows`]); 201 | const activeEl = document.activeElement; 202 | const activeNum = parseInt(activeEl.name); 203 | const activeNumP = activeNum + 1; 204 | const container = document.getElementsByClassName('text-tool-bar-container'); 205 | const btn = document.getElementsByClassName('text-tool-bar-btn'); 206 | const btn0 = btn[0]; 207 | 208 | //Remove toolbar with escape key 209 | if (ev.key === 'Escape') { 210 | detachToolBar(TextToolbar.toolBar); 211 | return; 212 | } 213 | //If the button is unfocused, focus with the tab key. 214 | if (activeEl.className !== 'text-tool-bar-btn' && ev.key === 'Tab') { 215 | btn0.focus(); 216 | } 217 | 218 | const inRange = (min: number, max: number, num: number) => min <= num && num <= max; 219 | const btnCount = container[0].childElementCount; 220 | let lastMultiple: number; 221 | 222 | if (btnCount % number_of_buttons_per_line == 0) { 223 | lastMultiple = btnCount; 224 | } else { 225 | lastMultiple = (Math.floor(btnCount / number_of_buttons_per_line) + 1) * number_of_buttons_per_line; 226 | } 227 | 228 | const page = number_of_buttons_per_line * number_of_rows_in_the_toolbar; 229 | const pageCount = Math.floor(lastMultiple / page); 230 | const pageBreak: number[] = new Array(); 231 | 232 | for (let i = 0; i <= lastMultiple; i += page) { 233 | pageBreak.push(i); 234 | } 235 | pageBreak.shift(); 236 | 237 | const isFirstPage = () => { 238 | const min = 1; 239 | let max; 240 | if (!this.isFromBtm) { 241 | max = page; 242 | } else if (this.isFromBtm) { 243 | max = lastMultiple - page * pageCount; 244 | } 245 | if (inRange(min, max, activeNumP)) { 246 | return true; 247 | } else { 248 | return false; 249 | } 250 | }; 251 | 252 | const isLastPage = () => { 253 | let min; 254 | if (pageBreak[pageBreak.length - 1] < btnCount) { 255 | min = pageBreak[pageBreak.length - 1] + 1; 256 | } else { 257 | min = lastMultiple - page + 1; 258 | } 259 | 260 | const max = lastMultiple; 261 | if (inRange(min, max, activeNumP)) { 262 | this.isFromBtm = true; 263 | } else if (isFirstPage()) { 264 | this.isFromBtm = false; 265 | } 266 | }; 267 | isLastPage(); 268 | 269 | const isPageBreak = () => { 270 | if (ev.key === 'ArrowDown' && !this.isFromBtm) { 271 | for (let i = 0; i < pageBreak.length; i++) { 272 | const min = pageBreak[i] - number_of_buttons_per_line + 1; 273 | const max = pageBreak[i]; 274 | if (inRange(min, max, activeNumP)) { 275 | return true; 276 | } 277 | } 278 | } else if (ev.key === 'ArrowDown' && this.isFromBtm) { 279 | const newArr = pageBreak.map((value) => { 280 | return value + number_of_buttons_per_line; 281 | }); 282 | for (let i = 0; i < newArr.length; i++) { 283 | const min = newArr[i] - number_of_buttons_per_line + 1; 284 | const max = newArr[i]; 285 | const num = lastMultiple - activeNumP + 1; 286 | if (inRange(min, max, num)) { 287 | return true; 288 | } 289 | } 290 | } else if (ev.key === 'ArrowUp' && !this.isFromBtm) { 291 | const newArr = pageBreak.map((value) => { 292 | return value + number_of_buttons_per_line; 293 | }); 294 | for (let i = 0; i < newArr.length; i++) { 295 | const min = newArr[i] - number_of_buttons_per_line + 1; 296 | const max = newArr[i]; 297 | if (inRange(min, max, activeNumP)) { 298 | return true; 299 | } 300 | } 301 | } else if (ev.key === 'ArrowUp' && this.isFromBtm) { 302 | for (let i = 0; i < pageBreak.length; i++) { 303 | const min = pageBreak[i] - number_of_buttons_per_line + 1; 304 | const max = pageBreak[i]; 305 | const num = lastMultiple - activeNumP + 1; 306 | if (inRange(min, max, num)) { 307 | return true; 308 | } 309 | } 310 | } else if (ev.key === 'ArrowRight' && !this.isFromBtm) { 311 | for (let i = 0; i < pageBreak.length; i++) { 312 | if (activeNumP === pageBreak[i]) { 313 | return true; 314 | } 315 | } 316 | } else if (ev.key === 'ArrowRight' && this.isFromBtm) { 317 | const num = lastMultiple - activeNumP + 1; 318 | const arr = pageBreak.map((value) => { 319 | return value + 1; 320 | }); 321 | for (let i = 0; i < arr.length; i++) { 322 | if (num === arr[i]) { 323 | return true; 324 | } 325 | } 326 | } else if (ev.key === 'ArrowLeft' && !this.isFromBtm) { 327 | const arr = pageBreak.map((value) => { 328 | return value + 1; 329 | }); 330 | for (let i = 0; i < arr.length; i++) { 331 | if (activeNumP === arr[i]) { 332 | return true; 333 | } 334 | } 335 | } else if (ev.key === 'ArrowLeft' && this.isFromBtm) { 336 | const num = lastMultiple - activeNumP + 1; 337 | for (let i = 0; i < pageBreak.length; i++) { 338 | if (num === pageBreak[i]) { 339 | return true; 340 | } 341 | } 342 | } 343 | }; 344 | 345 | const isFirstRow = () => { 346 | const min = 1; 347 | const max = number_of_buttons_per_line; 348 | if (inRange(min, max, activeNumP)) { 349 | return true; 350 | } 351 | }; 352 | 353 | const isLastRow = () => { 354 | const min = lastMultiple - number_of_buttons_per_line + 1; 355 | const max = lastMultiple; 356 | if (inRange(min, max, activeNumP)) { 357 | return true; 358 | } 359 | }; 360 | 361 | const moveNext = () => { 362 | ev.preventDefault(); 363 | 364 | const nextNum = activeNum + 1; 365 | //If it's the last button, go back to the first button. 366 | if (nextNum === container[0].childElementCount) { 367 | const nextEl = container[0].firstElementChild; 368 | nextEl.focus({ preventScroll: true }); 369 | container[0].scrollTo({ 370 | top: 0, 371 | left: 0, 372 | behavior: 'smooth', 373 | }); 374 | //If it's a page break, go to the next page. 375 | } else if (isPageBreak()) { 376 | const nextEl = container[0].children[nextNum]; 377 | nextEl.focus({ preventScroll: true }); 378 | container[0].scrollBy({ 379 | top: (button_size + button_spacing) * number_of_rows_in_the_toolbar, 380 | left: 0, 381 | behavior: 'smooth', 382 | }); 383 | //Next button exists 384 | } else if (nextNum < container[0].childElementCount) { 385 | const nextEl = container[0].children[nextNum]; 386 | nextEl.focus(); 387 | } 388 | }; 389 | 390 | const movePrev = () => { 391 | ev.preventDefault(); 392 | const prevNum = activeNum - 1; 393 | //If it's the first button, go to the last button. 394 | if (activeNum === 0) { 395 | const prevEl = container[0].lastElementChild; 396 | prevEl.focus({ preventScroll: true }); 397 | container[0].scrollTo({ 398 | top: container[0].scrollHeight, 399 | left: 0, 400 | behavior: 'smooth', 401 | }); 402 | //If it's a page break, go to the previous page. 403 | } else if (isPageBreak()) { 404 | const prevEl = container[0].children[prevNum]; 405 | prevEl.focus({ preventScroll: true }); 406 | container[0].scrollBy({ 407 | top: -(button_size + button_spacing) * number_of_rows_in_the_toolbar, 408 | left: 0, 409 | behavior: 'smooth', 410 | }); 411 | //Focus on previous button 412 | } else if (activeNum !== 0) { 413 | const prevEl = container[0].children[prevNum]; 414 | prevEl.focus(); 415 | } 416 | }; 417 | const moveDown = () => { 418 | ev.preventDefault(); 419 | const nextNum = activeNum + number_of_buttons_per_line; 420 | const nextEl = container[0].children[nextNum]; 421 | 422 | //No next button, but if there is a next row, focus on the last button. 423 | if (!nextEl && !isLastRow()) { 424 | const prevEl = container[0].lastElementChild; 425 | prevEl.focus({ preventScroll: true }); 426 | container[0].scrollTo({ 427 | top: container[0].scrollHeight, 428 | left: 0, 429 | behavior: 'smooth', 430 | }); 431 | } 432 | //If there is no next button, go back to the beginning. 433 | else if (!nextEl) { 434 | const nextNum = activeNum % number_of_buttons_per_line; 435 | const nextEl = container[0].children[nextNum]; 436 | nextEl.focus({ preventScroll: true }); 437 | container[0].scrollTo({ 438 | top: 0, 439 | left: 0, 440 | behavior: 'smooth', 441 | }); 442 | //If it's a page break, turn the page. 443 | } else if (isPageBreak()) { 444 | const nextNum = activeNum + number_of_buttons_per_line; 445 | const nextEl = container[0].children[nextNum]; 446 | nextEl.focus({ preventScroll: true }); 447 | container[0].scrollBy({ 448 | top: (button_size + button_spacing) * number_of_rows_in_the_toolbar, 449 | left: 0, 450 | behavior: 'smooth', 451 | }); 452 | //Focus on the button below 453 | } else { 454 | nextEl.focus({ preventScroll: true }); 455 | } 456 | }; 457 | 458 | const moveUp = () => { 459 | ev.preventDefault(); 460 | const prevNum = activeNum - number_of_buttons_per_line; 461 | const prevEl = container[0].children[prevNum]; 462 | const min = lastMultiple - number_of_buttons_per_line; 463 | const max = lastMultiple; 464 | const arr = new Array(); 465 | for (let i = min; i < max; i++) { 466 | arr.push(i); 467 | } 468 | if (isFirstRow()) { 469 | const prevEl = container[0].children[arr[activeNum]]; 470 | 471 | if (prevEl) { 472 | prevEl.focus({ preventScroll: true }); 473 | container[0].scrollTo({ 474 | top: container[0].scrollHeight, 475 | left: 0, 476 | behavior: 'smooth', 477 | }); 478 | } else if (!prevEl) { 479 | const prevEl = container[0].lastElementChild; 480 | prevEl.focus({ preventScroll: true }); 481 | container[0].scrollTo({ 482 | top: container[0].scrollHeight, 483 | left: 0, 484 | behavior: 'smooth', 485 | }); 486 | } 487 | } else if (isPageBreak()) { 488 | prevEl.focus({ preventScroll: true }); 489 | container[0].scrollBy({ 490 | top: -(button_size + button_spacing) * number_of_rows_in_the_toolbar, 491 | left: 0, 492 | behavior: 'smooth', 493 | }); 494 | } else { 495 | prevEl.focus({ preventScroll: true }); 496 | } 497 | }; 498 | 499 | //tab 500 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'Tab' && !ev.shiftKey) { 501 | moveNext(); 502 | } 503 | //shift+tab 504 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'Tab' && ev.shiftKey) { 505 | movePrev(); 506 | } 507 | //ArrowRight 508 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'ArrowRight') { 509 | moveNext(); 510 | } 511 | //ArrowLeft 512 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'ArrowLeft') { 513 | movePrev(); 514 | } 515 | //ArrowDown 516 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'ArrowDown') { 517 | moveDown(); 518 | } 519 | //ArrowUp 520 | if (activeEl.className == 'text-tool-bar-btn' && ev.key === 'ArrowUp') { 521 | moveUp(); 522 | } 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.9.7", 3 | "1.0.1": "0.12.0" 4 | } 5 | --------------------------------------------------------------------------------