diff --git a/main.ts b/main.ts index 314dcbd..fbe02aa 100644 --- a/main.ts +++ b/main.ts @@ -1,11 +1,10 @@ -import {App, Editor, MarkdownView, Modal, Notice, Plugin} from 'obsidian'; +import {Editor, MarkdownView, Plugin} from 'obsidian'; import {PrioPluginSettings} from "./utils/Settings"; import {SettingTab} from "./utils/SettingTab"; import {decreasePrio, increasePrio, removePrio, setPrio} from "./utils/Priority"; const DEFAULT_SETTINGS: PrioPluginSettings = { - selectedPreset: 'default', levels: 6, levelAliases: [ 'Major', @@ -38,19 +37,6 @@ export default class PrioPlugin extends Plugin { async onload() { await this.loadSettings(); - // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { - // Called when the user clicks the icon. - new Notice('This is a notice!'); - }); - // Perform additional things with the ribbon - ribbonIconEl.addClass('my-plugin-ribbon-class'); - - // This adds a status bar item to the bottom of the app. Does not work on mobile apps. - const statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); - - this.addCommand({ id: 'set-prio', name: 'Set priority', @@ -124,20 +110,4 @@ export default class PrioPlugin extends Plugin { } } -class SampleModal extends Modal { - constructor(app: App) { - super(app); - } - - onOpen() { - const {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - const {contentEl} = this; - contentEl.empty(); - } -} - diff --git a/manifest.json b/manifest.json index ae9460f..8c24b8e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,11 @@ { - "id": "obsidian-prio-plugin", - "name": "Obsidian Prio Plugin", + "id": "prioritize", + "name": "Prioritize", "version": "1.0.0", "minAppVersion": "0.15.0", - "description": "A plugin to prioritize your tasks and notes in Obsidian", + "description": "Prioritize your tasks and notes in Obsidian.", "author": "EloiMusk", "authorUrl": "https://github.com/EloiMusk/", - "fundingUrl": "", + "fundingUrl": "https://www.buymeacoffee.com/eloimusk", "isDesktopOnly": true } diff --git a/styles.css b/styles.css index 740ac44..fe9b427 100644 --- a/styles.css +++ b/styles.css @@ -1,7 +1,7 @@ /*Preset list setting*/ .preset-list { display: flex; - flex-wrap: nowrap; + flex-flow: column nowrap; width: 100%; border: 1px solid var(--color-base-10); box-shadow: inset var(--shadow-stationary); @@ -87,3 +87,51 @@ } /*---------------------*/ + +/*Preset Modal*/ +.preset-modal { + display: flex; + flex-flow: column nowrap; + width: 100%; + padding: 10px; +} + +.preset-modal-title { + margin: 5px; +} + +.preset-input { + margin: 5px; +} + +.preset-save-btn-group { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + width: 100%; +} + +/*---------------------*/ + +/*Save confirm modal*/ +.save-confirm-modal{ + display: flex; + flex-flow: column nowrap; + width: 100%; + padding: 10px; +} + +.save-confirm-modal-title { +} + +.save-confirm-modal-text { + margin: 5px; +} + +.save-btn-group { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + width: 100%; +} +/*---------------------*/ diff --git a/utils/SettingTab.ts b/utils/SettingTab.ts index 0b4edc8..2251ffe 100644 --- a/utils/SettingTab.ts +++ b/utils/SettingTab.ts @@ -6,16 +6,19 @@ import {PrioPluginSettings} from "./Settings"; export class SettingTab extends PluginSettingTab { plugin: PrioPlugin; saveConfirm: SaveConfirm; + addPresetModal: AddPresetModal; modified = false; constructor(app: App, plugin: PrioPlugin) { super(app, plugin); this.plugin = plugin; this.saveConfirm = new SaveConfirm(this); + this.addPresetModal = new AddPresetModal(this); } - hide(): any { + hide() { if (this.modified) { + this.saveConfirm.setSettingsTab(this); this.saveConfirm.open(); return; } @@ -56,11 +59,29 @@ export class SettingTab extends PluginSettingTab { }); } + const presetListContainer = createEl('div', { + cls: 'preset-list-container', + parent: containerEl + }) + const presetList = createEl('ol', { cls: 'preset-list', - parent: containerEl + parent: presetListContainer }); + const addPresetButton = createEl('button', { + text: 'Add Preset', + cls: ['mod-cta', 'mod-primary', 'btn'], + parent: presetListContainer + }); + + addPresetButton.addEventListener('click', () => { + this.addPresetModal.setPresetsList(presetList); + this.addPresetModal.open(); + }); + + this.generatePresetList(presets, presetList); + const levelSliderSetting = new Setting(containerEl); let levelSlider: SliderComponent; const levelText = createEl('input', { @@ -89,7 +110,6 @@ export class SettingTab extends PluginSettingTab { this.generateLevelAliasList(this.plugin.settings, levelAliasesList, [saveButton]); }); - this.generatePresetList(presets, presetList); levelSliderSetting .setName('Levels') @@ -120,7 +140,7 @@ export class SettingTab extends PluginSettingTab { containerEl.append(saveButton); saveButton.addEventListener('click', () => { - if (!this.isValid(levelAliasesList, [saveButton])) { + if (!this.isValid(levelAliasesList)) { return; } this.setAliases(this.plugin.settings, levelAliasesList); @@ -159,6 +179,9 @@ export class SettingTab extends PluginSettingTab { }) input.addEventListener('blur', (event) => { const valid = this.validateLevelAlias((event.target as HTMLInputElement).value, parseInt((event.target as HTMLInputElement).parentElement?.textContent ?? '') - 1, settings.levelAliases.slice(0, settings.levels)); + if (valid) { + this.plugin.settings.levelAliases[parseInt((event.target as HTMLInputElement).parentElement?.textContent ?? '') - 1] = (event.target as HTMLInputElement).value; + } for (const button of buttons) { button.disabled = !valid; } @@ -169,39 +192,147 @@ export class SettingTab extends PluginSettingTab { return els; } - generatePresetList = (presets: Preset[], presetList: HTMLOListElement) => (presets || []).map(preset => { - const el = createEl('li', { - text: preset.name, - cls: 'preset-list-item', - parent: presetList - }); - const btnGroup = el.createEl('div', { - cls: 'btn-group' - }); + generatePresetId = () => { + return Date.now().toString(36) + Math.random().toString(36); + } - btnGroup.createEl('button', { - text: 'Apply', - cls: ['preset-list-item-apply', 'btn', 'btn-primary'], - attr: { - 'onclick': 'alert("Preset Selected")' - } - }) - btnGroup.createEl('button', { - text: 'Overwrite', - cls: ['preset-list-item-save', 'btn', 'btn-primary'], - attr: { - 'onclick': 'alert("Save Preset")' - } - }) - return el; - }); + addPreset = (name: string, settings: PrioPluginSettings, presetList: HTMLOListElement) => { + if (!name || !settings || !this.presetIsValid(name, settings.presets ?? [], this.plugin.settings.presets?.length ?? 0 - 1)) { + return; + } - isValid = (levelAliasesList: HTMLOListElement, buttons: HTMLButtonElement[]) => { + if (this.plugin.settings.presets) { + this.plugin.settings.presets.push({ + id: `${this.generatePresetId()}`, + name, + settings: { + levels: this.plugin.settings.levels, + levelAliases: this.plugin.settings.levelAliases.slice(0, this.plugin.settings.levels), + } + }); + } else { + this.plugin.settings.presets = [{ + id: `${this.generatePresetId()}`, + name, + settings: { + levels: this.plugin.settings.levels, + levelAliases: this.plugin.settings.levelAliases.slice(0, this.plugin.settings.levels), + } + }]; + } + this.generatePresetList(this.plugin.settings.presets, presetList); + this.plugin.saveSettings().then(() => { + this.plugin.loadSettings().then(() => { + new Notice('Preset applied successfully!'); + }); + }); + this.display(); + } + + applyPreset = (preset: Preset, presetList: HTMLOListElement) => { + if (!preset || !presetList) { + return; + } + if (this.plugin.settings.presets) { + this.plugin.settings.levels = preset.settings.levels.valueOf(); + this.plugin.settings.levelAliases = [...preset.settings.levelAliases]; + } + this.generatePresetList(this.plugin.settings.presets ?? [], presetList); + this.display(); + new Notice('Preset applied successfully!'); + } + + deletePreset = (preset: Preset, presetList: HTMLOListElement) => { + if (this.plugin.settings.presets) { + this.plugin.settings.presets.remove(preset); + } + this.generatePresetList(this.plugin.settings.presets ?? [], presetList); + this.display(); + new Notice('Preset deleted successfully!'); + } + + overwritePreset = (preset: Preset, presetList: HTMLOListElement) => { + if (!preset || !presetList) { + return; + } + if (this.plugin.settings.presets) { + this.plugin.settings.presets[this.plugin.settings.presets.indexOf(preset)].settings.levels = this.plugin.settings.levels.valueOf(); + this.plugin.settings.presets[this.plugin.settings.presets.indexOf(preset)].settings.levelAliases = [...this.plugin.settings.levelAliases]; + } + this.generatePresetList(this.plugin.settings.presets ?? [], presetList); + this.display(); + new Notice('Preset overwritten successfully!'); + } + + generatePresetList = (presets: Preset[], presetList: HTMLOListElement) => { + presetList.empty(); + (presets || []).map(preset => { + const el = createEl('li', { + cls: 'preset-list-item', + parent: presetList + }); + + const presetListInput = el.createEl('input', { + cls: 'preset-list-item-input', + value: preset.name + }); + + presetListInput.addEventListener('change', (event) => { + if ( + presets && + presets.length >= presetList.children.length - 1 && + (event.target as HTMLInputElement).parentElement && + this.presetIsValid( + (event.target as HTMLInputElement).value, + presets, + ((event.target as HTMLInputElement).parentElement as HTMLElement).indexOf((event.target as HTMLInputElement)))) { + presets[presetList.children.length - 1].name = (event.target as HTMLInputElement).value; + } + }); + + const btnGroup = el.createEl('div', { + cls: 'btn-group' + }); + + const applyButton = btnGroup.createEl('button', { + text: 'Apply', + cls: ['preset-list-item-apply', 'btn', 'btn-primary'], + }) + const overwriteButton = btnGroup.createEl('button', { + text: 'Overwrite', + cls: ['preset-list-item-overwrite', 'btn', 'btn-primary'], + }) + const deleteButton = btnGroup.createEl('button', { + text: 'Delete', + cls: ['preset-list-item-delete', 'btn', 'mod-danger'], + }) + + applyButton.addEventListener('click', () => { + this.applyPreset(preset, presetList); + }); + overwriteButton.addEventListener('click', () => { + this.overwritePreset(preset, presetList); + }); + deleteButton.addEventListener('click', () => { + this.deletePreset(preset, presetList); + }); + + return el; + }); + } + + + isValid = (levelAliasesList: HTMLOListElement) => { const inputs = levelAliasesList.querySelectorAll('input'); const aliases: string[] = []; inputs.forEach(input => { aliases.push((input as HTMLInputElement).value); }); + inputs.forEach(input => { + if (!this.validateLevelAlias((input as HTMLInputElement).value, aliases.indexOf((input as HTMLInputElement).value), aliases)) { + return false; + } + }); if (aliases.length !== new Set(aliases).size) { new Notice('Level aliases must be unique.'); return false; @@ -228,6 +359,23 @@ export class SettingTab extends PluginSettingTab { } return true; } + + presetIsValid = (preset: string, presets: Preset[], index: number) => { + if (preset.length < 1) { + new Notice('Preset name must be at least 1 character long!'); + return false; + } + if (preset.match(/[^a-zA-Z0-9]/)) { + new Notice('Preset name must only contain alphanumeric characters!'); + return false; + } + const searchPreset = presets.find(p => p.name === preset); + if (searchPreset && presets.indexOf(searchPreset) !== index && presets.some(p => p.name === preset)) { + new Notice('Preset name already exists!'); + return false; + } + return true; + } } class SaveConfirm extends Modal { @@ -238,13 +386,27 @@ class SaveConfirm extends Modal { this.settingsTab = settingsTab; } + setSettingsTab(settingsTab: SettingTab) { + this.settingsTab = settingsTab; + } + open() { super.open(); } onOpen() { const {contentEl} = this; - contentEl.setText('You have unsaved changes. Would you like to save them?'); + contentEl.classList.add('save-confirm-modal'); + + contentEl.createEl('h2', { + text: 'Unsaved Changes', + cls: 'save-confirm-title' + }); + + contentEl.createEl('p', { + text: 'You have unsaved changes. Would you like to save them?', + cls: 'save-confirm-text' + }) const btnGroup = contentEl.createEl('div', { cls: 'save-btn-group' @@ -285,3 +447,74 @@ class SaveConfirm extends Modal { this.settingsTab.modified = false; } } + +class AddPresetModal extends Modal { + private settingsTab: SettingTab; + private presetsList: HTMLOListElement; + + constructor(settingsTab: SettingTab) { + super(settingsTab.app); + this.settingsTab = settingsTab; + } + + setPresetsList(presetsList: HTMLOListElement) { + this.presetsList = presetsList; + } + + open() { + super.open(); + } + + onOpen() { + const {contentEl} = this; + contentEl.classList.add('preset-modal'); + contentEl.createEl('h2', { + text: 'Add Preset', + cls: 'preset-modal-title' + }) + let presetName = ''; + + const presetNameInput = contentEl.createEl('input', { + cls: 'preset-input', + placeholder: 'Preset Name' + }); + presetNameInput.addEventListener('input', () => { + if (presetNameInput.value.length > 0) { + presetNameInput.classList.remove('invalid'); + presetName = presetNameInput.value; + } else { + presetNameInput.classList.add('invalid'); + } + }); + + const btnGroup = contentEl.createEl('div', { + cls: 'preset-save-btn-group' + }); + + const addButton = createEl('button', { + text: 'Add', + cls: ['btn', 'mod-cta'], + }); + const cancelButton = createEl('button', { + text: 'Cancel', + cls: ['btn'], + }); + + addButton.addEventListener('click', () => { + this.settingsTab.addPreset(presetName, this.settingsTab.plugin.settings, this.presetsList); + this.close(); + }); + + cancelButton.addEventListener('click', () => { + this.close(); + }); + + btnGroup.append(addButton); + btnGroup.append(cancelButton); + } + + onClose() { + const {contentEl} = this; + contentEl.empty(); + } +} diff --git a/utils/Settings.ts b/utils/Settings.ts index ca350a0..3961abf 100644 --- a/utils/Settings.ts +++ b/utils/Settings.ts @@ -1,7 +1,6 @@ import {Preset} from "./Presets"; export interface PrioPluginSettings { - selectedPreset?: string; levels: number; levelAliases: string[]; presets?: Preset[];