From f203c6041858dfdb891213ec7c1912275975cd24 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Tue, 15 Sep 2020 20:29:23 +0100 Subject: [PATCH] Support Obsidian 0.8.14 (#42) --- CHANGELOG.md | 1 + README.md | 14 +- src/obsidian.d.ts | 8 -- src/settings.ts | 326 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 230 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e37a76e..0a8b605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `contains-task-list` to match latest Obsidian styling. - The entire task (`li` element) has the `task-overdue` class on it, in addition to the date element specifically. - The task (`li` element) has either `has-time` or `has-no-time` derived from the date field. (No date or time will also have `has-no-time`). +- Add support for Obsidian v0.8.14 ### 🐛 Bug Fixes diff --git a/README.md b/README.md index 466a5e4..2dd9b48 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An experimental [Obsidian](https://obsidian.md/) plugin using [Volcano](https://github.com/kognise/volcano) to materialize [Todoist](https://todoist.com/) task lists in Obsidian notes. -_Tested with Obsidian 0.8.9 and Volcano 1.2.1, your results may vary!_ +_Tested with Obsidian 0.8.14 and Volcano 1.2.1, your results may vary!_ ![Example gif](./.github/obsidian-todoist-sync.gif) @@ -34,18 +34,6 @@ _Tested with Obsidian 0.8.9 and Volcano 1.2.1, your results may vary!_ | `autorefresh` | | The number of seconds between auto-refreshing. If omitted, the query use the default global settings. | number | null | | `sorting` | | Describes how to order the tasks in the query. Can be any of 'priority' or 'date', or multiple. | string[] | [] | -## Settings - -This plugin adds a setting tab to the Obsidian settings menu. This controls global settings for this plugin. - -| Name | What does it control? | Default | -| --------------------- | ------------------------------------------------------------------------------------------- | ------- | -| Task fade animation | Whether tasks should fade in and out when added or removed. | true | -| Auto-refresh | Whether queries should auto-refresh at a set interval. | false | -| Auto-refresh interval | The interval (in seconds) that queries should auto-refresh by default. Integer numbers only | 60 | -| Render dates | Whether dates should be rendered with tasks. | true | -| Render date icon | Whether rendered dates should include an icon. | true | - ## CSS ### General diff --git a/src/obsidian.d.ts b/src/obsidian.d.ts index d10d963..5d374f7 100644 --- a/src/obsidian.d.ts +++ b/src/obsidian.d.ts @@ -33,17 +33,9 @@ export class SettingsTab implements ISettingsTab { } interface ISettingsTab { - addToggleSetting(title: string, description: string): ISettingValue; - addTextSetting(title: string, description: string): ISettingValue; display(); } -export interface ISettingValue { - getValue(): T; - setValue(value: T); - onChange(func: () => void); -} - export type Constructor = new (...args: any[]) => T; export type Settings = Constructor; diff --git a/src/settings.ts b/src/settings.ts index 9f39ce2..8350485 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -42,19 +42,22 @@ export interface ISettings { export function SettingsTab(Base: TBase) { return class extends Base { // This is actually on TBase, but the mixin doesn't allow access without going through the prototype. - public containerEl: SettingsContainer; + public containerEl: HTMLDivElement; private app: App; private instance: PluginInstance; private plugin: TodoistPlugin; + private settingMgr: SettingManager; constructor(...args: any[]) { super(...args); + [this.app, this.instance, this.plugin] = args; } display() { - this.containerEl.empty(); + this.settingMgr = new SettingManager(this.containerEl); + this.settingMgr.empty(); this.fadeAnimationSettings(); this.autoRefreshSettings(); @@ -65,126 +68,253 @@ export function SettingsTab(Base: TBase) { } fadeAnimationSettings() { - const fadeToggle = this.addToggleSetting( - "Task fade animation", - "Whether tasks should fade in and out when added or removed." - ); - fadeToggle.setValue(this.plugin.options.fadeToggle); - fadeToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.fadeToggle = fadeToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Task fade animation", + description: + "Whether tasks should fade in and out when added or removed.", + configure: (setting) => { + setting.setValue(this.plugin.options.fadeToggle); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.fadeToggle = value)); + }); + }, }); } autoRefreshSettings() { - const autoRefreshToggle = this.addToggleSetting( - "Auto-refresh", - "Whether queries should auto-refresh at a set interval." - ); - autoRefreshToggle.setValue(this.plugin.options.autoRefreshToggle); - autoRefreshToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.autoRefreshToggle = autoRefreshToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Auto-refresh", + description: "Whether queries should auto-refresh at a set interval", + configure: (setting) => { + setting.setValue(this.plugin.options.autoRefreshToggle); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.autoRefreshToggle = value)); + }); + }, }); - const autoRefreshInterval = this.addTextSetting( - "Auto-refresh interval", - "The interval (in seconds) that queries should auto-refresh by default. Integer numbers only" - ); - autoRefreshInterval.setValue( - `${this.plugin.options.autoRefreshInterval}` - ); - autoRefreshInterval.onChange(() => { - const newSetting = autoRefreshInterval.getValue().trim(); - - if (newSetting.length == 0) { - return; - } - - if (isPositiveInteger(newSetting)) { - this.plugin.writeOptions( - (old) => (old.autoRefreshInterval = toInt(newSetting)) - ); - } else { - autoRefreshInterval.setValue( - `${this.plugin.options.autoRefreshInterval}` - ); - } + this.settingMgr.addText({ + name: "Auto-refesh interval", + description: + "The interval (in seconds) that queries should auto-refresh by default. Integer numbers only", + configure: (setting) => { + setting.setValue(`${this.plugin.options.autoRefreshInterval}`); + setting.onChange((value) => { + const newSetting = value.trim(); + + if (newSetting.length == 0) { + return; + } + + if (isPositiveInteger(newSetting)) { + this.plugin.writeOptions( + (old) => (old.autoRefreshInterval = toInt(newSetting)) + ); + } else { + setting.setValue(`${this.plugin.options.autoRefreshInterval}`); + } + }); + }, }); } dateSettings() { - const renderDateToggle = this.addToggleSetting( - "Render dates", - "Whether dates should be rendered with tasks." - ); - renderDateToggle.setValue(this.plugin.options.renderDate); - renderDateToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderDate = renderDateToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render dates", + description: "Whether dates should be rendered with tasks.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderDate); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderDate = value)); + }); + }, }); - const renderDateIconToggle = this.addToggleSetting( - "Render date icon", - "Whether rendered dates should include an icon." - ); - renderDateIconToggle.setValue(this.plugin.options.renderDateIcon); - renderDateIconToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderDateIcon = renderDateIconToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render date icon", + description: "Whether rendered dates should include an icon.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderDateIcon); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderDateIcon = value)); + }); + }, }); } projectSettings() { - const renderProjectToggle = this.addToggleSetting( - "Render project & section", - "Whether projects & sections should be rendered with tasks." - ); - renderProjectToggle.setValue(this.plugin.options.renderProject); - renderProjectToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderProject = renderProjectToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render project & section", + description: + "Whether projects & sections should be rendered with tasks.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderProject); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderProject = value)); + }); + }, }); - const renderProjectIconToggle = this.addToggleSetting( - "Render project & section icon", - "Whether rendered projects & sections should include an icon." - ); - renderProjectIconToggle.setValue(this.plugin.options.renderProjectIcon); - renderProjectIconToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderProjectIcon = renderProjectIconToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render project & section icon", + description: + "Whether rendered projects & sections should include an icon.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderProjectIcon); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderProjectIcon = value)); + }); + }, }); } labelsSettings() { - const renderLabelsToggle = this.addToggleSetting( - "Render labels", - "Whether labels should be rendered with tasks." - ); - renderLabelsToggle.setValue(this.plugin.options.renderLabels); - renderLabelsToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderLabels = renderLabelsToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render labels", + description: "Whether labels should be rendered with tasks.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderLabels); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderLabelsIcon = value)); + }); + }, }); - - const renderLabelsIconToggle = this.addToggleSetting( - "Render labels icon", - "Whether rendered labels should include an icon." - ); - renderLabelsIconToggle.setValue(this.plugin.options.renderLabelsIcon); - renderLabelsIconToggle.onChange(() => { - this.plugin.writeOptions( - (old) => (old.renderLabelsIcon = renderLabelsIconToggle.getValue()) - ); + this.settingMgr.addToggle({ + name: "Render labels icon", + description: "Whether rendered labels should include an icon.", + configure: (setting) => { + setting.setValue(this.plugin.options.renderLabelsIcon); + setting.onChange((value) => { + this.plugin.writeOptions((old) => (old.renderLabelsIcon = value)); + }); + }, }); } }; } + +class SettingManager { + private container: HTMLDivElement; + + constructor(containerEl: HTMLDivElement) { + this.container = containerEl; + } + + public empty() { + while (this.container.firstChild) { + this.container.removeChild(this.container.lastChild); + } + } + + public addToggle(config: ISettingConfiguration) { + const control = this.addSetting(config); + const toggle = new ToggleSetting(control); + config.configure(toggle); + } + + public addText(config: ISettingConfiguration) { + const control = this.addSetting(config); + const text = new TextSetting(control); + config.configure(text); + } + + private addSetting(config: ISettingConfiguration): HTMLElement { + const item = document.createElement("div") as HTMLDivElement; + item.classList.add("setting-item"); + this.container.append(item); + + const info = document.createElement("div") as HTMLDivElement; + info.classList.add("setting-item-info"); + item.appendChild(info); + + const name = document.createElement("div") as HTMLDivElement; + name.classList.add("setting-item-name"); + name.innerText = config.name; + info.appendChild(name); + + const desc = document.createElement("div") as HTMLDivElement; + desc.classList.add("setting-item-description"); + desc.innerText = config.description; + info.appendChild(desc); + + const control = document.createElement("div") as HTMLDivElement; + control.classList.add("setting-item-control"); + item.appendChild(control); + + return control; + } +} + +interface ISettingConfiguration { + name: string; + description: string; + configure: (settings: TSettings) => void; +} + +class ToggleSetting { + private value: boolean; + private toggleEl: HTMLDivElement; + private changeCallback?: (boolean) => void; + + constructor(controlEl: HTMLElement) { + this.toggleEl = document.createElement("div") as HTMLDivElement; + this.toggleEl.classList.add("checkbox-container"); + this.toggleEl.addEventListener("click", this.onClick.bind(this)); + controlEl.appendChild(this.toggleEl); + } + + public getValue(): boolean { + return this.value; + } + + public onChange(callback: (boolean) => void) { + this.changeCallback = callback; + } + + public setValue(value: boolean) { + this.value = value; + if (value) { + this.toggleEl.classList.add("is-enabled"); + } else { + this.toggleEl.classList.remove("is-enabled"); + } + + if (this.changeCallback) { + this.changeCallback(value); + } + } + + private onClick() { + this.setValue(!this.getValue()); + } +} + +class TextSetting { + private inputEl: HTMLInputElement; + private changeCallback?: (string) => void; + + constructor(controlEl: HTMLElement) { + this.inputEl = document.createElement("input") as HTMLInputElement; + this.inputEl.type = "text"; + this.inputEl.addEventListener("input", this.onChanged.bind(this)); + controlEl.appendChild(this.inputEl); + } + + public getValue(): string { + return this.inputEl.value; + } + + public onChange(callback: (string) => void) { + this.changeCallback = callback; + } + + public setValue(value: string) { + this.inputEl.value = value; + } + + private onChanged() { + if (this.changeCallback) { + this.changeCallback(this.inputEl.value); + } + } +}