├── .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 | 
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 | 
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 | 
56 |
57 | Tag Example:
58 |
59 | span: `selected text`
60 |
61 | div*2: `
`
62 |
63 | div span /%s/: `selected text
`
64 |
65 | 2. Actions with the prefix 'MD' add Markdown elements.
66 | 
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 | 
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: `,
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 = ``;
77 | break;
78 | case 'TAG:div*2 /%s/':
79 | actionTxt = ``;
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 = ``;
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}${action}>`;
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 |
--------------------------------------------------------------------------------