Skip to content

Commit

Permalink
Custom frontmatter handling solution
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
plasmabit committed Sep 1, 2023
1 parent a561307 commit ba8c43d
Showing 1 changed file with 170 additions and 10 deletions.
180 changes: 170 additions & 10 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface TimeThingsSettings {
modifiedKeyFormat: string;
enableClock: boolean;
enableModifiedKeyUpdate: boolean;
useCustomFrontmatterHandlingSolution: boolean;
updateIntervalFrontmatterMinutes: number;
}

const DEFAULT_SETTINGS: TimeThingsSettings = {
Expand All @@ -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)
{
Expand All @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -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.' });

Expand Down Expand Up @@ -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(),
);
}

}

0 comments on commit ba8c43d

Please sign in to comment.