From ba8c43d90f5b3433fe499089c0fcc24af57cf076 Mon Sep 17 00:00:00 2001 From: Nick Winters <65742767+DynamicPlayerSector@users.noreply.github.com> Date: Fri, 1 Sep 2023 03:08:17 +0300 Subject: [PATCH 1/2] Custom frontmatter handling solution Introducing custom frontmatter handling solution for smoother experience. It instantly updates the "modified" timestamp value with each keystroke, and doesn't force any YAML convention on the rest of your frontmatter. The only downside is that it might be having trouble with nested keys. --- main.ts | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 170 insertions(+), 10 deletions(-) diff --git a/main.ts b/main.ts index 344cf35..3b954ad 100644 --- a/main.ts +++ b/main.ts @@ -11,6 +11,8 @@ interface TimeThingsSettings { modifiedKeyFormat: string; enableClock: boolean; enableModifiedKeyUpdate: boolean; + useCustomFrontmatterHandlingSolution: boolean; + updateIntervalFrontmatterMinutes: number; } const DEFAULT_SETTINGS: TimeThingsSettings = { @@ -20,17 +22,22 @@ const DEFAULT_SETTINGS: TimeThingsSettings = { modifiedKeyName: 'updated_at', modifiedKeyFormat: 'YYYY-MM-DD[T]HH:mm:ss.SSSZ', enableClock: true, - enableModifiedKeyUpdate: true + enableModifiedKeyUpdate: true, + useCustomFrontmatterHandlingSolution: false, + updateIntervalFrontmatterMinutes: 1 } export default class TimeThings extends Plugin { settings: TimeThingsSettings; + isDB: boolean; statusBar: HTMLElement; // # Required + debugBar: HTMLElement; isProccessing = false; async onload() { await this.loadSettings(); + this.isDB = true; // for debugging purposes if (this.settings.enableClock) { @@ -45,18 +52,42 @@ export default class TimeThings extends Plugin { ); } + if (this.isDB) { + this.debugBar = this.addStatusBarItem(); + this.debugBar.setText("Time Things Debug Build") + } + // # On file modification this.registerEvent(this.app.vault.on('modify', (file) => { + if (this.settings.useCustomFrontmatterHandlingSolution === true) { + return; + } if (this.settings.enableModifiedKeyUpdate) { - this.updateModifiedKey(file); + this.objectUpdateModifiedKey(file); + } })) + this.registerDomEvent(document, 'keydown', (evt: KeyboardEvent) => { + if (this.settings.useCustomFrontmatterHandlingSolution === false) { + return; + } + const dateNow = moment(); + const dateFormatted = dateNow.format(this.settings.modifiedKeyFormat); + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (activeView === null) { + return; + } + const editor: Editor = activeView.editor; + + this.editorUpdateKey(editor, this.settings.modifiedKeyName, dateFormatted); + }); + // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new TimeThingsSettingsTab(this.app, this)); } - // # The actual function + updateStatusBar() { const dateNow = moment(); const dateUTC = moment.utc(); // Convert to UTC time @@ -67,39 +98,143 @@ export default class TimeThings extends Plugin { this.statusBar.setText(emoji + " " + dateFormatted); } + + isFrontmatterPresent(editor: Editor): boolean { + if (editor.getLine(0) !== "---") { + return false; + } + for (let i = 1; i <= editor.lastLine(); i++) { + if (editor.getLine(i) === "---") { + return true; + } + } + return false; + } + + frontmatterEndLine(editor: Editor): number | undefined { + if (this.isFrontmatterPresent(editor)) { + for (let i = 1; i <= editor.lastLine(); i++) { + if (editor.getLine(i) === "---") { + return i; + } + } + } + return undefined; // # End line not found + } + + editorUpdateKey(editor: Editor, fieldPath: string, fieldValue: string) { + const fieldLine = this.getFieldLine(editor, fieldPath); + if (fieldLine === undefined) { + return; + } + const value = editor.getLine(fieldLine).split(/:(.*)/s)[1].trim(); + if (moment(value, this.settings.modifiedKeyFormat, true).isValid() === false) { // Little safecheck in place to reduce chance of bugs + this.isDB && console.log("not valid date"); + this.isDB && console.log(fieldLine); + return; + } + const initialLine = editor.getLine(fieldLine).split(':', 1); + const newLine = initialLine[0] + ": " + fieldValue; + editor.setLine(fieldLine, newLine); + + } + + getFieldLine(editor: Editor, fieldPath: string): number | undefined { + const frontmatterEndLine = this.frontmatterEndLine(editor); + const keys = fieldPath.split('.'); + const depth = keys.length; + + if (frontmatterEndLine === undefined) { + return undefined; + } + + let targetDepth = 1; + let currentDepth = 1; + let startLine = 1; + let emergingPath = []; + + for (const key of keys) { + for (let i = startLine; i <= frontmatterEndLine; i++) { + + const currentLine = editor.getLine(i); + const currentField = currentLine.split(':'); + const currentFieldName = currentField[0].trim(); + + if (currentFieldName === key) { + emergingPath.push(currentFieldName); + this.isDB && console.log(emergingPath); + let targetPath = fieldPath.split('.'); + let targetPathShrink = targetPath.slice(0, emergingPath.length); + if (targetPathShrink.join('.') === emergingPath.join('.') === false) { + this.isDB && console.log("Path wrong: " + emergingPath + " | " + targetPathShrink); + emergingPath.pop(); + startLine = i + 1; + continue; + } + else { + if (emergingPath.join('.') === fieldPath) { + if (targetDepth > 1) { + if (this.isLineIndented(currentLine) === false) { // met first level variable, obviously return + this.isDB && console.log("Not indented: " + i + " | " + currentLine + " | " + startLine) + return undefined; + } + } + else { + if (this.isLineIndented(currentLine)) { + startLine = i + 1; + emergingPath = []; + continue; + } + } + return i; + } + startLine = i + 1; + targetDepth += 1; + continue; + } + } + } + } + + return undefined; + } + + isLineIndented(line: string): boolean { + return /^[\s\t]/.test(line); + } - async updateModifiedKey(file: TAbstractFile) { + async objectUpdateModifiedKey(file: TAbstractFile) { await this.app.fileManager.processFrontMatter(file as TFile, (frontmatter) => { const dateNow = moment(); const dateFormatted = dateNow.format(this.settings.modifiedKeyFormat); - const updateKeyValue = moment(this.getNestedValue(frontmatter, this.settings.modifiedKeyName), this.settings.modifiedKeyFormat); + const updateKeyValue = moment(this.objectGetValue(frontmatter, this.settings.modifiedKeyName), this.settings.modifiedKeyFormat); if (updateKeyValue.add(1, 'minutes') > dateNow) { return; } - this.setNestedValue(frontmatter, this.settings.modifiedKeyName, dateFormatted); + this.objectSetValue(frontmatter, this.settings.modifiedKeyName, dateFormatted); }) } - getNestedValue(obj: any, path: string) { - const keys = path.split('.'); + objectGetValue(obj: any, fieldPath: string) { + const keys = fieldPath.split('.'); let value = obj; for (const key of keys) { value = value[key]; if (value === undefined) { - return undefined; // If any key is not found, return undefined + return undefined; } } return value; } - setNestedValue(obj: any, path: string, value: string) { + objectSetValue(obj: any, path: string, value: string) { const keys = path.split('.'); let currentLevel = obj; @@ -167,6 +302,17 @@ class TimeThingsSettingsTab extends PluginSettingTab { return linkEl; } + new Setting(containerEl) + .setName('Use custom frontmatter handling solution') + .setDesc('Smoother experiene. Prone to bugs if you use a nested value.') + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.useCustomFrontmatterHandlingSolution) + .onChange(async (newValue) => { + this.plugin.settings.useCustomFrontmatterHandlingSolution = newValue; + await this.plugin.saveSettings(); + }),); + containerEl.createEl('h1', { text: 'Status bar' }); containerEl.createEl('p', { text: 'Displays clock in the status bar.' }); @@ -250,6 +396,20 @@ class TimeThingsSettingsTab extends PluginSettingTab { this.plugin.settings.modifiedKeyFormat = value; await this.plugin.saveSettings(); })); + + new Setting(containerEl) + .setName('Interval between updates') + .setDesc('Only for Obsidian frontmatter API.') + .addSlider((slider) => + slider + .setLimits(1, 15, 1) + .setValue(this.plugin.settings.updateIntervalFrontmatterMinutes) + .onChange(async (value) => { + this.plugin.settings.updateIntervalFrontmatterMinutes = value; + await this.plugin.saveSettings(); + }) + .setDynamicTooltip(), + ); } } \ No newline at end of file From 796ba221ddacc7f4c615f279a874f9166b7ac5da Mon Sep 17 00:00:00 2001 From: Nick Winters <65742767+DynamicPlayerSector@users.noreply.github.com> Date: Fri, 1 Sep 2023 03:09:45 +0300 Subject: [PATCH 2/2] Custom frontmatter handling solution Introducing custom frontmatter handling solution for smoother experience. It instantly updates the "modified" timestamp value with each keystroke, and doesn't force any YAML convention on the rest of your frontmatter. The only downside is that it might be having trouble with nested keys. --- main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.ts b/main.ts index 3b954ad..c83aabd 100644 --- a/main.ts +++ b/main.ts @@ -37,7 +37,7 @@ export default class TimeThings extends Plugin { async onload() { await this.loadSettings(); - this.isDB = true; // for debugging purposes + this.isDB = false; // for debugging purposes if (this.settings.enableClock) {