├── .gitignore ├── LICENSE ├── README.md ├── images └── demo.gif ├── manifest.json ├── package.json ├── src ├── commands │ ├── command.ts │ └── commandsManager.ts ├── event │ └── eventHandler.ts ├── main.ts ├── modals │ ├── noteWidthModal.ts │ └── progressBarModal.ts ├── note │ ├── noteWidthManager.ts │ └── yamlFrontMatterProcessor.ts ├── settings │ ├── donationButton.ts │ ├── settingsManager.ts │ └── settingsTab.ts ├── statusbar │ ├── statusBarManager.ts │ └── wrapperManager.ts ├── ui │ ├── domElementManager.ts │ ├── uiElementCreator.ts │ └── uiManager.ts └── utility │ ├── config.ts │ ├── constants.ts │ ├── lokiDatabase.ts │ ├── utilities.ts │ └── uuidGenerator.ts ├── styles.css └── versions.json /.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 | # Don't include eslint stuff 16 | .eslintignore 17 | .eslintrc 18 | 19 | # Don't include editor configs 20 | .editorconfig 21 | 22 | # Exclude sourcemaps 23 | *.map 24 | 25 | # Don't include npm stuff 26 | .npmrc 27 | 28 | # obsidian 29 | data.json 30 | noteWidthDatabase.json 31 | 32 | # Exclude macOS Finder (System Explorer) View States 33 | .DS_Store 34 | 35 | testing 36 | condition.txt 37 | 38 | tsconfig.json 39 | version-bump.mjs 40 | esbuild.config.mjs 41 | 42 | #misc 43 | app.json 44 | appearance.json 45 | workspace.json 46 | hotkeys.json 47 | core-plugins.json 48 | core-plugins-migration.json 49 | package-lock.json 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2023 0skater0 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 Custom Note Width 2 | 3 | A plugin for Obsidian that enables you to easily adjust the editor's line width on a note-by-note basis. 4 | All these following options ensure that users have a tailored experience, making line width adjustments both efficient and user-friendly. 5 | To make this task more intuitive and adaptable to your preferences, the plugin offers several methods: 6 | 7 | - Employ a straightforward slider or textbox situated in the status bar for quick and visual adjustments. 8 | - For users who prefer to work with YAML front matter there is an option to set a custom width through YAML, allowing you to maintain specific formatting or structures in your notes. 9 | - Integrated commands have been added, which can be executed directly within the Obsidian interface. This is especially handy for users who prefer quick actions or are accustomed to using keyboard shortcuts, offering a seamless way to modify the line width without diving into settings or codes. 10 | 11 | ![Demo GIF](/images/demo.gif) 12 | 13 | ## Features 14 | 15 | - Customize line width for individual notes(or all notes) using a convenient slider in the status bar. 16 | - Easily increase or decrease line width to tailor your editing experience. 17 | - User-friendly interface for intuitive usage. 18 | - Fully customizable through settings for personalized adjustments. 19 | 20 | ## Usage 21 | 22 | Once you enable the plugin in the settings menu, a slider will appear in the bottom right status bar. 23 | With this plugin, you can easily make real-time adjustments to the width of notes using either the slider or the textfield, ensuring a smooth and seamless editing experience. 24 | 25 | ## Settings 26 | 27 | ### General Settings 28 | 29 | - Enable or disable to adjust width on a note-by-note basis 30 | - Enable or disable to adjust the default note width 31 | - Enable or disable to adjust note width via YAML front matter 32 | - When both "Change width for each note" and "Enable custom width via YAML front matter" are active, you will be able to choose the order of execution via this priority list: 33 | It first checks the saved note-by-note width setting; if unspecified, it then looks to the YAML front matter. 34 | 35 | ### Style Settings 36 | 37 | - Enable or disable the slider 38 | - Enable or disable the textbox 39 | - Adjust the slider width 40 | 41 | ## Installation 42 | 43 | The plugin can be found in the Community Plugins directory which can be accessed from the Settings pane under Third Party Plugins. 44 | 45 | ## Manual installation 46 | 47 | 1. Download custom-note-width.zip located at [latest release](https://github.com/0skater0/obsidian-custom-note-width/releases) 48 | 2. Extract the obsidian-custom-note-width folder from the zip to your vault's plugins
folder: `/.obsidian/plugins/` Note: On some machines the `.obsidian` folder may be hidden. On MacOS you should be able to press `Command+Shift+Dot` to show the folder in Finder. 49 | 3. Reload Obsidian 50 | 4. If prompted about Safe Mode, you can disable safe mode and enable the plugin. 51 | 52 | ## Important 53 | 54 | ‼️‼️ IT'S IMPORTANT TO REGULARLY MAKE BACKUPS! DON'T FORGET TO PROTECT YOUR DATA! 🥹👍 ‼️‼️ 55 | 56 | ## Feedback and Support 57 | 58 | If you encounter any issues or have suggestions for improvements, please create a new [issue](https://github.com/0skater0/obsidian-custom-note-width/issues) on the GitHub repository. 59 | 60 | ## License 61 | 62 | This plugin is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. 63 | Please note that this plugin is provided as-is without any warranty. Use it at your own risk. 64 | 65 | ### Support Me & my work 🙏 66 | 67 | This plugin is offered completely free of charge‼️ 68 | 69 | If you fancy buying me a coffee to fuel more creations like this plugin, you have the option to do so by clicking the button below. 70 | 71 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/P5P7NLC40) 72 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0skater0/obsidian-custom-note-width/9b9025cd0b1cabb2c2d4755740c7501effe90d6a/images/demo.gif -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "custom-note-width", 3 | "name": "Custom Note Width", 4 | "version": "1.0.2", 5 | "minAppVersion": "0.15.0", 6 | "description": "Let's you adjust the line width on a note-by-note basis.", 7 | "author": "0skater0", 8 | "authorUrl": "https://github.com/0skater0", 9 | "fundingUrl": "https://ko-fi.com/skater_", 10 | "isDesktopOnly": true 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-note-width", 3 | "version": "1.0.2", 4 | "description": "A plugin for Obsidian that enables you to easily adjust the editor's line width on a note-by-note basis. You can do this by using a simple slider located in the status bar or the adjacent text box.", 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/js-yaml": "^4.0.5", 16 | "@types/lokijs": "^1.5.8", 17 | "@types/node": "^16.11.6", 18 | "@typescript-eslint/eslint-plugin": "^5.62.0", 19 | "@typescript-eslint/parser": "5.29.0", 20 | "builtin-modules": "3.3.0", 21 | "esbuild": "0.17.3", 22 | "eslint": "^8.7.0", 23 | "eslint-config-standard-with-typescript": "^37.0.0", 24 | "eslint-plugin-import": "^2.28.0", 25 | "eslint-plugin-n": "^16.0.1", 26 | "eslint-plugin-promise": "^6.1.1", 27 | "obsidian": "latest", 28 | "prettier": "^2.5.1", 29 | "tslib": "2.4.0", 30 | "typescript": "^4.7.4" 31 | }, 32 | "dependencies": { 33 | "js-yaml": "^4.1.0", 34 | "lokijs": "^1.5.12" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/command.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import { COMMANDS } from "src/utility/constants"; 3 | import { isActiveLeafMarkdown } from "src/utility/utilities"; 4 | 5 | /** 6 | * Abstract base class for commands. 7 | */ 8 | export abstract class Command 9 | { 10 | /** The command identifier. */ 11 | id: string; 12 | /** Display name of the command. */ 13 | name: string; 14 | /** Title of the modal associated with the command. */ 15 | modalTitle: string; 16 | 17 | /** 18 | * Constructs a new Command instance. 19 | * @param id - The command identifier. 20 | * @param name - Display name of the command. 21 | * @param modalTitle - Title of the modal associated with the command. 22 | */ 23 | constructor(id: string, name: string, modalTitle: string) 24 | { 25 | this.id = id; 26 | this.name = name; 27 | this.modalTitle = modalTitle; 28 | } 29 | 30 | /** 31 | * Executes the command. 32 | * @param arg - The argument for the command. 33 | */ 34 | abstract execute(arg: number): void; 35 | 36 | /** 37 | * Determines if the command can be executed. 38 | * @returns A boolean indicating if the command can be executed. 39 | */ 40 | abstract canExecute(): boolean; 41 | } 42 | 43 | /** 44 | * Command to change the default note width. 45 | */ 46 | export class ChangeDefaultNoteWidthCommand extends Command 47 | { 48 | /** Reference to the CustomNoteWidth plugin. */ 49 | plugin: CustomNoteWidth; 50 | 51 | /** 52 | * Constructs a new ChangeDefaultNoteWidthCommand instance. 53 | * @param plugin - Reference to the CustomNoteWidth plugin. 54 | */ 55 | constructor(plugin: CustomNoteWidth) 56 | { 57 | super(COMMANDS.CHANGE_DEFAULT_NOTE_WIDTH.ID, COMMANDS.CHANGE_DEFAULT_NOTE_WIDTH.NAME, COMMANDS.CHANGE_DEFAULT_NOTE_WIDTH.MODAL_TITLE); 58 | this.plugin = plugin; 59 | } 60 | 61 | /** @inheritdoc */ 62 | public execute(arg: number): void 63 | { 64 | this.plugin.noteWidthManager.changeDefaultNoteWidth(arg); 65 | } 66 | 67 | /** @inheritdoc */ 68 | public canExecute(): boolean 69 | { 70 | return this.plugin.settingsManager.getEnableChangeDefaultNoteWidth() && this.plugin.settingsManager.getEnableSaveWidthIndividually(); 71 | } 72 | } 73 | 74 | /** 75 | * Command to change the width of all notes. 76 | */ 77 | export class ChangeAllNoteWidthCommand extends Command 78 | { 79 | /** Reference to the CustomNoteWidth plugin. */ 80 | plugin: CustomNoteWidth; 81 | 82 | /** 83 | * Constructs a new ChangeAllNoteWidthCommand instance. 84 | * @param plugin - Reference to the CustomNoteWidth plugin. 85 | */ 86 | constructor(plugin: CustomNoteWidth) 87 | { 88 | super(COMMANDS.CHANGE_ALL_NOTE_WIDTH.ID, COMMANDS.CHANGE_ALL_NOTE_WIDTH.NAME, COMMANDS.CHANGE_ALL_NOTE_WIDTH.MODAL_TITLE); 89 | this.plugin = plugin; 90 | } 91 | 92 | /** @inheritdoc */ 93 | public execute(arg: number): void 94 | { 95 | this.plugin.noteWidthManager.changeAllNoteWidth(arg); 96 | } 97 | 98 | /** @inheritdoc */ 99 | public canExecute(): boolean 100 | { 101 | return true; 102 | } 103 | } 104 | 105 | /** 106 | * Command to change the width of a single note. 107 | */ 108 | export class ChangeNoteWidthCommand extends Command 109 | { 110 | /** Reference to the CustomNoteWidth plugin. */ 111 | plugin: CustomNoteWidth; 112 | 113 | /** 114 | * Constructs a new ChangeNoteWidthCommand instance. 115 | * @param plugin - Reference to the CustomNoteWidth plugin. 116 | */ 117 | constructor(plugin: CustomNoteWidth) 118 | { 119 | super(COMMANDS.CHANGE_NOTE_WIDTH.ID, COMMANDS.CHANGE_NOTE_WIDTH.NAME, COMMANDS.CHANGE_NOTE_WIDTH.MODAL_TITLE); 120 | this.plugin = plugin; 121 | } 122 | 123 | /** @inheritdoc */ 124 | public execute(arg: number): void 125 | { 126 | this.plugin.noteWidthManager.changeNoteWidth(arg); 127 | } 128 | 129 | /** @inheritdoc */ 130 | public canExecute(): boolean 131 | { 132 | return isActiveLeafMarkdown(this.plugin.app); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/commands/commandsManager.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import NoteWidthModal from "src/modals/noteWidthModal"; 3 | import { Command, ChangeNoteWidthCommand, ChangeDefaultNoteWidthCommand, ChangeAllNoteWidthCommand } from "src/commands/command"; 4 | 5 | /** 6 | * Interface representing the active commands. 7 | */ 8 | interface ActiveCommands 9 | { 10 | [id: string]: boolean; 11 | } 12 | 13 | /** 14 | * Manages the registration and execution of commands. 15 | */ 16 | export default class CommandsManager 17 | { 18 | /** Reference to the CustomNoteWidth plugin. */ 19 | plugin: CustomNoteWidth; 20 | /** Object representing the active state of commands. */ 21 | activeCommands: ActiveCommands = {}; 22 | /** Array containing all the commands. */ 23 | commands: Command[] = []; 24 | 25 | /** 26 | * Constructs a new CommandsManager instance. 27 | * @param plugin - Reference to the CustomNoteWidth plugin. 28 | */ 29 | constructor(plugin: CustomNoteWidth) 30 | { 31 | this.plugin = plugin; 32 | this.commands.push(new ChangeNoteWidthCommand(plugin)); 33 | this.commands.push(new ChangeDefaultNoteWidthCommand(plugin)); 34 | this.commands.push(new ChangeAllNoteWidthCommand(plugin)); 35 | 36 | this.registerCommands(); 37 | } 38 | 39 | /** 40 | * Registers all the commands in the commands array. 41 | */ 42 | private registerCommands(): void 43 | { 44 | for (const command of this.commands) 45 | { 46 | this.addCommand(command); 47 | } 48 | } 49 | 50 | /** 51 | * Adds and activates a command. 52 | * @param command - The command to be added and activated. 53 | */ 54 | private addCommand(command: Command): void 55 | { 56 | if (this.activeCommands.hasOwnProperty(command.id)) return; 57 | 58 | this.activeCommands[command.id] = true; 59 | const commandCallback = this.commandCallback.bind(this, command.execute.bind(command), command.modalTitle); 60 | 61 | this.plugin.addCommand({ 62 | id: command.id, 63 | name: command.name, 64 | callback: () => 65 | { 66 | if (this.activeCommands[command.id]) 67 | { 68 | commandCallback(); 69 | } 70 | }, 71 | checkCallback: (checking: boolean) => 72 | { 73 | if (!this.activeCommands[command.id] || (command.canExecute && !command.canExecute())) 74 | { 75 | return false; 76 | } 77 | if (checking) 78 | { 79 | return true; 80 | } 81 | commandCallback(); 82 | return true; 83 | }, 84 | }); 85 | } 86 | 87 | /** 88 | * Callback for command execution. Opens the NoteWidthModal and then executes the command action. 89 | * @param commandAction - The action to be executed by the command. 90 | * @param modalTitle - The title of the modal. 91 | */ 92 | private commandCallback(commandAction: (arg: number) => void, modalTitle: string): void 93 | { 94 | new NoteWidthModal(this.plugin.app, (number) => { commandAction(number); }, modalTitle).open(); 95 | } 96 | 97 | /** 98 | * Disables a command. 99 | * @param id - The identifier of the command to be disabled. 100 | */ 101 | public disableCommand(id: string): void 102 | { 103 | this.activeCommands[id] = false; 104 | } 105 | 106 | /** 107 | * Enables a command. 108 | * @param id - The identifier of the command to be enabled. 109 | */ 110 | public enableCommand(id: string): void 111 | { 112 | if (this.activeCommands.hasOwnProperty(id)) 113 | { 114 | this.activeCommands[id] = true; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/event/eventHandler.ts: -------------------------------------------------------------------------------- 1 | import { App, Notice, WorkspaceLeaf, debounce } from "obsidian"; 2 | import CustomNoteWidth from "src/main"; 3 | import { NOTICES } from "src/utility/constants"; 4 | import { isActiveLeafMarkdown } from "src/utility/utilities"; 5 | import { CONFIG } from "src/utility/config"; 6 | 7 | /** 8 | * Handles events related to the CustomNoteWidth plugin. 9 | */ 10 | export default class EventHandler 11 | { 12 | updateTimeout: number; 13 | //previousWindowState: { width: number; height: number; }; 14 | public isUserInputTriggered: boolean = false; 15 | 16 | /** 17 | * Constructs a new EventHandler instance. 18 | * @param app - The Obsidian application instance. 19 | * @param plugin - Reference to the CustomNoteWidth plugin. 20 | */ 21 | constructor(private app: App, private plugin: CustomNoteWidth) 22 | { 23 | //this.previousWindowState = { width: window.innerWidth, height: window.innerHeight }; 24 | } 25 | 26 | /** 27 | * Register event handlers related to the plugin. 28 | */ 29 | public registerEventHandlers(): void 30 | { 31 | this.plugin.registerEvent(this.app.workspace.on("resize", this.handleResize)); 32 | this.plugin.registerEvent(this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange)); 33 | } 34 | 35 | /** 36 | * Deregister event handlers related to the plugin. 37 | */ 38 | public deregisterEventHandlers(): void 39 | { 40 | this.app.workspace.off("resize", this.handleResize); 41 | this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange); 42 | } 43 | 44 | /** 45 | * Handles the resize event of the workspace. 46 | * Debounces updates and checks for window size changes. 47 | */ 48 | private handleResize = async (): Promise => 49 | { 50 | this.handleSidebarChange(); 51 | 52 | // Check if the slider width exceeds the threshold and reset it if necessary 53 | if (this.plugin.settingsManager.getSliderWidth() / window.innerWidth > CONFIG.SLIDER_HIDE_THRESHOLD) 54 | { 55 | new Notice(NOTICES.SLIDER_HIDE_WARNING, 5000); 56 | const slider = this.plugin.uiManager.getSliderElement(); 57 | await this.plugin.settingsManager.saveSettings({ 58 | ...this.plugin.settingsManager.settings, 59 | sliderWidth: this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth 60 | }); 61 | if (slider) slider.style.width = this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth + 'px'; 62 | } 63 | 64 | // Update the note width 65 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(this.plugin.settingsManager.getWidthPercentage()); 66 | }; 67 | 68 | /** 69 | * Handles the active leaf change event. 70 | * Updates the note width based on the active leaf. 71 | */ 72 | private handleActiveLeafChange = async (leaf: WorkspaceLeaf): Promise => 73 | { 74 | // Enable/Disable our statusbar elements(wrapper) 75 | if (isActiveLeafMarkdown(this.app)) 76 | { 77 | this.plugin.statusBarManager.showStatusBarItem(); 78 | } else 79 | { 80 | this.plugin.statusBarManager.hideStatusBarItem(); 81 | } 82 | 83 | if (!leaf) return; 84 | this.handleActiveLeafChangeDebounced(); 85 | }; 86 | 87 | /** 88 | * Debounced logic for handling the active leaf change. 89 | */ 90 | private handleActiveLeafChangeDebouncedLogic = async (): Promise => 91 | { 92 | await this.plugin.noteWidthManager.refreshNoteWidth(this.isUserInputTriggered); 93 | }; 94 | 95 | /** 96 | * Debounced method for handling the active leaf change. 97 | * This helps ensure that rapid changes in the active leaf don't trigger rapid updates, 98 | * but instead are aggregated over a short period (300ms in this case) before being processed. 99 | */ 100 | private handleActiveLeafChangeDebounced = debounce(this.handleActiveLeafChangeDebouncedLogic, 300); 101 | 102 | /** 103 | * Handles changes to the sidebar. 104 | */ 105 | private handleSidebarChange(): void 106 | { 107 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(this.plugin.settingsManager.getWidthPercentage()); 108 | } 109 | 110 | /** 111 | * Configures the events for the slider element. 112 | * @param slider - The slider HTML element. 113 | * @param sliderValueText - The text display associated with the slider. 114 | */ 115 | public handleTextInputEvent(slider: HTMLInputElement, sliderValueText: HTMLElement): void 116 | { 117 | // If the input is below "0" set to "0", if above "100" set to "100" 118 | sliderValueText.addEventListener("change", () => 119 | { 120 | const minValue = 0; 121 | const maxValue = 100; 122 | const enteredValue = parseInt((sliderValueText as HTMLInputElement).value); 123 | 124 | if (isNaN(enteredValue) || enteredValue < minValue) 125 | { 126 | (sliderValueText as HTMLInputElement).value = (sliderValueText as HTMLInputElement).min; 127 | } else if (enteredValue > maxValue) 128 | { 129 | (sliderValueText as HTMLInputElement).value = (sliderValueText as HTMLInputElement).max; 130 | } 131 | }); 132 | 133 | // Restrict the user input to the range between 0 and 100 and limit to 3 digits 134 | sliderValueText.addEventListener("input", async () => 135 | { 136 | let enteredValue = parseInt((sliderValueText as HTMLInputElement).value); 137 | const minValue = 0; 138 | const maxValue = 100; 139 | 140 | if (isNaN(enteredValue) || enteredValue < minValue) 141 | { 142 | enteredValue = minValue; 143 | } else if (enteredValue > maxValue) 144 | { 145 | enteredValue = maxValue; 146 | } 147 | 148 | const clampedValue = Math.min(Math.max(enteredValue, minValue), maxValue); 149 | slider.value = clampedValue.toString(); 150 | 151 | 152 | 153 | await this.plugin.settingsManager.saveSettings({ 154 | ...this.plugin.settingsManager.settings, 155 | widthPercentage: clampedValue, 156 | }); 157 | 158 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(clampedValue); 159 | 160 | if (this.plugin.settingsManager.getEnableSaveWidthIndividually()) 161 | { 162 | this.isUserInputTriggered = true; 163 | if (this.updateTimeout) clearTimeout(this.updateTimeout); 164 | this.updateTimeout = window.setTimeout(async () => 165 | { 166 | await this.plugin.noteWidthManager.refreshNoteWidth(this.isUserInputTriggered); 167 | }, 250); 168 | } 169 | 170 | // Limit to 3 digits 171 | (sliderValueText as HTMLInputElement).value = (sliderValueText as HTMLInputElement).value.substring(0, 3); 172 | }); 173 | 174 | // Add event listener to the slider 175 | slider.addEventListener("input", async () => 176 | { 177 | const value = parseInt(slider.value); 178 | 179 | await this.plugin.settingsManager.saveSettings({ 180 | ...this.plugin.settingsManager.settings, 181 | widthPercentage: value, 182 | }); 183 | 184 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(value); 185 | 186 | if (this.plugin.settingsManager.getEnableSaveWidthIndividually() || this.plugin.settingsManager.getEnableYAMLWidth()) 187 | { 188 | this.isUserInputTriggered = true; 189 | if (this.updateTimeout) clearTimeout(this.updateTimeout); 190 | this.updateTimeout = window.setTimeout(async () => 191 | { 192 | await this.plugin.noteWidthManager.refreshNoteWidth(this.isUserInputTriggered); 193 | }, 250); 194 | } 195 | 196 | if (sliderValueText instanceof HTMLInputElement) 197 | { 198 | sliderValueText.value = value.toString(); 199 | } else if (sliderValueText instanceof HTMLSpanElement) 200 | { 201 | sliderValueText.textContent = value.toString(); 202 | } 203 | }); 204 | } 205 | 206 | /** 207 | * Configures the events for text span. 208 | * @param slider - The slider HTML element. 209 | * @param sliderValueText - The text display associated with the slider. 210 | */ 211 | public handleTextSpanEvent(slider: HTMLInputElement, sliderValueText: HTMLElement): void 212 | { 213 | slider.addEventListener("input", async () => 214 | { 215 | const value = parseInt(slider.value); 216 | await this.plugin.settingsManager.saveSettings({ 217 | ...this.plugin.settingsManager.settings, 218 | widthPercentage: value, 219 | }); 220 | 221 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(value); 222 | 223 | if (this.plugin.settingsManager.getEnableSaveWidthIndividually() || this.plugin.settingsManager.getEnableYAMLWidth()) 224 | { 225 | this.isUserInputTriggered = true; 226 | if (this.updateTimeout) clearTimeout(this.updateTimeout); 227 | this.updateTimeout = window.setTimeout(async () => 228 | { 229 | await this.plugin.noteWidthManager.refreshNoteWidth(this.isUserInputTriggered); 230 | }, 250); 231 | } 232 | 233 | sliderValueText.textContent = value.toString(); 234 | }); 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "obsidian"; 2 | import CustomNoteWidthSettingTab from "src/settings/settingsTab"; 3 | import CommandsManager from "src/commands/commandsManager"; 4 | import SettingsManager from "src/settings/settingsManager"; 5 | import NoteWidthManager from "src/note/noteWidthManager"; 6 | import EventHandler from "src/event/eventHandler"; 7 | import StatusBarManager from "src/statusbar/statusBarManager"; 8 | import WrapperManager from "src/statusbar/wrapperManager"; 9 | import UIElementCreator from "src/ui/uiElementCreator"; 10 | import LokiDatabase from "src/utility/lokiDatabase"; 11 | import { getDatabasePath } from "src/utility/utilities"; 12 | import { DATABASE_FILENAME, getLoadedMessage, getUnloadedMessage } from "src/utility/constants"; 13 | import UIManager from "./ui/uiManager"; 14 | import YamlFrontMatterProcessor from "src/note/yamlFrontMatterProcessor"; 15 | 16 | /** 17 | * The main plugin class for CustomNoteWidth. 18 | * Handles the initialization and management of various components related to adjusting the note width in Obsidian. 19 | */ 20 | export default class CustomNoteWidth extends Plugin 21 | { 22 | settingsManager: SettingsManager; 23 | noteWidthManager: NoteWidthManager; 24 | eventHandler: EventHandler; 25 | wrapperManager: WrapperManager; 26 | statusBarManager: StatusBarManager; 27 | uiElementCreator: UIElementCreator; 28 | commandsManager: CommandsManager; 29 | database: LokiDatabase; 30 | settingsTab: CustomNoteWidthSettingTab; 31 | uiManager: UIManager; 32 | yamlFrontMatterProcessor: YamlFrontMatterProcessor; 33 | 34 | /** 35 | * This function is called when the plugin is loaded. 36 | * It initializes various managers, handlers, and UI elements. 37 | * @returns {Promise} A promise that resolves when the loading process is completed. 38 | */ 39 | async onload(): Promise 40 | { 41 | // Create settings manager to load settings 42 | this.settingsManager = new SettingsManager(this); 43 | await this.settingsManager.loadSettings(); 44 | 45 | // Create the settings tab for "Custom Note Width" in Obsidian settings 46 | this.settingsTab = new CustomNoteWidthSettingTab(this.app, this); 47 | this.addSettingTab(this.settingsTab); 48 | 49 | // Initialize the Database 50 | if (this.settingsManager.getEnableSaveWidthIndividually()) 51 | { 52 | this.database = new LokiDatabase(getDatabasePath(this.app, this, DATABASE_FILENAME)); 53 | await this.database.init(); 54 | } 55 | 56 | // Create wrapper manager and create a wrapper div element 57 | this.wrapperManager = new WrapperManager(); 58 | 59 | // Creates a yaml frontmatter processor 60 | this.yamlFrontMatterProcessor = new YamlFrontMatterProcessor(this.app); 61 | 62 | // Create statusbar manager 63 | this.statusBarManager = new StatusBarManager(this); 64 | 65 | // Create note width manager 66 | this.noteWidthManager = new NoteWidthManager(this.app, this); 67 | 68 | // Create event handler 69 | this.eventHandler = new EventHandler(this.app, this); 70 | 71 | // Create ui creator 72 | this.uiElementCreator = new UIElementCreator(this); 73 | 74 | // Create ui manager 75 | this.uiManager = new UIManager(this); 76 | 77 | // Create the commands 78 | this.commandsManager = new CommandsManager(this); 79 | 80 | // Register the event handlers 81 | this.eventHandler.registerEventHandlers(); 82 | 83 | // Apply the editor width at startup 84 | this.noteWidthManager.updateNoteWidthEditorStyle(this.settingsManager.getWidthPercentage()); 85 | 86 | // Plugin loaded 87 | console.log(getLoadedMessage(this.manifest.version)); 88 | } 89 | 90 | /** 91 | * This function is called when the plugin is unloaded. 92 | * It handles the cleanup of various components. 93 | * @returns {Promise} A promise that resolves when the unloading process is completed. 94 | */ 95 | async onunload(): Promise 96 | { 97 | this.wrapperManager.removeWrapper(); 98 | this.noteWidthManager.removeNoteWidthEditorStyle(); 99 | this.eventHandler.deregisterEventHandlers(); 100 | 101 | // Plugin unloaded 102 | console.log(getUnloadedMessage(this.manifest.version)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/modals/noteWidthModal.ts: -------------------------------------------------------------------------------- 1 | import { Modal, App } from "obsidian"; 2 | import { APPLY_BUTTON_TEXT, DOM_IDENTIFIERS } from "src/utility/constants"; 3 | 4 | /** 5 | * Modal for adjusting the note width. 6 | */ 7 | export default class NoteWidthModal extends Modal 8 | { 9 | /** Current value entered in the modal. */ 10 | private currentNumber: number | null = null; 11 | 12 | /** Handler for the keydown event. */ 13 | private keydownHandler: (ev: KeyboardEvent) => void; 14 | 15 | /** 16 | * Constructs a new NoteWidthModal instance. 17 | * @param app - The Obsidian application instance. 18 | * @param onNumberEntered - Callback to execute when a number is entered. 19 | * @param modalTitle - Title to display on the modal. 20 | */ 21 | constructor(app: App, private onNumberEntered: (number: number) => void, private modalTitle: string) 22 | { 23 | super(app); 24 | this.keydownHandler = (ev: KeyboardEvent) => 25 | { 26 | if (ev.key !== "Enter" || this.currentNumber === null) 27 | { 28 | return; 29 | } 30 | 31 | ev.preventDefault(); 32 | this.onNumberEntered(this.currentNumber); 33 | this.close(); 34 | }; 35 | } 36 | 37 | /** 38 | * Adds an input field to the modal for entering the note width value. 39 | */ 40 | private addInputField(): void 41 | { 42 | const inputContainer = this.contentEl.createDiv(); 43 | inputContainer.id = DOM_IDENTIFIERS.NWM_INPUT_CONTAINER; 44 | const inputEl = inputContainer.createEl("input", { type: "number" }); 45 | inputEl.oninput = (ev: InputEvent) => 46 | { 47 | let number = parseFloat((ev.target as HTMLInputElement).value); 48 | if (isNaN(number)) 49 | { 50 | this.currentNumber = 0; 51 | (ev.target as HTMLInputElement).value = "0"; 52 | return; 53 | } 54 | 55 | number = Math.max(0, Math.min(100, number)); 56 | this.currentNumber = number; 57 | (ev.target as HTMLInputElement).value = number.toString(); 58 | }; 59 | } 60 | 61 | /** 62 | * Adds a submit button to the modal for confirming the entered note width value. 63 | */ 64 | private addSubmitButton(): void 65 | { 66 | const buttonContainer = this.contentEl.createDiv(); 67 | buttonContainer.id = DOM_IDENTIFIERS.NWM_BUTTON_CONTAINER; 68 | const submitButton = buttonContainer.createEl("button", { text: APPLY_BUTTON_TEXT }); 69 | submitButton.onclick = () => 70 | { 71 | if (this.currentNumber === null) 72 | { 73 | return; 74 | } 75 | 76 | this.onNumberEntered(this.currentNumber); 77 | this.close(); 78 | }; 79 | } 80 | 81 | /** 82 | * Called when the modal is opened. 83 | * Sets up the modal elements and event listeners. 84 | */ 85 | public onOpen(): void 86 | { 87 | this.modalEl.id = DOM_IDENTIFIERS.NWM_CONTAINER; 88 | this.titleEl.textContent = this.modalTitle; 89 | this.addInputField(); 90 | this.addSubmitButton(); 91 | document.addEventListener("keydown", this.keydownHandler); 92 | } 93 | 94 | /** 95 | * Called when the modal is closed. 96 | * Removes event listeners. 97 | */ 98 | public onClose(): void 99 | { 100 | document.removeEventListener("keydown", this.keydownHandler); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/modals/progressBarModal.ts: -------------------------------------------------------------------------------- 1 | import { Modal, App } from "obsidian"; 2 | import { CANCEL_BUTTON_TEXT, DOM_IDENTIFIERS } from "src/utility/constants"; 3 | import { classSelector } from "src/utility/utilities"; 4 | import { domElementManager } from "src/ui/domElementManager"; 5 | 6 | /** 7 | * Modal displaying a progress bar. 8 | */ 9 | export default class ProgressBarModal extends Modal 10 | { 11 | /** Current progress percentage. */ 12 | progress: number = 0; 13 | 14 | /** Flag indicating if the progress has been cancelled. */ 15 | isCancelled: boolean = false; 16 | 17 | /** 18 | * Constructs a new ProgressBarModal instance. 19 | * @param app - The Obsidian application instance. 20 | * @param title - String which represents the modal title 21 | */ 22 | constructor(app: App, private title: string) 23 | { 24 | super(app); 25 | this.progress = 0; 26 | } 27 | 28 | /** 29 | * Adds a cancel button to the modal. 30 | */ 31 | private addCancelButton(): void 32 | { 33 | const cancelButton = this.contentEl.createEl("button", { text: CANCEL_BUTTON_TEXT }); 34 | cancelButton.style.marginTop = "20px"; 35 | cancelButton.style.float = "right"; 36 | cancelButton.onclick = () => 37 | { 38 | this.isCancelled = true; 39 | this.close(); 40 | }; 41 | } 42 | 43 | /** 44 | * Updates the displayed progress in the modal. 45 | */ 46 | private updateProgress(): void 47 | { 48 | const innerDiv = this.contentEl.querySelector(classSelector(DOM_IDENTIFIERS.PROGRESS_INNER)) as HTMLElement; 49 | if (innerDiv) 50 | { 51 | innerDiv.style.width = `${this.progress}%`; 52 | } 53 | } 54 | 55 | /** 56 | * Adds a container for the progress bar to the modal. 57 | */ 58 | private addProgressBarContainer(): void 59 | { 60 | if (domElementManager.querySelector(classSelector(DOM_IDENTIFIERS.PROGRESS_BAR_CONTAINER), this.contentEl)) 61 | { 62 | return; 63 | } 64 | 65 | const progressBarContainer = this.contentEl.createDiv({ cls: DOM_IDENTIFIERS.PROGRESS_BAR_CONTAINER }); 66 | 67 | const outerDiv = progressBarContainer.createDiv({ cls: DOM_IDENTIFIERS.PROGRESS_OUTER }); 68 | const innerDiv = outerDiv.createDiv({ cls: DOM_IDENTIFIERS.PROGRESS_INNER }); 69 | } 70 | 71 | /** 72 | * Increments the displayed progress by a given percentage. 73 | * @param percentage - The percentage by which to increment the progress. 74 | */ 75 | public incrementProgress(percentage: number): void 76 | { 77 | this.progress += percentage; 78 | this.updateProgress(); 79 | } 80 | 81 | /** 82 | * Called when the modal is opened. 83 | * Sets up the modal elements and event listeners. 84 | */ 85 | public onOpen(): void 86 | { 87 | this.titleEl.setText(this.title); 88 | const closeEl = this.containerEl.querySelector(".modal-close-button"); 89 | if (closeEl) closeEl.remove(); 90 | 91 | document.body.addEventListener("click", this.handleClickOutside, true); 92 | } 93 | 94 | /** 95 | * Handles clicks outside the modal. 96 | * @param event - The mouse event object. 97 | */ 98 | private handleClickOutside = (event: MouseEvent): void => 99 | { 100 | if (this.modalEl && this.modalEl.contains(event.target as Node)) 101 | { 102 | return; 103 | } 104 | this.isCancelled = true; 105 | this.close(); 106 | document.body.removeEventListener("click", this.handleClickOutside, true); 107 | }; 108 | 109 | /** 110 | * Displays the progress bar modal. 111 | */ 112 | public display(): void 113 | { 114 | this.open(); 115 | this.addProgressBarContainer(); 116 | this.addCancelButton(); 117 | this.updateProgress(); 118 | } 119 | 120 | /** 121 | * Closes the modal and removes event listeners. 122 | */ 123 | public close(): void 124 | { 125 | const cacheKey = domElementManager.generateCacheKey(classSelector(DOM_IDENTIFIERS.PROGRESS_INNER), this.contentEl); 126 | domElementManager.invalidateCache(cacheKey); 127 | 128 | document.body.removeEventListener("click", this.handleClickOutside); 129 | super.close(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/note/noteWidthManager.ts: -------------------------------------------------------------------------------- 1 | import { App } from "obsidian"; 2 | import CustomNoteWidth from "src/main"; 3 | import ProgressBarModal from "src/modals/progressBarModal"; 4 | import { DOM_IDENTIFIERS, NOTE_ID_KEY, PRIORITY_LIST, PROGRESS_BAR_MODAL_VALUE_TITLE_TEXT } from "src/utility/constants"; 5 | import { calculateNoteWidth, getActiveEditorDiv, getEditorMode, validateWidth } from "src/utility/utilities"; 6 | import UUIDGenerator from "src/utility/uuidGenerator"; 7 | 8 | /** 9 | * Manages the note width functionalities. 10 | */ 11 | export default class NoteWidthManager 12 | { 13 | /** Element for injecting custom styles. */ 14 | styleElement: HTMLStyleElement | null = null; 15 | 16 | /** 17 | * Constructs a new NoteWidthManager instance. 18 | * @param app - The Obsidian application instance. 19 | * @param plugin - Reference to the CustomNoteWidth plugin. 20 | */ 21 | constructor(private app: App, private plugin: CustomNoteWidth) 22 | { 23 | // Create a new style element and append it to the head of the document 24 | this.styleElement = document.createElement("style"); 25 | this.styleElement.id = DOM_IDENTIFIERS.CUSTOM_NOTE_WIDTH; 26 | document.getElementsByTagName("head")[0].appendChild(this.styleElement); 27 | } 28 | 29 | /** 30 | * Removes the custom editor style, if it exists. 31 | */ 32 | public removeNoteWidthEditorStyle(): void 33 | { 34 | if (!this.styleElement) return; 35 | 36 | this.styleElement.remove(); 37 | this.styleElement = null; 38 | } 39 | 40 | /** 41 | * Updates the custom editor style with a new width percentage, if it exists. 42 | * @param widthPercentage - The width percentage to be applied. 43 | */ 44 | public async updateNoteWidthEditorStyle(widthPercentage: number): Promise 45 | { 46 | if (!this.styleElement) throw "custom-note-width style element not found!"; 47 | 48 | const editorMode = getEditorMode(); 49 | if (editorMode === null) return; 50 | 51 | const editorDiv = getActiveEditorDiv(this.app, editorMode); 52 | if (!editorDiv) return; 53 | 54 | const noteWidth = await calculateNoteWidth(widthPercentage, editorDiv); 55 | if (!noteWidth) 56 | { 57 | console.error("Something went wrong while changing the note width!", new Error().stack); 58 | return; 59 | } 60 | 61 | this.styleElement.innerText = `body { --file-line-width: ${noteWidth}px;}`; 62 | } 63 | 64 | /** 65 | * Changes the width of all notes. 66 | * @param width - The width to be applied. 67 | */ 68 | public async changeAllNoteWidth(width: number): Promise 69 | { 70 | const CUSTOM_WIDTH_YAML_KEY = this.plugin.settingsManager.getYAMLKey(); 71 | let noteWidth = validateWidth(width); 72 | const isSaveWidthIndividuallyEnabled = this.plugin.settingsManager.getEnableSaveWidthIndividually(); 73 | const isYAMLWidthEnabled = this.plugin.settingsManager.getEnableYAMLWidth(); 74 | 75 | if (isSaveWidthIndividuallyEnabled && isYAMLWidthEnabled) 76 | { 77 | this.plugin.database.updateAllNotesWidth(width); 78 | const progressBarModal = new ProgressBarModal(this.app, PROGRESS_BAR_MODAL_VALUE_TITLE_TEXT); 79 | progressBarModal.display(); 80 | 81 | await this.plugin.yamlFrontMatterProcessor.updateAllYamlValues(CUSTOM_WIDTH_YAML_KEY, noteWidth, progressBarModal); 82 | 83 | progressBarModal.close(); 84 | } 85 | else if (isYAMLWidthEnabled) 86 | { 87 | const progressBarModal = new ProgressBarModal(this.app, PROGRESS_BAR_MODAL_VALUE_TITLE_TEXT); 88 | progressBarModal.display(); 89 | 90 | await this.plugin.yamlFrontMatterProcessor.updateAllYamlValues(CUSTOM_WIDTH_YAML_KEY, noteWidth, progressBarModal); 91 | 92 | progressBarModal.close(); 93 | } 94 | else if (isSaveWidthIndividuallyEnabled) 95 | { 96 | this.plugin.database.updateAllNotesWidth(width); 97 | } 98 | 99 | this.updateNoteWidthEditorStyle(width); 100 | this.plugin.uiManager.setSliderAndTextField(width); 101 | await this.plugin.settingsManager.saveWidthPercentage(width); 102 | } 103 | 104 | /** 105 | * Updates the default width of a note. 106 | * @param width - The new width to set for the note. 107 | * @returns A promise which resolves when the operation completes. 108 | */ 109 | public async changeDefaultNoteWidth(width: number): Promise 110 | { 111 | await this.plugin.settingsManager.saveDefaultNoteWidth(width); 112 | } 113 | 114 | /** 115 | * Updates the width of the current note based on settings and available note ID. 116 | * @param width - The new width to set for the current note. 117 | * @returns A promise which resolves when the operation completes. 118 | */ 119 | public async changeNoteWidth(width: number): Promise 120 | { 121 | let noteWidth = validateWidth(width); 122 | const CUSTOM_WIDTH_YAML_KEY = this.plugin.settingsManager.getYAMLKey(); 123 | const yamlProcessor = this.plugin.yamlFrontMatterProcessor; 124 | const isSaveWidthIndividuallyEnabled = this.plugin.settingsManager.getEnableSaveWidthIndividually(); 125 | const isYAMLWidthEnabled = this.plugin.settingsManager.getEnableYAMLWidth(); 126 | const hasNoteID = await yamlProcessor.hasYamlKey(NOTE_ID_KEY); 127 | const hasCustomKey = await yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY); 128 | 129 | if (isSaveWidthIndividuallyEnabled && isYAMLWidthEnabled) 130 | { 131 | if (hasNoteID) 132 | { 133 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 134 | 135 | if (this.plugin.database.noteExists(noteID)) 136 | { 137 | this.plugin.database.addNote(noteID, noteWidth); 138 | } 139 | else 140 | { 141 | let noteWidth = validateWidth(width); 142 | this.plugin.database.addNote(noteID, noteWidth); 143 | } 144 | } else if (hasCustomKey) 145 | { 146 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, noteWidth); 147 | } 148 | else 149 | { 150 | yamlProcessor.setYamlValue(NOTE_ID_KEY, UUIDGenerator.getUniqueUUID(this.plugin.database)); 151 | this.plugin.database.addNote(UUIDGenerator.getUniqueUUID(this.plugin.database), noteWidth); 152 | } 153 | } 154 | else if (isSaveWidthIndividuallyEnabled) 155 | { 156 | if (hasNoteID) 157 | { 158 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 159 | 160 | if (this.plugin.database.noteExists(noteID)) 161 | { 162 | this.plugin.database.addNote(noteID, noteWidth); 163 | } 164 | else 165 | { 166 | let noteWidth = validateWidth(width); 167 | this.plugin.database.addNote(noteID, noteWidth); 168 | } 169 | } 170 | else 171 | { 172 | this.plugin.yamlFrontMatterProcessor.setYamlValue(NOTE_ID_KEY, UUIDGenerator.getUniqueUUID(this.plugin.database)); 173 | this.plugin.database.addNote(UUIDGenerator.getUniqueUUID(this.plugin.database), noteWidth); 174 | } 175 | } 176 | else if (isYAMLWidthEnabled) 177 | { 178 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, noteWidth); 179 | } 180 | 181 | this.plugin.uiManager.setSliderAndTextField(noteWidth); 182 | await this.updateNoteWidthEditorStyle(noteWidth); 183 | await this.plugin.settingsManager.saveWidthPercentage(noteWidth); 184 | } 185 | 186 | /** 187 | * Updates the note width based on user settings. 188 | */ 189 | public async refreshNoteWidth(isUserInputTriggered: boolean): Promise 190 | { 191 | const yamlProcessor = this.plugin.yamlFrontMatterProcessor; 192 | const uiManager = this.plugin.uiManager; 193 | const database = this.plugin.database; 194 | const DEFAULT_WIDTH = this.plugin.settingsManager.getDefaultNoteWidth(); 195 | const isSaveWidthIndividuallyEnabled = this.plugin.settingsManager.getEnableSaveWidthIndividually(); 196 | const isYAMLWidthEnabled = this.plugin.settingsManager.getEnableYAMLWidth(); 197 | const currentWidthPercentage = this.plugin.settingsManager.getWidthPercentage(); 198 | const CUSTOM_WIDTH_YAML_KEY = this.plugin.settingsManager.getYAMLKey(); 199 | const CURRENT_PRIORITY = this.plugin.settingsManager.getCurrentPriority(); 200 | 201 | if (!isUserInputTriggered) 202 | { 203 | if (!isSaveWidthIndividuallyEnabled && !isYAMLWidthEnabled) 204 | { 205 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 206 | return; 207 | } 208 | 209 | if (isSaveWidthIndividuallyEnabled && isYAMLWidthEnabled) 210 | { 211 | if (!yamlProcessor.hasYamlFrontMatter()) 212 | { 213 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 214 | return; 215 | } 216 | 217 | if (CURRENT_PRIORITY === PRIORITY_LIST.SAVED_NOTE_WIDTH) 218 | { 219 | if (await yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 220 | { 221 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 222 | 223 | if (database.noteExists(noteID)) 224 | { 225 | let width = database.getNoteWidth(noteID); 226 | if (width !== null) 227 | { 228 | width = validateWidth(width); 229 | uiManager.updateUIAndEditorWidth(width); 230 | return; 231 | } 232 | } 233 | else 234 | { 235 | if (await yamlProcessor.isOnlyYamlKey(NOTE_ID_KEY)) 236 | { 237 | yamlProcessor.removeYamlFrontMatter(); 238 | } 239 | else 240 | { 241 | yamlProcessor.removeYamlKey(NOTE_ID_KEY); 242 | } 243 | 244 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 245 | return; 246 | } 247 | } 248 | else if (await yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY)) 249 | { 250 | let width = await yamlProcessor.getYamlValue(CUSTOM_WIDTH_YAML_KEY); 251 | if (width !== null) 252 | { 253 | width = validateWidth(width); 254 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, width); 255 | uiManager.updateUIAndEditorWidth(await width); 256 | return; 257 | } 258 | } else 259 | { 260 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 261 | return; 262 | } 263 | } 264 | else if (CURRENT_PRIORITY === PRIORITY_LIST.YAML_NOTE_WIDTH) 265 | { 266 | if (await yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY)) 267 | { 268 | let width = await yamlProcessor.getYamlValue(CUSTOM_WIDTH_YAML_KEY); 269 | if (width !== null) 270 | { 271 | width = validateWidth(width); 272 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, width); 273 | uiManager.updateUIAndEditorWidth(await width); 274 | return; 275 | } 276 | } 277 | else if (await yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 278 | { 279 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 280 | 281 | if (database.noteExists(noteID)) 282 | { 283 | let width = database.getNoteWidth(noteID); 284 | if (width !== null) 285 | { 286 | width = validateWidth(width); 287 | uiManager.updateUIAndEditorWidth(width); 288 | return; 289 | } 290 | } 291 | else 292 | { 293 | if (await yamlProcessor.isOnlyYamlKey(NOTE_ID_KEY)) 294 | { 295 | yamlProcessor.removeYamlFrontMatter(); 296 | } 297 | else 298 | { 299 | yamlProcessor.removeYamlKey(NOTE_ID_KEY); 300 | } 301 | 302 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 303 | return; 304 | } 305 | } 306 | else 307 | { 308 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 309 | return; 310 | } 311 | } 312 | } 313 | else if (isSaveWidthIndividuallyEnabled) 314 | { 315 | if (!yamlProcessor.hasYamlFrontMatter()) 316 | { 317 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 318 | return; 319 | } 320 | 321 | if (!yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 322 | { 323 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 324 | return; 325 | } 326 | else 327 | { 328 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 329 | 330 | if (database.noteExists(noteID)) 331 | { 332 | let width = database.getNoteWidth(noteID); 333 | if (width !== null) 334 | { 335 | width = validateWidth(width); 336 | uiManager.updateUIAndEditorWidth(width); 337 | return; 338 | } 339 | } 340 | else 341 | { 342 | if (await yamlProcessor.isOnlyYamlKey(NOTE_ID_KEY)) 343 | { 344 | yamlProcessor.removeYamlFrontMatter(); 345 | } 346 | else 347 | { 348 | yamlProcessor.removeYamlKey(NOTE_ID_KEY); 349 | } 350 | 351 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 352 | return; 353 | } 354 | } 355 | } 356 | else if (isYAMLWidthEnabled) 357 | { 358 | if (!yamlProcessor.hasYamlFrontMatter()) 359 | { 360 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 361 | return; 362 | } 363 | 364 | if (!yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY)) 365 | { 366 | uiManager.updateUIAndEditorWidth(DEFAULT_WIDTH); 367 | return; 368 | } 369 | else 370 | { 371 | let width = await yamlProcessor.getYamlValue(CUSTOM_WIDTH_YAML_KEY); 372 | if (width !== null) 373 | { 374 | width = validateWidth(width); 375 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, width); 376 | uiManager.updateUIAndEditorWidth(await width); 377 | return; 378 | } 379 | } 380 | } 381 | } 382 | else 383 | { 384 | this.plugin.eventHandler.isUserInputTriggered = false; 385 | 386 | if (!isSaveWidthIndividuallyEnabled && !isYAMLWidthEnabled) 387 | { 388 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(currentWidthPercentage); 389 | return; 390 | } 391 | 392 | if (isSaveWidthIndividuallyEnabled && isYAMLWidthEnabled) 393 | { 394 | if (!yamlProcessor.hasYamlFrontMatter()) 395 | { 396 | if (CURRENT_PRIORITY === PRIORITY_LIST.SAVED_NOTE_WIDTH) 397 | { 398 | const UUID = UUIDGenerator.getUniqueUUID(database); 399 | database.addNote(UUID, currentWidthPercentage); 400 | yamlProcessor.setYamlValue(NOTE_ID_KEY, UUID); 401 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 402 | return; 403 | } 404 | else if (CURRENT_PRIORITY === PRIORITY_LIST.YAML_NOTE_WIDTH) 405 | { 406 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, currentWidthPercentage); 407 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 408 | return; 409 | } 410 | } 411 | else 412 | { 413 | if (!yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY) && !yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 414 | { 415 | if (CURRENT_PRIORITY === PRIORITY_LIST.SAVED_NOTE_WIDTH) 416 | { 417 | const UUID = UUIDGenerator.getUniqueUUID(database); 418 | yamlProcessor.setYamlValue(NOTE_ID_KEY, UUID); 419 | database.addNote(UUID, currentWidthPercentage); 420 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 421 | } 422 | else if (CURRENT_PRIORITY === PRIORITY_LIST.YAML_NOTE_WIDTH) 423 | { 424 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, currentWidthPercentage); 425 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 426 | } 427 | 428 | return; 429 | } 430 | 431 | if (await yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 432 | { 433 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 434 | database.addNote(noteID, currentWidthPercentage); 435 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 436 | } 437 | 438 | if (await yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY)) 439 | { 440 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, currentWidthPercentage); 441 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 442 | } 443 | 444 | return; 445 | } 446 | } 447 | else if (isSaveWidthIndividuallyEnabled) 448 | { 449 | if (await yamlProcessor.hasYamlKey(NOTE_ID_KEY)) 450 | { 451 | const noteID = await yamlProcessor.getYamlValue(NOTE_ID_KEY); 452 | database.addNote(noteID, currentWidthPercentage); 453 | uiManager.updateUIAndEditorWidth(currentWidthPercentage); 454 | return; 455 | } 456 | else 457 | { 458 | const UUID = UUIDGenerator.getUniqueUUID(database); 459 | yamlProcessor.setYamlValue(NOTE_ID_KEY, UUID); 460 | database.addNote(UUID, currentWidthPercentage); 461 | return; 462 | } 463 | } 464 | else if (isYAMLWidthEnabled) 465 | { 466 | if (await yamlProcessor.hasYamlKey(CUSTOM_WIDTH_YAML_KEY)) 467 | { 468 | yamlProcessor.setYamlValue(CUSTOM_WIDTH_YAML_KEY, currentWidthPercentage); 469 | return; 470 | } 471 | } 472 | 473 | } 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/note/yamlFrontMatterProcessor.ts: -------------------------------------------------------------------------------- 1 | import { App } from "obsidian"; 2 | import { YAML_FRONTMATTER_REGEX } from "src/utility/constants"; 3 | import { getActiveMarkdownView } from "src/utility/utilities"; 4 | import ProgressBarModal from "src/modals/progressBarModal"; 5 | 6 | /** 7 | * Processor for handling and updating YAML front matter in notes. 8 | */ 9 | export default class YamlFrontMatterProcessor 10 | { 11 | /** 12 | * Constructs a new YamlFrontMatterProcessor instance. 13 | * @param app - The Obsidian application instance. 14 | * @param plugin - Reference to the CustomNoteWidth plugin. 15 | */ 16 | constructor(private app: App) { } 17 | 18 | /** 19 | * Replaces a specified YAML key in all notes with a new key. 20 | * @param oldKey - The original key to be replaced. 21 | * @param newKey - The new key to replace the original with. 22 | */ 23 | public async replaceYamlKeyInAllNotes(oldKey: string, newKey: string, progressBarModal: ProgressBarModal): Promise 24 | { 25 | const files = this.app.vault.getMarkdownFiles(); 26 | const incrementValue = 100 / files.length; 27 | 28 | for (const file of files) 29 | { 30 | if (progressBarModal.isCancelled) 31 | { 32 | break; 33 | } 34 | 35 | await this.app.fileManager.processFrontMatter(file, (frontMatter) => 36 | { 37 | if (oldKey in frontMatter) 38 | { 39 | frontMatter[newKey] = frontMatter[oldKey]; 40 | delete frontMatter[oldKey]; 41 | } 42 | }); 43 | 44 | // Update the progress bar after processing each note 45 | progressBarModal.incrementProgress(incrementValue); 46 | } 47 | } 48 | 49 | /** 50 | * Update the specified YAML key's value in all Markdown files within the vault. 51 | * @async 52 | * @param {string} yamlKey - The key in the YAML front matter to update. 53 | * @param {number} newValue - The new value to set for the specified YAML key. 54 | * @param {ProgressBarModal} progressBarModal - The progress bar modal to indicate the progress of the operation. 55 | * @returns {Promise} - Resolves when all files have been processed. 56 | * @throws Will throw an error if the operation fails. 57 | */ 58 | public async updateAllYamlValues(yamlKey: string, newValue: number, progressBarModal: ProgressBarModal): Promise 59 | { 60 | const files = this.app.vault.getMarkdownFiles(); 61 | const incrementValue = 100 / files.length; 62 | 63 | for (const file of files) 64 | { 65 | if (progressBarModal.isCancelled) 66 | { 67 | break; 68 | } 69 | 70 | await this.app.fileManager.processFrontMatter(file, (frontMatter) => 71 | { 72 | if (yamlKey in frontMatter) 73 | { 74 | frontMatter[yamlKey] = newValue; 75 | } 76 | }); 77 | 78 | progressBarModal.incrementProgress(incrementValue); 79 | } 80 | } 81 | 82 | /** 83 | * Retrieves the content of the active note. 84 | * @private 85 | * @returns {string | null} The content of the active note or null if not available. 86 | */ 87 | private getActiveNoteContent(): string | null 88 | { 89 | const activeView = getActiveMarkdownView(this.app); 90 | return activeView?.editor.getValue() || null; 91 | } 92 | 93 | /** 94 | * Extracts the YAML front matter from the active note. 95 | * @public 96 | * @returns {string | null} The extracted YAML front matter or null if not found. 97 | */ 98 | public extractYamlFromNote(): string | null 99 | { 100 | const content = this.getActiveNoteContent(); 101 | if (!content) return null; 102 | const frontMatterMatch = content.match(YAML_FRONTMATTER_REGEX); 103 | return frontMatterMatch && frontMatterMatch[1]; 104 | } 105 | 106 | /** 107 | * Checks if the active note contains a YAML front matter. 108 | * @public 109 | * @returns {boolean} True if the active note contains a YAML front matter, otherwise false. 110 | */ 111 | public hasYamlFrontMatter(): boolean 112 | { 113 | return Boolean(this.extractYamlFromNote()); 114 | } 115 | 116 | /** 117 | * Retrieves the value of a given YAML key from the active note. 118 | * @public 119 | * @param {string} key - The key to retrieve its value from the YAML front matter. 120 | * @returns {any} The value of the specified key or null if not found. 121 | */ 122 | public async getYamlValue(key: string): Promise 123 | { 124 | const activeView = getActiveMarkdownView(this.app); 125 | if (!activeView || !activeView.file) return null; 126 | 127 | let value = null; 128 | 129 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 130 | { 131 | value = frontMatter[key]; 132 | }); 133 | 134 | return value; 135 | } 136 | 137 | /** 138 | * Sets a value for a given YAML key in the active note's front matter. 139 | * @public 140 | * @param {string} key - The key to set its value. 141 | * @param {any} value - The value to set for the specified key. 142 | */ 143 | public async setYamlValue(key: string, value: any): Promise 144 | { 145 | const activeView = getActiveMarkdownView(this.app); 146 | if (!activeView || !activeView.file) return; 147 | 148 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 149 | { 150 | frontMatter[key] = value; 151 | }); 152 | } 153 | 154 | /** 155 | * Removes a specified key from the YAML front matter of the active note. 156 | * @public 157 | * @param {string} key - The key to remove from the YAML front matter. 158 | */ 159 | public async removeYamlKey(key: string): Promise 160 | { 161 | const activeView = getActiveMarkdownView(this.app); 162 | if (!activeView || !activeView.file) return; 163 | 164 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 165 | { 166 | delete frontMatter[key]; 167 | }); 168 | } 169 | 170 | /** 171 | * Checks if the active note's YAML front matter contains a specified key. 172 | * @public 173 | * @param {string} key - The key to check for its existence. 174 | * @returns {boolean} True if the key exists, otherwise false. 175 | */ 176 | public async hasYamlKey(key: string): Promise 177 | { 178 | const activeView = getActiveMarkdownView(this.app); 179 | if (!activeView || !activeView.file) return false; 180 | 181 | let exists = false; 182 | 183 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 184 | { 185 | exists = key in frontMatter; 186 | }); 187 | 188 | return exists; 189 | } 190 | 191 | /** 192 | * Checks if a specified key is the only key in the active note's YAML front matter. 193 | * @public 194 | * @param {string} key - The key to check. 195 | * @returns {boolean} True if the key is the only one in the YAML front matter, otherwise false. 196 | */ 197 | public async isOnlyYamlKey(key: string): Promise 198 | { 199 | const activeView = getActiveMarkdownView(this.app); 200 | if (!activeView || !activeView.file) return false; 201 | 202 | let isOnly = false; 203 | 204 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 205 | { 206 | isOnly = Object.keys(frontMatter).length === 1 && frontMatter[key] !== undefined; 207 | }); 208 | 209 | return isOnly; 210 | } 211 | 212 | /** 213 | * Removes the entire YAML front matter from the active note. 214 | * @public 215 | */ 216 | public async removeYamlFrontMatter(): Promise 217 | { 218 | const activeView = getActiveMarkdownView(this.app); 219 | if (!activeView || !activeView.file) return; 220 | 221 | await this.app.fileManager.processFrontMatter(activeView.file, (frontMatter) => 222 | { 223 | for (const key of Object.keys(frontMatter)) 224 | { 225 | delete frontMatter[key]; 226 | } 227 | }); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/settings/donationButton.ts: -------------------------------------------------------------------------------- 1 | import { DOM_IDENTIFIERS, DONATION_DISCLAIMER_TEXT, DONATION_LINK, KOFI_SVG } from "src/utility/constants"; 2 | 3 | /** 4 | * Represents a donation button component. 5 | */ 6 | export default class DonationButton 7 | { 8 | /** DOM parser used for parsing SVG content. */ 9 | parser: DOMParser; 10 | 11 | /** 12 | * Constructs a new DonationButton instance. 13 | */ 14 | constructor() 15 | { 16 | this.parser = new DOMParser(); 17 | } 18 | 19 | /** 20 | * Creates a donation button element. 21 | * @param link - The URL to which the donation button should redirect. 22 | * @param img - The image element to use for the donation button. 23 | * @returns - The created donation button as an HTMLElement. 24 | */ 25 | private createDonateButtonElement(link: string, img: HTMLElement): HTMLElement 26 | { 27 | // Create an anchor element for the donation link 28 | const a = document.createElement("a"); 29 | a.setAttribute("href", link); 30 | 31 | // Set image styles to adjust height and width 32 | img.style.height = "auto"; 33 | img.style.width = "100%"; 34 | 35 | // Append the image element to the anchor element 36 | a.appendChild(img); 37 | 38 | // Create a flex container (div) and center the anchor element inside it 39 | const div = document.createElement("div"); 40 | div.appendChild(a); 41 | 42 | return div; 43 | } 44 | 45 | /** 46 | * Creates a donation button with a disclaimer. 47 | * @param containerEl - The container element to which the donation button should be appended. 48 | * @returns - The container element with the appended donation button. 49 | */ 50 | public createDonationButton(containerEl: HTMLElement): HTMLElement 51 | { 52 | const div = containerEl.createEl("div"); 53 | div.id = DOM_IDENTIFIERS.DONATION_BUTTON; 54 | 55 | // Create a paragraph element to display a disclaimer message 56 | const p = document.createElement("p"); 57 | p.textContent = DONATION_DISCLAIMER_TEXT; 58 | 59 | // Create the donation button and append it to the container div 60 | div.appendChild(this.createDonateButtonElement(DONATION_LINK, this.parser.parseFromString(KOFI_SVG, "text/xml").documentElement)); 61 | 62 | // Append the disclaimer paragraph to the container div 63 | div.appendChild(p); 64 | 65 | return div; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/settings/settingsManager.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import { PRIORITY_LIST } from "src/utility/constants"; 3 | 4 | /** 5 | * Interface representing the settings structure for CustomNoteWidth. 6 | */ 7 | export interface CustomNoteWidthSettings 8 | { 9 | widthPercentage: number; 10 | sliderWidth: number; 11 | defaultNoteWidth: number; 12 | yamlKey: string; 13 | enableSlider: boolean; 14 | enableTextInput: boolean; 15 | enableYAMLWidth: boolean; 16 | enableSaveWidthIndividually: boolean; 17 | enableChangeDefaultNoteWidth: boolean; 18 | priorityList: string[]; 19 | } 20 | 21 | /** 22 | * Manages settings functionalities for the CustomNoteWidth plugin. 23 | */ 24 | export default class SettingsManager 25 | { 26 | settings: CustomNoteWidthSettings; 27 | 28 | /** Default settings for the plugin. */ 29 | DEFAULT_SETTINGS: CustomNoteWidthSettings = { 30 | widthPercentage: 36, 31 | sliderWidth: 85, 32 | defaultNoteWidth: 36, 33 | yamlKey: "custom-width", 34 | enableSlider: true, 35 | enableTextInput: true, 36 | enableYAMLWidth: true, 37 | enableSaveWidthIndividually: true, 38 | enableChangeDefaultNoteWidth: false, 39 | priorityList: [PRIORITY_LIST.SAVED_NOTE_WIDTH, PRIORITY_LIST.YAML_NOTE_WIDTH] 40 | }; 41 | 42 | /** 43 | * Constructs a new SettingsManager instance. 44 | * @param plugin - Reference to the CustomNoteWidth plugin. 45 | */ 46 | constructor(private plugin: CustomNoteWidth) 47 | { 48 | } 49 | 50 | /** 51 | * Retrieves a specific setting from the plugin's settings object. 52 | * @param key - The setting key to retrieve. 53 | * @returns - Value associated with the specified key. 54 | */ 55 | private getSetting(key: T): CustomNoteWidthSettings[T] 56 | { 57 | const value = this.settings[key]; 58 | 59 | if (value === undefined) 60 | { 61 | throw new Error(`Invalid setting key: ${key}`); 62 | } 63 | 64 | return value; 65 | } 66 | 67 | /** 68 | * Retrieves the width percentage setting. 69 | * @returns - The width percentage setting value. 70 | */ 71 | public getWidthPercentage(): number 72 | { 73 | return this.getSetting("widthPercentage"); 74 | } 75 | 76 | /** 77 | * Retrieves the enable slider setting. 78 | * @returns - The enable slider setting value. 79 | */ 80 | public getEnableSlider(): boolean 81 | { 82 | return this.getSetting("enableSlider"); 83 | } 84 | 85 | /** 86 | * Retrieves the YAML key setting. 87 | * @returns - The YAML key setting value. 88 | */ 89 | public getYAMLKey(): string 90 | { 91 | return this.getSetting("yamlKey"); 92 | } 93 | 94 | /** 95 | * Retrieves the enable text input setting. 96 | * @returns - The enable text input setting value. 97 | */ 98 | public getEnableTextInput(): boolean 99 | { 100 | return this.getSetting("enableTextInput"); 101 | } 102 | 103 | /** 104 | * Retrieves the slider width setting. 105 | * @returns - The slider width setting value. 106 | */ 107 | public getSliderWidth(): number 108 | { 109 | return this.getSetting("sliderWidth"); 110 | } 111 | 112 | /** 113 | * Retrieves the enable save width individually setting. 114 | * @returns - The enable save width individually setting value. 115 | */ 116 | public getEnableSaveWidthIndividually(): boolean 117 | { 118 | return this.getSetting("enableSaveWidthIndividually"); 119 | } 120 | 121 | /** 122 | * Retrieves the enable change default note width setting. 123 | * @returns - The enable change default note width setting value. 124 | */ 125 | public getEnableChangeDefaultNoteWidth(): boolean 126 | { 127 | return this.getSetting("enableChangeDefaultNoteWidth"); 128 | } 129 | 130 | /** 131 | * Retrieves the enable YAML width setting. 132 | * @returns - The enable YAML width setting value. 133 | */ 134 | public getEnableYAMLWidth(): boolean 135 | { 136 | return this.getSetting("enableYAMLWidth"); 137 | } 138 | 139 | /** 140 | * Asynchronously saves the defaultNoteWidth setting. 141 | * @param defaultNoteWidth - The default note width value to be saved. 142 | */ 143 | public getDefaultNoteWidth(): number 144 | { 145 | return this.getSetting("defaultNoteWidth"); 146 | } 147 | 148 | /** 149 | * Retrieves the current priority setting. 150 | * @returns - The current priority setting value. 151 | */ 152 | public getCurrentPriority(): string 153 | { 154 | return this.getSetting("priorityList")[0]; 155 | } 156 | 157 | /** 158 | * Retrieves the priority list setting. 159 | * @returns - The priority list setting value. 160 | */ 161 | public getPriorityList(): string[] 162 | { 163 | return this.getSetting("priorityList"); 164 | } 165 | 166 | /** 167 | * Asynchronously saves the default note width setting. 168 | * @param defaultNoteWidth - The default note width value to be saved. 169 | */ 170 | public async saveDefaultNoteWidth(defaultNoteWidth: number): Promise 171 | { 172 | this.settings.defaultNoteWidth = defaultNoteWidth; 173 | await this.plugin.saveData(this.settings); 174 | } 175 | 176 | /** 177 | * Asynchronously saves the widthPercentage setting. 178 | * @param widthPercentage - The width percentage value to be saved. 179 | */ 180 | public async saveWidthPercentage(widthPercentage: number): Promise 181 | { 182 | this.settings.widthPercentage = widthPercentage; 183 | await this.plugin.saveData(this.settings); 184 | } 185 | 186 | /** 187 | * Asynchronously loads the settings by combining the default settings with the loaded data from the plugin. 188 | */ 189 | public async loadSettings(): Promise 190 | { 191 | this.settings = Object.assign({}, this.DEFAULT_SETTINGS, await this.plugin.loadData()); 192 | } 193 | 194 | /** 195 | * Asynchronously saves the provided settings. 196 | * @param settings - The settings to be saved. 197 | */ 198 | public async saveSettings(settings: CustomNoteWidthSettings): Promise 199 | { 200 | this.settings = settings; 201 | await this.plugin.saveData(settings); 202 | } 203 | 204 | /** 205 | * Asynchronously resets the editor & saved width to the default value. 206 | */ 207 | public async resetEditorWidth(): Promise 208 | { 209 | this.plugin.noteWidthManager.removeNoteWidthEditorStyle(); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/settings/settingsTab.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting, ToggleComponent, Notice } from "obsidian"; 2 | import type CustomNoteWidth from "src/main"; 3 | import DonationButton from "src/settings/donationButton"; 4 | import YamlFrontMatterProcessor from "src/note/yamlFrontMatterProcessor"; 5 | import ProgressBarModal from "src/modals/progressBarModal"; 6 | import { DATABASE_FILENAME, DOM_IDENTIFIERS, NOTICES, PROGRESS_BAR_MODAL_KEY_TITLE_TEXT } from "src/utility/constants"; 7 | import LokiDatabase from "src/utility/lokiDatabase"; 8 | import { getDatabasePath } from "src/utility/utilities"; 9 | 10 | /** 11 | * Represents the settings tab for the CustomNoteWidth plugin. 12 | */ 13 | export default class CustomNoteWidthSettingTab extends PluginSettingTab 14 | { 15 | donationButton: DonationButton; 16 | yamlProcessor: YamlFrontMatterProcessor; 17 | 18 | /** 19 | * Constructs a new CustomNoteWidthSettingTab instance. 20 | * @param app - The Obsidian application instance. 21 | * @param plugin - Reference to the CustomNoteWidth plugin. 22 | */ 23 | constructor(app: App, private plugin: CustomNoteWidth) 24 | { 25 | super(app, plugin); 26 | this.donationButton = new DonationButton(); 27 | this.yamlProcessor = new YamlFrontMatterProcessor(app); 28 | } 29 | 30 | public display(): void 31 | { 32 | let updateTimeout: number; 33 | 34 | // Get the container element for the settings modal. 35 | const { containerEl } = this; 36 | containerEl.empty(); 37 | 38 | // Toggle to enable the slider 39 | new Setting(containerEl) 40 | .setName("Enable slider") 41 | .setDesc("Toggle to enable/disable the slider.") 42 | .addToggle((cb: ToggleComponent) => 43 | { 44 | cb.setValue(this.plugin.settingsManager.getEnableSlider()); 45 | cb.onChange(async (value: boolean) => 46 | { 47 | await this.plugin.settingsManager.saveSettings({ 48 | ...this.plugin.settingsManager.settings, 49 | enableSlider: value, 50 | }); 51 | this.plugin.uiManager.updateUI(); 52 | this.display(); 53 | }); 54 | }); 55 | 56 | // Only show this setting if the slider is enabled to change slider width 57 | if (this.plugin.settingsManager.getEnableSlider()) 58 | { 59 | new Setting(containerEl) 60 | .setName("Slider width") 61 | .setDesc("Change the width of the slider.") 62 | .addText((text) => text 63 | .setPlaceholder("85") 64 | .setValue(this.plugin.settingsManager.getSliderWidth().toString()) 65 | .onChange(async (value) => 66 | { 67 | let sliderWidth = parseInt(value); 68 | 69 | if (value === "") 70 | { 71 | return; 72 | } 73 | else if (isNaN(sliderWidth)) 74 | { 75 | await this.plugin.settingsManager.saveSettings({ 76 | ...this.plugin.settingsManager.settings, 77 | sliderWidth: this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth 78 | }); 79 | text.setValue(this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth.toString()); 80 | const slider = this.plugin.uiManager.getSliderElement(); 81 | if (slider) slider.style.width = this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth + "px"; 82 | } 83 | else if (sliderWidth / window.innerWidth > 0.9) 84 | { 85 | new Notice(NOTICES.SLIDER_HIDE_WARNING, 5000); 86 | await this.plugin.settingsManager.saveSettings({ 87 | ...this.plugin.settingsManager.settings, 88 | sliderWidth: this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth 89 | }); 90 | text.setValue(this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth.toString()); 91 | const slider = this.plugin.uiManager.getSliderElement(); 92 | if (slider) slider.style.width = this.plugin.settingsManager.DEFAULT_SETTINGS.sliderWidth + "px"; 93 | } 94 | else 95 | { 96 | await this.plugin.settingsManager.saveSettings({ 97 | ...this.plugin.settingsManager.settings, 98 | sliderWidth: sliderWidth 99 | }); 100 | const slider = this.plugin.uiManager.getSliderElement(); 101 | if (slider) slider.style.width = sliderWidth + "px"; 102 | } 103 | }) 104 | ); 105 | } 106 | 107 | // Allow the change of the note width via text field input 108 | new Setting(containerEl) 109 | .setName("Enable text field") 110 | .setDesc("Enable to change the width via text field input.") 111 | .addToggle((cb: ToggleComponent) => 112 | { 113 | cb.setValue(this.plugin.settingsManager.getEnableTextInput()); 114 | cb.onChange(async (value: boolean) => 115 | { 116 | await this.plugin.settingsManager.saveSettings({ 117 | ...this.plugin.settingsManager.settings, 118 | enableTextInput: value, 119 | }); 120 | this.plugin.uiManager.updateUI(); 121 | this.display(); 122 | }); 123 | }); 124 | 125 | // Toggle to separately save the width of each note 126 | new Setting(containerEl) 127 | .setName("Change width for each note") 128 | .setDesc("Toggle to separately change and save the width of notes.") 129 | .addToggle((cb: ToggleComponent) => 130 | { 131 | cb.setValue( 132 | this.plugin.settingsManager.getEnableSaveWidthIndividually() 133 | ); 134 | cb.onChange(async (value: boolean) => 135 | { 136 | await this.plugin.settingsManager.saveSettings({ 137 | ...this.plugin.settingsManager.settings, 138 | enableSaveWidthIndividually: value 139 | }); 140 | 141 | if (value && !this.plugin.database) 142 | { 143 | this.plugin.database = new LokiDatabase(getDatabasePath(this.plugin.app, this.plugin, DATABASE_FILENAME)); 144 | await this.plugin.database.init(); 145 | } 146 | 147 | if (!value) 148 | { 149 | await this.plugin.settingsManager.saveSettings({ 150 | ...this.plugin.settingsManager.settings, 151 | enableChangeDefaultNoteWidth: false 152 | }); 153 | } 154 | this.display(); 155 | }); 156 | }); 157 | 158 | // Only show this setting if enableSaveWidthIndividually is enabled 159 | if (this.plugin.settingsManager.getEnableSaveWidthIndividually()) 160 | { 161 | // Toggle to change default note width 162 | new Setting(containerEl) 163 | .setName("Change default note width") 164 | .setDesc("Toggle to change the default width of notes.") 165 | .addToggle((cb: ToggleComponent) => 166 | { 167 | cb.setValue( 168 | this.plugin.settingsManager.getEnableChangeDefaultNoteWidth() 169 | ); 170 | cb.onChange(async (value: boolean) => 171 | { 172 | await this.plugin.settingsManager.saveSettings({ 173 | ...this.plugin.settingsManager.settings, 174 | enableChangeDefaultNoteWidth: value 175 | }); 176 | if (value) 177 | { 178 | this.plugin.commandsManager.enableCommand( 179 | "change-default-note-width" 180 | ); 181 | } else 182 | { 183 | this.plugin.commandsManager.disableCommand( 184 | "change-default-note-width" 185 | ); 186 | } 187 | this.display(); 188 | }); 189 | }); 190 | 191 | if (this.plugin.settingsManager.getEnableChangeDefaultNoteWidth()) 192 | { 193 | // Change the default width for notes 194 | new Setting(containerEl) 195 | .setName("Default width") 196 | .setDesc("Set the Default width each new note should have.") 197 | .addText((text) => 198 | text 199 | .setPlaceholder("36") 200 | .setValue(this.plugin.settingsManager.getDefaultNoteWidth().toString()) 201 | .onChange(async (value) => 202 | { 203 | let defaultWidth = parseInt(value); 204 | 205 | if (value === "" || value.trim() === "") 206 | { 207 | return; 208 | } 209 | else if (isNaN(defaultWidth)) 210 | { 211 | await this.plugin.settingsManager.saveSettings({ 212 | ...this.plugin.settingsManager.settings, 213 | defaultNoteWidth: this.plugin.settingsManager.DEFAULT_SETTINGS.defaultNoteWidth 214 | }); 215 | 216 | text.setValue(this.plugin.settingsManager.DEFAULT_SETTINGS.defaultNoteWidth.toString()); 217 | } else if (defaultWidth < 0) 218 | { 219 | await this.plugin.settingsManager.saveSettings({ 220 | ...this.plugin.settingsManager.settings, 221 | defaultNoteWidth: 0 222 | }); 223 | 224 | text.setValue("0"); 225 | } 226 | else if (defaultWidth > 100) 227 | { 228 | await this.plugin.settingsManager.saveSettings({ 229 | ...this.plugin.settingsManager.settings, 230 | defaultNoteWidth: 100 231 | }); 232 | 233 | text.setValue("100"); 234 | } 235 | else 236 | { 237 | await this.plugin.settingsManager.saveSettings({ ...this.plugin.settingsManager.settings, defaultNoteWidth: defaultWidth }); 238 | } 239 | }) 240 | ); 241 | } 242 | } 243 | 244 | // Toggle to enable the option to retrieve note width from the YAML front matter. 245 | new Setting(containerEl) 246 | .setName("Enable custom width via YAML front matter") 247 | .setDesc("Enable the option to retrieve note width from the YAML front matter.") 248 | .addToggle((cb: ToggleComponent) => 249 | { 250 | cb.setValue(this.plugin.settingsManager.getEnableYAMLWidth()); 251 | cb.onChange(async (value: boolean) => 252 | { 253 | await this.plugin.settingsManager.saveSettings({ 254 | ...this.plugin.settingsManager.settings, 255 | enableYAMLWidth: value, 256 | }); 257 | this.display(); 258 | }); 259 | }); 260 | 261 | // Only show this setting if enableYAMLWidth is enabled 262 | if (this.plugin.settingsManager.getEnableYAMLWidth()) 263 | { 264 | new Setting(containerEl) 265 | .setName("YAML front matter key for custom width") 266 | .setDesc("Specify the YAML front matter key to use for setting the custom width of the editor. If a note includes this key in its YAML front matter, the specified value will be used as the editor's width.") 267 | .addText((text) => 268 | { 269 | text.setPlaceholder("custom-width") 270 | .setValue(this.plugin.settingsManager.getYAMLKey()) 271 | .onChange(async (value) => 272 | { 273 | // Reset the timer on every change 274 | if (updateTimeout) clearTimeout(updateTimeout); 275 | 276 | updateTimeout = window.setTimeout(async () => 277 | { 278 | const oldKey = this.plugin.settingsManager.getYAMLKey(); 279 | 280 | if (!value || value.trim() === "") 281 | { 282 | return; 283 | } 284 | 285 | if (oldKey !== value) 286 | { 287 | const progressBarModal = new ProgressBarModal(this.app, PROGRESS_BAR_MODAL_KEY_TITLE_TEXT); 288 | progressBarModal.display(); 289 | 290 | // Call the method to replace old key with new key in all notes 291 | await this.yamlProcessor.replaceYamlKeyInAllNotes(oldKey, value, progressBarModal); 292 | 293 | progressBarModal.close(); 294 | } 295 | 296 | await this.plugin.settingsManager.saveSettings({ 297 | ...this.plugin.settingsManager.settings, 298 | yamlKey: value 299 | }); 300 | }, 1500); 301 | }); 302 | }); 303 | } 304 | 305 | // Only show priority list if getEnableYAMLWidth and getEnableSaveWidthIndividually 306 | if (this.plugin.settingsManager.getEnableYAMLWidth() && 307 | this.plugin.settingsManager.getEnableSaveWidthIndividually()) 308 | { 309 | new Setting(containerEl) 310 | .setName("Priority list") 311 | .setDesc("Choose the priority in which the following will be executed."); 312 | { 313 | const listContainer = containerEl.createEl("div"); 314 | 315 | // For each function, create a list item with up/down buttons 316 | this.plugin.settingsManager.getPriorityList().forEach((funcName, index) => 317 | { 318 | // Create a container for this list item 319 | const listItem = listContainer.createEl("div", { cls: DOM_IDENTIFIERS.PRIORITY_LIST_ITEM }); 320 | 321 | // Add the priority number 322 | const priority = listItem.createEl("span", { cls: DOM_IDENTIFIERS.PRIORITY_NUMBER }); 323 | priority.innerText = `${index + 1}. `; 324 | 325 | // Add the function name 326 | const funcNameContainer = listItem.createEl("div"); 327 | const funcNameSpan = funcNameContainer.createEl("span"); 328 | funcNameSpan.innerText = funcName; 329 | 330 | // Create the button container 331 | const buttonContainer = listItem.createEl("div"); 332 | buttonContainer.style.float = "right"; 333 | 334 | // Create the up button if this is not the first item 335 | if (index > 0) 336 | { 337 | const upButton = buttonContainer.createEl("button"); 338 | upButton.innerText = "\u2191"; 339 | upButton.style.marginRight = "8px"; 340 | upButton.addEventListener("click", async () => 341 | { 342 | const priorityList = this.plugin.settingsManager.getPriorityList(); 343 | [priorityList[index], priorityList[index - 1]] = [priorityList[index - 1], priorityList[index]]; 344 | await this.plugin.settingsManager.saveSettings({ 345 | ...this.plugin.settingsManager.settings, 346 | priorityList: priorityList 347 | }); 348 | this.display(); 349 | }); 350 | } 351 | 352 | // Create the down button if this is not the last item 353 | if (index < this.plugin.settingsManager.getPriorityList().length - 1) 354 | { 355 | const downButton = buttonContainer.createEl("button"); 356 | downButton.innerText = "\u2193"; 357 | downButton.style.marginRight = "8px"; 358 | downButton.addEventListener("click", async () => 359 | { 360 | const priorityList = this.plugin.settingsManager.getPriorityList(); 361 | [priorityList[index], priorityList[index + 1]] = [priorityList[index + 1], priorityList[index]]; 362 | await this.plugin.settingsManager.saveSettings({ 363 | ...this.plugin.settingsManager.settings, 364 | priorityList: priorityList 365 | }); 366 | this.display(); 367 | }); 368 | 369 | } 370 | }); 371 | 372 | } 373 | } 374 | 375 | // Donation button 376 | containerEl.appendChild(this.donationButton.createDonationButton(containerEl)); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/statusbar/statusBarManager.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import { DOM_IDENTIFIERS } from "src/utility/constants"; 3 | import { classSelector } from "src/utility/utilities"; 4 | import { domElementManager } from "src/ui/domElementManager"; 5 | 6 | /** 7 | * Manages the status bar related functionalities for the CustomNoteWidth plugin. 8 | */ 9 | export default class StatusBarManager 10 | { 11 | wrapper: HTMLDivElement | null = null; 12 | plugin: CustomNoteWidth; 13 | statusBarItemEl: HTMLElement | null = null; 14 | 15 | /** 16 | * Constructs a new StatusBarManager instance. 17 | * @param plugin - Reference to the CustomNoteWidth plugin. 18 | */ 19 | constructor(plugin: CustomNoteWidth) 20 | { 21 | this.plugin = plugin; 22 | this.setWrapper(plugin.wrapperManager.getWrapper()); 23 | } 24 | 25 | /** 26 | * Appends the wrapper to the status bar. 27 | */ 28 | public appendToStatusBar(): void 29 | { 30 | if (!this.wrapper || (!this.plugin.settingsManager.getEnableSlider() && !this.plugin.settingsManager.getEnableTextInput())) 31 | { 32 | this.wrapper = null; 33 | return; 34 | } 35 | 36 | this.statusBarItemEl = this.plugin.addStatusBarItem(); 37 | this.statusBarItemEl.appendChild(this.wrapper); 38 | this.statusBarItemEl.style.paddingLeft = "0px"; 39 | const statusBar = domElementManager.querySelector(classSelector(DOM_IDENTIFIERS.STATUSBAR)); 40 | statusBar?.insertBefore(this.statusBarItemEl, statusBar.firstChild); 41 | } 42 | 43 | /** 44 | * Removes the status bar item. 45 | */ 46 | public removeStatusBarItem(): void 47 | { 48 | if (!this.statusBarItemEl) 49 | return; 50 | 51 | this.statusBarItemEl.detach(); 52 | } 53 | 54 | /** 55 | * Sets the wrapper element for the status bar. 56 | * @param wrapper - The wrapper element to set. 57 | */ 58 | public setWrapper(wrapper: HTMLDivElement | null): void 59 | { 60 | this.wrapper = wrapper; 61 | } 62 | 63 | /** 64 | * Displays the status bar item. 65 | */ 66 | public showStatusBarItem(): void 67 | { 68 | if (!this.statusBarItemEl) 69 | return; 70 | 71 | this.statusBarItemEl.style.display = "flex"; 72 | } 73 | 74 | /** 75 | * Hides the status bar item. 76 | */ 77 | public hideStatusBarItem(): void 78 | { 79 | if (!this.statusBarItemEl) 80 | return; 81 | 82 | this.statusBarItemEl.style.display = "none"; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/statusbar/wrapperManager.ts: -------------------------------------------------------------------------------- 1 | import { DOM_IDENTIFIERS } from "src/utility/constants"; 2 | 3 | /** 4 | * Manages the wrapper functionalities for the status bar. 5 | */ 6 | export default class WrapperManager 7 | { 8 | /** Wrapper element for the status bar. */ 9 | wrapper: HTMLDivElement | null = null; 10 | 11 | /** 12 | * Constructs a new WrapperManager instance and creates a wrapper. 13 | */ 14 | constructor() 15 | { 16 | this.createWrapper(); 17 | } 18 | 19 | /** 20 | * Creates a wrapper div to hold all elements added to the status bar. 21 | */ 22 | public createWrapper(): void 23 | { 24 | this.wrapper = document.createElement("div"); 25 | this.wrapper.id = DOM_IDENTIFIERS.WRAPPER; 26 | } 27 | 28 | /** 29 | * Removes the wrapper div from the status bar. 30 | */ 31 | public removeWrapper(): void 32 | { 33 | if (!this.wrapper) return; 34 | 35 | this.wrapper.remove(); 36 | this.wrapper = null; 37 | } 38 | 39 | /** 40 | * Appends the provided elements to the wrapper. 41 | * @param elements - Array of HTML elements to be appended to the wrapper. 42 | */ 43 | public appendToWrapper(...elements: (HTMLElement | void)[]): void 44 | { 45 | if (!this.wrapper) return; 46 | 47 | for (const element of elements) 48 | { 49 | if (element) 50 | { 51 | this.wrapper.appendChild(element); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Retrieves the wrapper element. 58 | * @returns The wrapper element or null if not present. 59 | */ 60 | public getWrapper(): HTMLDivElement | null 61 | { 62 | return this.wrapper; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/domElementManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages and caches DOM elements for efficient querying. 3 | */ 4 | class DOMElementManager 5 | { 6 | /** Cache to store queried DOM elements. */ 7 | private cachedElements: Map = new Map(); 8 | 9 | /** 10 | * Searches for a DOM element using the provided selector and caches it. 11 | * @param selector - CSS selector to query the element. 12 | * @param context - Context (root element) in which to perform the query. 13 | * @returns The queried DOM element or null if not found. 14 | */ 15 | public querySelector(selector: string, context: Document | Element = document): Element | null 16 | { 17 | const cacheKey = this.generateCacheKey(selector, context); 18 | 19 | // Check if the element is already in the cache. 20 | if (this.cachedElements.has(cacheKey)) 21 | { 22 | return this.cachedElements.get(cacheKey) || null; 23 | } 24 | 25 | // If the element is not in the cache, find it in the DOM and store it. 26 | const element = context.querySelector(selector); 27 | this.cachedElements.set(cacheKey, element); 28 | return element; 29 | } 30 | 31 | /** 32 | * Invalidates (removes) an element from the cache. 33 | * @param selector - CSS selector of the element to invalidate. 34 | */ 35 | public invalidateCache(key: string): void 36 | { 37 | this.cachedElements.delete(key); 38 | } 39 | 40 | /** 41 | * Clears the entire cache. 42 | */ 43 | public clearCache(): void 44 | { 45 | this.cachedElements.clear(); 46 | } 47 | 48 | /** 49 | * Generates a cache key based on the selector and context. 50 | * @param selector - The CSS selector. 51 | * @param context - The context in which to perform the query. 52 | * @returns The generated cache key. 53 | */ 54 | public generateCacheKey(selector: string, context: Document | Element = document): string 55 | { 56 | return `${selector}-${context}`; 57 | } 58 | 59 | } 60 | 61 | // Exporting an instance of DOMElementManager so it can be used anywhere. 62 | export const domElementManager = new DOMElementManager(); 63 | -------------------------------------------------------------------------------- /src/ui/uiElementCreator.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import { DOM_IDENTIFIERS } from "src/utility/constants"; 3 | 4 | export default class UIElementCreator 5 | { 6 | 7 | /** 8 | * Constructs a new UIElementCreator instance and initializes UI elements. 9 | * @param plugin - Reference to the CustomNoteWidth plugin. 10 | */ 11 | constructor(private plugin: CustomNoteWidth) 12 | { 13 | this.createUIElements(); 14 | } 15 | 16 | /** 17 | * Creates the slider UI element. 18 | * @returns The created slider element. 19 | */ 20 | private createSliderElement(): HTMLInputElement 21 | { 22 | // Return dummy input element if slider is not enabled 23 | if (!this.plugin.settingsManager.getEnableSlider()) 24 | { 25 | const slider = document.createElement("input"); 26 | slider.id = DOM_IDENTIFIERS.DUMMY; 27 | return slider; 28 | } 29 | 30 | const slider = document.createElement("input"); 31 | slider.id = DOM_IDENTIFIERS.SLIDER; 32 | slider.type = "range"; 33 | slider.min = "0"; 34 | slider.max = "100"; 35 | slider.value = this.plugin.settingsManager.getWidthPercentage().toString(); 36 | slider.style.width = this.plugin.settingsManager.getSliderWidth() + "px"; 37 | 38 | return slider; 39 | } 40 | 41 | /** 42 | * Creates either a text input field or a simple span element based on the provided slider. 43 | * @param slider - The reference slider element. 44 | * @param isInput - Flag indicating whether to create an input field or a span. 45 | * @returns The created HTMLElement or null. 46 | */ 47 | private createTextInput(slider: HTMLInputElement | null, isInput: boolean): HTMLElement | null 48 | { 49 | let text: HTMLElement | null = null; 50 | 51 | if (slider) 52 | { 53 | if (isInput) 54 | { 55 | text = document.createElement("input"); 56 | (text as HTMLInputElement).type = "number"; 57 | (text as HTMLInputElement).value = slider.value; 58 | (text as HTMLInputElement).min = "0"; 59 | (text as HTMLInputElement).max = "100"; 60 | text.style.height = "160%"; 61 | text.style.width = "38px"; 62 | text.style.fontSize = "108%"; 63 | } else 64 | { 65 | text = document.createElement("span"); 66 | text.textContent = slider.value; 67 | text.style.fontSize = "102.5%"; 68 | text.style.marginBottom = "-0.5px"; 69 | } 70 | 71 | text.id = DOM_IDENTIFIERS.SLIDER_VALUE; 72 | } 73 | 74 | return text; 75 | } 76 | 77 | /** 78 | * Creates and configures either the text input field or the span based on the provided slider and width value. 79 | * @param slider - The reference slider element. 80 | * @param widthValue - Optional width value to set. 81 | * @returns The created HTMLElement or null. 82 | */ 83 | private createAndConfigureText(slider: HTMLInputElement | null, widthValue?: string): HTMLElement | null 84 | { 85 | let sliderValueText: HTMLElement | null = null; 86 | 87 | if (slider && this.plugin.settingsManager.getEnableTextInput()) 88 | { 89 | sliderValueText = this.createTextInput(slider, this.plugin.settingsManager.getEnableTextInput()); 90 | if (sliderValueText) 91 | { 92 | this.plugin.eventHandler.handleTextInputEvent(slider, sliderValueText); 93 | } 94 | } else if (slider) 95 | { 96 | sliderValueText = this.createTextInput(slider, this.plugin.settingsManager.getEnableTextInput()); 97 | if (sliderValueText) 98 | { 99 | this.plugin.eventHandler.handleTextSpanEvent(slider, sliderValueText); 100 | } 101 | } else if (this.plugin.settingsManager.getEnableTextInput()) 102 | { 103 | sliderValueText = this.createTextInput(null, this.plugin.settingsManager.getEnableTextInput()); 104 | if (sliderValueText && widthValue) 105 | { 106 | (sliderValueText as HTMLInputElement).value = widthValue; 107 | } 108 | } 109 | 110 | return sliderValueText; 111 | } 112 | 113 | /** 114 | * Creates the UI elements and attaches them to the plugin's wrapper. 115 | */ 116 | public createUIElements(): void 117 | { 118 | let slider: HTMLInputElement | null = null; 119 | let sliderValueText: HTMLElement | null = null; 120 | 121 | if (this.plugin.settingsManager.getEnableSlider()) 122 | { 123 | slider = this.createSliderElement(); 124 | if (slider) 125 | { 126 | sliderValueText = this.createAndConfigureText(slider); 127 | } 128 | } 129 | 130 | if (this.plugin.settingsManager.getEnableTextInput() && !slider) 131 | { 132 | const dummySlider = document.createElement("input"); 133 | dummySlider.value = this.plugin.settingsManager.getWidthPercentage().toString(); 134 | sliderValueText = this.createAndConfigureText(dummySlider); 135 | } 136 | 137 | if (slider && sliderValueText) 138 | { 139 | this.plugin.wrapperManager.appendToWrapper(slider, sliderValueText); 140 | } else if (sliderValueText) 141 | { 142 | this.plugin.wrapperManager.appendToWrapper(sliderValueText); 143 | } 144 | 145 | this.plugin.statusBarManager.appendToStatusBar(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/ui/uiManager.ts: -------------------------------------------------------------------------------- 1 | import CustomNoteWidth from "src/main"; 2 | import { DOM_IDENTIFIERS } from "src/utility/constants"; 3 | import { idSelector } from "src/utility/utilities"; 4 | import { domElementManager } from "src/ui/domElementManager"; 5 | 6 | /** 7 | * Manages and updates the UI elements for the CustomNoteWidth plugin. 8 | */ 9 | export default class UIManager 10 | { 11 | /** Reference to the slider element. */ 12 | slider: HTMLInputElement | null = null; 13 | 14 | /** Reference to the text field input element. */ 15 | textField: HTMLInputElement | null = null; 16 | 17 | /** 18 | * Constructs a new UIManager instance and initializes the UI elements. 19 | * @param plugin - Reference to the CustomNoteWidth plugin. 20 | */ 21 | constructor(private plugin: CustomNoteWidth) 22 | { 23 | this.initializeElements(); 24 | } 25 | 26 | /** 27 | * Initializes the slider and text field elements from the DOM. 28 | */ 29 | private initializeElements(): void 30 | { 31 | this.slider = this.getSliderElement(); 32 | this.textField = this.getTextFieldElement(); 33 | } 34 | 35 | /** 36 | * Retrieves the slider UI element from the DOM. 37 | * @returns The slider element or null if not found. 38 | */ 39 | public getSliderElement(): HTMLInputElement | null 40 | { 41 | let slider = this.slider; 42 | if (!slider) 43 | { 44 | slider = domElementManager.querySelector(idSelector(DOM_IDENTIFIERS.SLIDER)) as HTMLInputElement; 45 | if (!slider) 46 | { 47 | console.error("Slider element could not be found."); 48 | return null; 49 | } 50 | } 51 | return slider; 52 | } 53 | 54 | /** 55 | * Retrieves the text field input UI element from the DOM. 56 | * @returns The text field input element or null if not found. 57 | */ 58 | public getTextFieldElement(): HTMLInputElement | null 59 | { 60 | let textField = this.textField; 61 | if (!textField) 62 | { 63 | textField = domElementManager.querySelector(idSelector(DOM_IDENTIFIERS.SLIDER_VALUE)) as HTMLInputElement; 64 | if (!textField) 65 | { 66 | console.error("Text field element could not be found."); 67 | return null; 68 | } 69 | } 70 | return textField; 71 | } 72 | 73 | /** 74 | * Sets the value for both the slider and text field input elements. 75 | * @param value - The value to set. 76 | */ 77 | public setSliderAndTextField(value: number): void 78 | { 79 | const slider = this.getSliderElement(); 80 | const sliderTextField = this.getTextFieldElement(); 81 | 82 | if (slider) 83 | { 84 | slider.value = value.toString(); 85 | } 86 | 87 | if (sliderTextField) 88 | { 89 | sliderTextField.value = value.toString(); 90 | } 91 | } 92 | 93 | /** 94 | * Updates the UI and the width of the editor based on the provided width value. 95 | * @param width - The width value. 96 | */ 97 | public updateUIAndEditorWidth(width: number): void 98 | { 99 | this.plugin.uiManager.setSliderAndTextField(width); 100 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(width); 101 | } 102 | 103 | /** 104 | * Updates the UI directly to apply changes made in the settings tab. 105 | */ 106 | public updateUI(): void 107 | { 108 | this.plugin.wrapperManager.removeWrapper(); 109 | this.plugin.statusBarManager.removeStatusBarItem(); 110 | this.plugin.wrapperManager.createWrapper(); 111 | this.plugin.statusBarManager.setWrapper(this.plugin.wrapperManager.getWrapper()); 112 | this.plugin.uiElementCreator.createUIElements(); 113 | this.plugin.noteWidthManager.updateNoteWidthEditorStyle(this.plugin.settingsManager.getWidthPercentage()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/utility/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration constants for the application. 3 | */ 4 | export const CONFIG = { 5 | /** Delay in milliseconds for debounce operations. */ 6 | DEBOUNCE_DELAY: 300, 7 | 8 | /** Threshold for hiding the slider. */ 9 | SLIDER_HIDE_THRESHOLD: 0.954, 10 | 11 | /** Maximum length of the slider value. */ 12 | SLIDER_VALUE_MAX_LENGTH: 3 13 | }; 14 | -------------------------------------------------------------------------------- /src/utility/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * UUID format string used for generating unique identifiers. 3 | */ 4 | export const UUID_FORMAT = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"; 5 | 6 | /** 7 | * Regular expression used for detecting and parsing YAML frontmatter sections. 8 | */ 9 | export const YAML_FRONTMATTER_REGEX = new RegExp(/^---\n([\s\S]*?)\n---/); 10 | 11 | /** 12 | * Default width (in percentage) applied to notes. 13 | */ 14 | export const DEFAULT_NOTE_WIDTH = 36; 15 | 16 | /** 17 | * Human-readable name of the plugin. 18 | */ 19 | export const PLUGIN_NAME = "Custom Note Width"; 20 | 21 | /** 22 | * Name of the database collection used for storing note widths. 23 | */ 24 | export const DATABASE_COLLECTION_NOTE_WIDTHS = "NoteWidths"; 25 | 26 | /** 27 | * Filename of the database used for storing note widths. 28 | */ 29 | export const DATABASE_FILENAME = "noteWidthDatabase.json"; 30 | 31 | /** 32 | * Link for donations to support the plugin's development. 33 | */ 34 | export const DONATION_LINK = "https://ko-fi.com/skater_"; 35 | 36 | /** 37 | * Disclaimer text displayed when presenting the donation link. 38 | */ 39 | export const DONATION_DISCLAIMER_TEXT = "Disclaimer: Please note that clicking the image will open a link in your browser."; 40 | 41 | /** 42 | * Start delimiter for YAML frontmatter sections. 43 | */ 44 | export const YAML_START = "---\n"; 45 | 46 | /** 47 | * End delimiter for YAML frontmatter sections. 48 | */ 49 | export const YAML_END = "\n---"; 50 | 51 | /** 52 | * Double newline used as a separator in YAML sections. 53 | */ 54 | export const YAML_NEWLINE = "\n\n"; 55 | 56 | /** 57 | * Key used in YAML frontmatter to store the note's unique identifier. 58 | */ 59 | export const NOTE_ID_KEY = "noteID"; 60 | 61 | /** 62 | * Text displayed on the cancel button in the plugin's UI. 63 | */ 64 | export const CANCEL_BUTTON_TEXT = "Cancel"; 65 | 66 | /** 67 | * Text displayed on the apply button in the plugin's UI. 68 | */ 69 | export const APPLY_BUTTON_TEXT = "Apply"; 70 | 71 | /** 72 | * Title for the modal used when updating YAML frontmatter keys in all notes. 73 | */ 74 | export const PROGRESS_BAR_MODAL_KEY_TITLE_TEXT = "Changing all YAML-Frontmatter keys..."; 75 | 76 | /** 77 | * Title for the modal used when updating the value of all YAML frontmatter keys in all notes. 78 | */ 79 | export const PROGRESS_BAR_MODAL_VALUE_TITLE_TEXT = "Changing the value for all YAML-Frontmatter's..."; 80 | 81 | /** 82 | * List of priority checks performed to determine the width of a note. 83 | * @property {string} SAVED_NOTE_WIDTH - Check for a saved note width in the database. 84 | * @property {string} YAML_NOTE_WIDTH - Check for a width specified in the note's YAML frontmatter. 85 | */ 86 | export const PRIORITY_LIST = { 87 | SAVED_NOTE_WIDTH: "Check for saved note width", 88 | YAML_NOTE_WIDTH: "Check for YAML-Frontmatter width" 89 | }; 90 | 91 | /** 92 | * Definitions of commands provided by the plugin. 93 | * @property {Object} CHANGE_NOTE_WIDTH - Command to change the width of the open note. 94 | * @property {string} CHANGE_NOTE_WIDTH.ID - Command identifier. 95 | * @property {string} CHANGE_NOTE_WIDTH.NAME - Human-readable command name. 96 | * @property {string} CHANGE_NOTE_WIDTH.MODAL_TITLE - Title for the modal prompt. 97 | * 98 | * @property {Object} CHANGE_DEFAULT_NOTE_WIDTH - Command to change the default note width. 99 | * @property {string} CHANGE_DEFAULT_NOTE_WIDTH.ID - Command identifier. 100 | * @property {string} CHANGE_DEFAULT_NOTE_WIDTH.NAME - Human-readable command name. 101 | * @property {string} CHANGE_DEFAULT_NOTE_WIDTH.MODAL_TITLE - Title for the modal prompt. 102 | * 103 | * @property {Object} CHANGE_ALL_NOTE_WIDTH - Command to change the width for all notes. 104 | * @property {string} CHANGE_ALL_NOTE_WIDTH.ID - Command identifier. 105 | * @property {string} CHANGE_ALL_NOTE_WIDTH.NAME - Human-readable command name. 106 | * @property {string} CHANGE_ALL_NOTE_WIDTH.MODAL_TITLE - Title for the modal prompt. 107 | */ 108 | export const COMMANDS = { 109 | CHANGE_NOTE_WIDTH: { 110 | ID: "change-note-width", 111 | NAME: "Change the width of the open note", 112 | MODAL_TITLE: "Enter the width for the open note (%)" 113 | }, 114 | CHANGE_DEFAULT_NOTE_WIDTH: { 115 | ID: "change-default-note-width", 116 | NAME: "Change the default note width", 117 | MODAL_TITLE: "Enter the default note width (%)" 118 | }, 119 | CHANGE_ALL_NOTE_WIDTH: { 120 | ID: "change-all-note-width", 121 | NAME: "Change the width for all notes", 122 | MODAL_TITLE: "Enter the width for all notes (%)" 123 | } 124 | }; 125 | 126 | /** 127 | * Notices and warnings displayed to users. 128 | * @property {string} SLIDER_HIDE_WARNING - Warning message when the slider UI element is too large. 129 | */ 130 | export const NOTICES = { 131 | SLIDER_HIDE_WARNING: "Slider too large!" 132 | }; 133 | 134 | /** 135 | * Identifiers for various DOM elements used in the plugin's UI. 136 | * @property {string} DUMMY - Identifier for a dummy element. 137 | * @property {string} SLIDER - Identifier for the width adjustment slider. 138 | * @property {string} SLIDER_VALUE - Identifier for displaying the current slider value. 139 | * @property {string} WRAPPER - Identifier for the note width wrapper element. 140 | * @property {string} STATUSBAR_ELEMENT - Identifier for the plugin's status bar UI element. 141 | * @property {string} DONATION_BUTTON - Identifier for the donation button. 142 | * @property {string} STATUSBAR - Identifier for Obsidian's status bar. 143 | * @property {string} CUSTOM_NOTE_WIDTH - Identifier for the custom note width element. 144 | * @property {string} MARKDOWN_PREVIEW_VIEW - Identifier for the markdown preview view. 145 | * @property {string} CM_SCROLLER - Identifier for the code mirror scroller. 146 | * @property {string} PROGRESS_BAR_CONTAINER - Identifier for the progress bar container. 147 | * @property {string} MODAL_CLOSE_BUTTON - Identifier for the modal's close button. 148 | * @property {string} NWM_CONTAINER - Identifier for the note width manager container. 149 | * @property {string} NWM_INPUT_CONTAINER - Identifier for the note width manager input container. 150 | * @property {string} NWM_BUTTON_CONTAINER - Identifier for the note width manager button container. 151 | * @property {string} PRIORITY_LIST_ITEM - Identifier for a priority list item. 152 | * @property {string} PRIORITY_NUMBER - Identifier for displaying priority numbers. 153 | */ 154 | export const DOM_IDENTIFIERS = { 155 | DUMMY: "dummy", 156 | SLIDER: "custom-note-width-slider", 157 | SLIDER_VALUE: "custom-note-width-slider-value", 158 | WRAPPER: "custom-note-width-wrapper", 159 | STATUSBAR_ELEMENT: "plugin-custom-note-width", 160 | DONATION_BUTTON: "custom-note-width-donation-button", 161 | STATUSBAR: "status-bar", 162 | CUSTOM_NOTE_WIDTH: "custom-note-width", 163 | MARKDOWN_PREVIEW_VIEW: "markdown-preview-view", 164 | CM_SCROLLER: "cm-scroller", 165 | PROGRESS_BAR_CONTAINER: "progress-bar-container", 166 | PROGRESS_OUTER: "progress-bar-outer", 167 | PROGRESS_INNER: "progress-bar-inner", 168 | MODAL_CLOSE_BUTTON: "modal-close-button", 169 | NWM_CONTAINER: "nwm-container", 170 | NWM_INPUT_CONTAINER: "nwm-input-container", 171 | NWM_BUTTON_CONTAINER: "nwm-button-container", 172 | PRIORITY_LIST_ITEM: "priority-list-item", 173 | PRIORITY_NUMBER: "priority-number" 174 | }; 175 | 176 | /** 177 | * Generates a loaded message for the plugin with the given version. 178 | * @param {string} version - The version of the plugin. 179 | * @returns {string} The formatted loaded message. 180 | */ 181 | export function getLoadedMessage(version: string): string 182 | { 183 | return `${PLUGIN_NAME} v${version} loaded!`; 184 | } 185 | 186 | /** 187 | * Generates an unloaded message for the plugin with the given version. 188 | * @param {string} version - The version of the plugin. 189 | * @returns {string} The formatted unloaded message. 190 | */ 191 | export function getUnloadedMessage(version: string): string 192 | { 193 | return `${PLUGIN_NAME} v${version} unloaded!`; 194 | } 195 | 196 | // ============================ 197 | //#region KOFI SVG 198 | // ============================ 199 | 200 | export const KOFI_SVG = 201 | ''; 202 | 203 | // ============================ 204 | //#endregion 205 | // ============================ 206 | -------------------------------------------------------------------------------- /src/utility/lokiDatabase.ts: -------------------------------------------------------------------------------- 1 | import Loki from "lokijs"; 2 | import { DATABASE_COLLECTION_NOTE_WIDTHS } from "src/utility/constants"; 3 | 4 | /** 5 | * Configuration interface for the Loki database. 6 | */ 7 | interface LokiDatabaseConfig 8 | { 9 | /** Indicates whether the database should be autosaved. */ 10 | autosave?: boolean; 11 | /** Interval (in milliseconds) for autosave operations. */ 12 | autosaveInterval?: number; 13 | } 14 | 15 | /** 16 | * Represents a note with an ID and width property. 17 | */ 18 | interface Note 19 | { 20 | /** Unique identifier for the note. */ 21 | id: string; 22 | /** Width of the note. */ 23 | width: number; 24 | } 25 | 26 | /** 27 | * Class responsible for managing operations on the Loki database. 28 | */ 29 | export default class LokiDatabase 30 | { 31 | private db: Loki; 32 | private notesCollection: Loki.Collection; 33 | 34 | /** 35 | * Constructs a new LokiDatabase instance. 36 | * @param dbPath - Path to the Loki database file. 37 | * @param config - Configuration options for the database. 38 | */ 39 | constructor(dbPath: string, config: LokiDatabaseConfig = { autosave: true, autosaveInterval: 4000 }) 40 | { 41 | this.db = new Loki(dbPath, { 42 | autosave: config.autosave, 43 | autosaveInterval: config.autosaveInterval 44 | }); 45 | } 46 | 47 | /** 48 | * Initializes the database and collections. 49 | */ 50 | public async init(): Promise 51 | { 52 | try 53 | { 54 | await this.loadDatabase(); 55 | this.getOrCreateCollection(DATABASE_COLLECTION_NOTE_WIDTHS); 56 | } catch (error) 57 | { 58 | console.error(`Error initializing the database: ${error.message}`); 59 | } 60 | } 61 | 62 | /** 63 | * Adds or updates a note with the given ID and width. 64 | * @param note_id - Unique identifier for the note. 65 | * @param note_width - Width of the note. 66 | */ 67 | public async addNote(note_id: string, note_width: number): Promise 68 | { 69 | try 70 | { 71 | const existingNote = this.notesCollection.findOne({ id: note_id }); 72 | if (existingNote) 73 | { 74 | this.updateNote(existingNote, note_width); 75 | } else 76 | { 77 | this.insertNote({ id: note_id, width: note_width }); 78 | } 79 | } catch (error) 80 | { 81 | console.error(`Error adding note: ${error.message}`); 82 | } 83 | } 84 | 85 | /** 86 | * Loads the Loki database into memory. 87 | * @returns Promise that resolves when the database is loaded. 88 | */ 89 | private loadDatabase(): Promise 90 | { 91 | return new Promise((resolve, reject) => 92 | { 93 | this.db.loadDatabase({}, (error) => 94 | { 95 | if (error) 96 | { 97 | reject(error); 98 | } else 99 | { 100 | resolve(); 101 | } 102 | }); 103 | }); 104 | } 105 | 106 | /** 107 | * Retrieves or creates a collection in the database. 108 | * @param collectionName - Name of the collection. 109 | */ 110 | private getOrCreateCollection(collectionName: string): void 111 | { 112 | const collection = this.db.getCollection(collectionName); 113 | if (!collection) 114 | { 115 | this.notesCollection = this.db.addCollection(collectionName); 116 | } else 117 | { 118 | this.notesCollection = collection; 119 | } 120 | } 121 | 122 | /** 123 | * Updates the width of an existing note. 124 | * @param existingNote - The note to update. 125 | * @param note_width - New width value for the note. 126 | */ 127 | public updateNote(existingNote: Note, note_width: number): void 128 | { 129 | if (existingNote.width !== note_width) 130 | { 131 | existingNote.width = note_width; 132 | this.notesCollection.update(existingNote); 133 | } 134 | } 135 | 136 | /** 137 | * Inserts a new note into the collection. 138 | * @param note - The note to insert. 139 | */ 140 | private insertNote(note: Note): void 141 | { 142 | this.notesCollection.insert(note); 143 | } 144 | 145 | /** 146 | * Checks if a note with the given ID exists in the database. 147 | * @param note_id - Unique identifier for the note. 148 | * @returns True if the note exists, otherwise false. 149 | */ 150 | public noteExists(note_id: string): boolean 151 | { 152 | const note = this.notesCollection.findOne({ id: note_id }); 153 | return note !== null; 154 | } 155 | 156 | /** 157 | * Retrieves the width of a note with the given ID. 158 | * @param note_id - Unique identifier for the note. 159 | * @returns Width of the note or null if not found. 160 | */ 161 | public getNoteWidth(note_id: string): number | null 162 | { 163 | const note = this.notesCollection.findOne({ id: note_id }); 164 | return note ? note.width : null; 165 | } 166 | 167 | /** 168 | * Updates the width of all notes in the database. 169 | * @param width - New width value for all notes. 170 | */ 171 | public async updateAllNotesWidth(width: number): Promise 172 | { 173 | try 174 | { 175 | const allNotes = this.notesCollection.find(); 176 | for (const note of allNotes) 177 | { 178 | note.width = width; 179 | this.notesCollection.update(note); 180 | } 181 | } catch (error) 182 | { 183 | console.error(`Error updating width for all notes: ${error.message}`); 184 | } 185 | } 186 | 187 | /** 188 | * Removes the width of a note with the given ID. 189 | * @param note_id - Unique identifier for the note. 190 | */ 191 | public removeNoteWidth(note_id: string): void 192 | { 193 | const note = this.notesCollection.findOne({ id: note_id }); 194 | if (note) 195 | { 196 | this.notesCollection.remove(note); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/utility/utilities.ts: -------------------------------------------------------------------------------- 1 | import { App, FileSystemAdapter, MarkdownView } from "obsidian"; 2 | import * as path from "path"; 3 | import CustomNoteWidth from "src/main"; 4 | import { DOM_IDENTIFIERS } from "src/utility/constants"; 5 | 6 | /** 7 | * Retrieves the absolute path to the vault. 8 | * @param app - The Obsidian app instance. 9 | * @returns The absolute vault path. 10 | */ 11 | export function getAbsoluteVaultPath(app: App): string 12 | { 13 | let adapter = app.vault.adapter; 14 | if (adapter instanceof FileSystemAdapter) 15 | { 16 | return adapter.getBasePath(); 17 | } 18 | return ""; 19 | } 20 | 21 | /** 22 | * Retrieves the absolute path to the plugin directory. 23 | * @param app - The Obsidian app instance. 24 | * @returns The absolute plugin directory path. 25 | */ 26 | export function getAbsolutePluginPath(app: App, plugin: CustomNoteWidth): string 27 | { 28 | return path.join(getAbsoluteVaultPath(app), plugin.manifest.dir as string); 29 | } 30 | 31 | /** 32 | * Retrieves the absolute path to the database file within the plugin directory. 33 | * @param app - The Obsidian app instance. 34 | * @param filename - The database filename. 35 | * @returns The absolute path to the database file. 36 | */ 37 | export function getDatabasePath(app: App, plugin: CustomNoteWidth, filename: string): string 38 | { 39 | return path.join(getAbsolutePluginPath(app, plugin), filename); 40 | } 41 | 42 | /** 43 | * Checks if the provided value is a record. 44 | * @param value - Value to check. 45 | * @returns True if the value is a record, false otherwise. 46 | */ 47 | export function isRecord(value: unknown): value is Record 48 | { 49 | if (typeof value === "object" && value !== null) 50 | { 51 | const valueAsRecord = value as Record; 52 | for (const key in valueAsRecord) 53 | { 54 | if (typeof key !== "string" || typeof valueAsRecord[key] === "function" || valueAsRecord[key] === undefined) 55 | { 56 | return false; 57 | } 58 | } 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | /** 65 | * Checks if the active leaf in the Obsidian app is a markdown view. 66 | * @param app - The Obsidian app instance. 67 | * @returns True if the active leaf is a markdown view, false otherwise. 68 | */ 69 | export function isActiveLeafMarkdown(app: App): boolean 70 | { 71 | const activeView = app.workspace.getActiveViewOfType(MarkdownView); 72 | return !!activeView; 73 | } 74 | 75 | /** 76 | * Retrieves the active markdown view in the Obsidian app. 77 | * @param app - The Obsidian app instance. 78 | * @returns The active markdown view or null if none. 79 | */ 80 | export function getActiveMarkdownView(app: App): MarkdownView | null 81 | { 82 | return app.workspace.getActiveViewOfType(MarkdownView); 83 | } 84 | 85 | /** 86 | * Retrieves the active editor's DOM element in a specific mode (either preview or source). 87 | * @param app - The Obsidian app instance. 88 | * @param mode - The desired mode ("preview" or "source"). 89 | * @returns The editor's DOM element or null if not found. 90 | */ 91 | export function getActiveEditorDiv(app: App, mode: string): Element | null 92 | { 93 | const activeView = getActiveMarkdownView(app); 94 | if (activeView) 95 | { 96 | if (mode === "preview") 97 | { 98 | return activeView.containerEl.querySelector(classSelector(DOM_IDENTIFIERS.MARKDOWN_PREVIEW_VIEW)); 99 | } else if (mode === "source") 100 | { 101 | return activeView.containerEl.querySelector(classSelector(DOM_IDENTIFIERS.CM_SCROLLER)); 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | /** 108 | * Retrieves the mode of the active markdown view. 109 | * @returns The mode ("preview" or "source") or null if no active markdown view. 110 | */ 111 | export function getEditorMode(): string | null 112 | { 113 | const view = getActiveMarkdownView(this.app); 114 | if (view) return view.getMode(); 115 | return null; 116 | } 117 | 118 | /** 119 | * Calculates and returns the width of a note based on the width percentage and editor div dimensions. 120 | * @param widthPercentage - The width percentage. 121 | * @param editorDiv - The editor's DOM element. 122 | * @returns The calculated note width or null if unable to calculate. 123 | */ 124 | export async function calculateNoteWidth(widthPercentage: number, editorDiv: Element): Promise 125 | { 126 | const charWidth = await getCharWidth(); 127 | if (charWidth) 128 | { 129 | const noteWidth = charWidth * (1 + (widthPercentage / 100) * (editorDiv.clientWidth / charWidth - 1)); 130 | return noteWidth; 131 | } else 132 | { 133 | return null; 134 | } 135 | } 136 | 137 | /** 138 | * Calculates and returns the width of a single character in the editor. 139 | * @returns The character width or null if unable to calculate. 140 | */ 141 | export async function getCharWidth(): Promise 142 | { 143 | const editorDiv = document.querySelector(classSelector(DOM_IDENTIFIERS.MARKDOWN_PREVIEW_VIEW)) || document.querySelector(classSelector(DOM_IDENTIFIERS.CM_SCROLLER)); 144 | if (!editorDiv) 145 | { 146 | console.error("Editor not found!", new Error().stack); 147 | return null; 148 | } 149 | const editorFontFamily = window.getComputedStyle(editorDiv).fontFamily; 150 | const editorFontSize = window.getComputedStyle(editorDiv).fontSize; 151 | const canvas = document.createElement("canvas"); 152 | const context = canvas.getContext("2d"); 153 | if (!context) 154 | { 155 | console.error("Failed to get canvas context!", new Error().stack); 156 | return null; 157 | } 158 | context.font = `${editorFontSize} ${editorFontFamily}`; 159 | const metrics = context.measureText("m"); 160 | const charWidth = metrics.width; 161 | canvas.remove(); 162 | return charWidth; 163 | } 164 | 165 | /** 166 | * Constructs a selector string for a given ID. 167 | * @param id - The ID to use. 168 | * @returns A selector string for the ID. 169 | */ 170 | export function idSelector(id: string): string 171 | { 172 | return `#${id}`; 173 | } 174 | 175 | /** 176 | * Constructs a selector string for a given class name. 177 | * @param className - The class name to use. 178 | * @returns A selector string for the class name. 179 | */ 180 | export function classSelector(className: string): string 181 | { 182 | return `.${className}`; 183 | } 184 | 185 | /** 186 | * Validates a given width value to ensure it's within a specified range. 187 | * 188 | * @param {number} width - The width value to be validated. 189 | * @returns {number} - Returns the validated width value. If the input width is greater than 100, it returns 100. If it's less than 0, it returns 0. If the input is not a number, it returns the defaultWidth. 190 | */ 191 | export function validateWidth(width: number): number 192 | { 193 | return Math.min(100, Math.max(0, width)); 194 | } 195 | -------------------------------------------------------------------------------- /src/utility/uuidGenerator.ts: -------------------------------------------------------------------------------- 1 | import LokiDatabase from "src/utility/lokiDatabase"; 2 | import { UUID_FORMAT } from "src/utility/constants"; 3 | 4 | /** 5 | * Class responsible for generating unique UUIDs. 6 | */ 7 | export default class UUIDGenerator 8 | { 9 | 10 | /** 11 | * Generates a UUID version 4. 12 | * @returns A UUID string. 13 | */ 14 | private static generateUUIDV4(): string 15 | { 16 | return UUID_FORMAT.replace(/[xy]/g, function (c) 17 | { 18 | let r = Math.random() * 16 | 0, 19 | v = c === "x" ? r : (r & 0x3 | 0x8); 20 | return v.toString(16); 21 | }); 22 | } 23 | 24 | /** 25 | * Generates a unique UUID ensuring it's not already stored in the provided database. 26 | * @param database - The LokiDatabase instance to check for existing UUIDs. 27 | * @returns A unique UUID string. 28 | */ 29 | public static getUniqueUUID(database: LokiDatabase): string 30 | { 31 | let generatedUUID = this.generateUUIDV4(); 32 | while (database.noteExists(generatedUUID)) 33 | { 34 | generatedUUID = this.generateUUIDV4(); 35 | } 36 | return generatedUUID; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* Modals */ 2 | #nwm-container { 3 | width: 18%; 4 | } 5 | 6 | #nwm-input-container { 7 | display: flex; 8 | justify-content: space-between; 9 | } 10 | 11 | #nwm-button-container { 12 | text-align: right; 13 | } 14 | 15 | /* UI Elements */ 16 | #custom-note-width-wrapper { 17 | display: inline-flex; 18 | align-items: center; 19 | margin: 0px; 20 | padding: 0px; 21 | height: 12px; 22 | } 23 | 24 | #custom-note-width-slider { 25 | margin: 0px; 26 | padding: 0px; 27 | margin-right: 5px; 28 | } 29 | 30 | #custom-note-width-slider-value { 31 | text-align: center; 32 | margin-top: 0px; 33 | padding: 0px; 34 | min-width: 3ch; 35 | } 36 | 37 | #dummy { 38 | display: none; 39 | } 40 | 41 | /* Donation button */ 42 | #custom-note-width-donation-button { 43 | margin-top: 5%; 44 | } 45 | 46 | #custom-note-width-donation-button p { 47 | text-align: center; 48 | font-size: 7px; 49 | font-style: italic; 50 | font-weight: bold; 51 | margin-top: -0.5%; 52 | } 53 | 54 | #custom-note-width-donation-button div { 55 | display: flex; 56 | justify-content: center; 57 | } 58 | 59 | /* Settings */ 60 | .priority-list-item { 61 | border: 1px solid rgb(54, 54, 54); 62 | border-radius: 8px; 63 | margin-bottom: 8px; 64 | margin-left: 20px; 65 | position: relative; 66 | padding-left: 10px; 67 | padding-top: 8px; 68 | padding-bottom: 8px; 69 | display: flex; 70 | align-items: center; 71 | justify-content: space-between; 72 | } 73 | 74 | .priority-number { 75 | position: absolute; 76 | left: -20px; 77 | font-weight: bold; 78 | } 79 | 80 | .progress-bar-outer { 81 | background-color: grey; 82 | width: 100%; 83 | height: 30px; 84 | border-radius: 8px; 85 | } 86 | 87 | .progress-bar-inner { 88 | background-color: rgb(125, 91, 237); 89 | width: 0%; 90 | height: 30px; 91 | border-radius: 8px; 92 | } 93 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.1": "0.15.0", 3 | "1.0.0": "0.15.0" 4 | } --------------------------------------------------------------------------------