timestamp button

This commit is contained in:
stfrigerio 2025-04-10 17:37:14 +02:00
parent cbf9d4f81d
commit 6d6759bda1
4 changed files with 170 additions and 0 deletions

View file

@ -22,6 +22,7 @@ import { registerTextInput } from "src/webcomponents/TextInput/registerTextInput
import { SQLiteDBSettings, DEFAULT_SETTINGS } from "./src/types"; import { SQLiteDBSettings, DEFAULT_SETTINGS } from "./src/types";
import { pluginState } from "src/pluginState"; import { pluginState } from "src/pluginState";
import { registerTimestampUpdaterButton } from "src/webcomponents/TimestampUpdaterButton/registerTimestampUpdaterButton";
export default class SQLiteDBPlugin extends Plugin { export default class SQLiteDBPlugin extends Plugin {
settings: SQLiteDBSettings; settings: SQLiteDBSettings;
@ -45,6 +46,8 @@ export default class SQLiteDBPlugin extends Plugin {
registerTextInput(el, this.app, this.dbService); registerTextInput(el, this.app, this.dbService);
}); });
registerTimestampUpdaterButton(this);
//? Codeblocks //? Codeblocks
this.registerMarkdownCodeBlockProcessor( this.registerMarkdownCodeBlockProcessor(
"sql", "sql",

View file

@ -0,0 +1,35 @@
/**
* Creates the HTMLButtonElement for the timestamp updater.
*
* @param onClick The async function to execute when the button is clicked.
* @returns The created HTMLButtonElement.
*/
export function createTimestampButtonElement(onClick: () => Promise<void>): HTMLButtonElement {
const button = document.createElement('button');
button.setText('Update Timestamp');
button.addClass('timestamp-updater-button');
button.style.backgroundColor = 'var(--interactive-accent)';
button.style.color = 'var(--text-on-accent)';
button.style.border = 'none';
button.style.padding = '6px 12px';
button.style.borderRadius = '4px';
button.style.cursor = 'pointer';
button.style.fontSize = '0.9em';
button.addEventListener('click', async (event) => {
event.stopPropagation();
button.disabled = true;
button.style.opacity = '0.7';
button.style.cursor = 'wait';
try {
await onClick();
} finally {
button.disabled = false;
button.style.opacity = '1';
button.style.cursor = 'pointer';
}
});
return button;
}

View file

@ -0,0 +1,44 @@
import { App, MarkdownPostProcessorContext, Notice, Plugin, TFile } from 'obsidian';
import { updateTimestampInFile } from './timestampFileUpdater';
import { createTimestampButtonElement } from './createTimestampButtonElement';
/**
* Registers the Markdown postprocessor to find <timestamp-updater-button>
* tags and replace them with functional buttons.
*
* @param plugin The Obsidian Plugin instance.
*/
export function registerTimestampUpdaterButton(plugin: Plugin): void {
plugin.registerMarkdownPostProcessor((element, context: MarkdownPostProcessorContext) => {
const { app } = plugin;
console.log(`Timestamp PostProcessor running for section in: ${context.sourcePath}`);
const placeholderElements = element.querySelectorAll('.timestamp-updater-placeholder');
if (placeholderElements.length === 0) {
return;
}
placeholderElements.forEach((placeholderEl) => {
const handleClick = async () => {
const activeFile = app.workspace.getActiveFile();
if (!activeFile || !(activeFile instanceof TFile)) {
new Notice('No active file selected.', 3000);
return;
}
const result = await updateTimestampInFile(app, activeFile);
new Notice(result.message, result.success ? 2000 : 5000);
};
const buttonElement = createTimestampButtonElement(handleClick);
try {
placeholderEl.replaceWith(buttonElement);
} catch (e) {
console.error("Error during replaceWith:", e);
}
});
});
}

View file

@ -0,0 +1,88 @@
import { App, Notice, TFile } from 'obsidian';
// Regex patterns are specific to this module's responsibility
const frontmatterRegex = /^---\s*([\s\S]*?)\s*---/;
const updatedAtRegexLine = /^(updatedAt:\s*)(.*)$/m; // Find existing line
const closingDashRegex = /^\s*---\s*$/; // To find the closing dashes
interface UpdateResult {
success: boolean;
message: string;
}
/**
* Reads the file, updates the 'updatedAt' timestamp in the frontmatter manually,
* and saves the file back.
*
* @param app Obsidian App instance
* @param file The TFile to modify
* @returns Promise resolving to an UpdateResult object
*/
export async function updateTimestampInFile(app: App, file: TFile): Promise<UpdateResult> {
try {
const content = await app.vault.read(file);
const match = content.match(frontmatterRegex);
if (!match || !match[0]) {
return { success: false, message: 'No frontmatter found in this file.' };
}
const fullFrontmatterBlock = match[0];
const frontmatterContent = match[1];
const body = content.substring(fullFrontmatterBlock.length);
const newTimestamp = new Date().toISOString();
let existingTimestamp: string | null = null;
let updatedFrontmatterBlock: string;
const updatedAtMatch = frontmatterContent.match(updatedAtRegexLine);
if (updatedAtMatch && updatedAtMatch[2]) {
existingTimestamp = updatedAtMatch[2].trim();
}
if (existingTimestamp === newTimestamp) {
return { success: true, message: `Timestamp already up-to-date (${newTimestamp.split('T')[1].substring(0, 8)}).` };
}
// Manual Update Logic
if (updatedAtMatch) {
// Found existing line: replace the value part
updatedFrontmatterBlock = fullFrontmatterBlock.replace(
updatedAtRegexLine,
`$1${newTimestamp}`
);
} else {
// Didn't find the line: Append it before the closing '---'
const lines = fullFrontmatterBlock.trimEnd().split('\n');
// Find the index of the closing '---' line
const closingDashIndex = lines.findIndex(line => closingDashRegex.test(line));
if (closingDashIndex > 0) { // Ensure closing dashes are found and not the first line
lines.splice(closingDashIndex, 0, `updatedAt: ${newTimestamp}`); // Insert before closing dashes
updatedFrontmatterBlock = lines.join('\n');
// Add back potential trailing newline if original block had one
if (fullFrontmatterBlock.endsWith('\n') && !updatedFrontmatterBlock.endsWith('\n')) {
updatedFrontmatterBlock += '\n';
}
} else {
console.warn(`FileUpdater: Could not find closing '---' to append timestamp in ${file.basename}`);
return { success: false, message: 'Could not reliably update frontmatter (structure error?).' };
}
}
// Reconstruct the full content
const newContent = updatedFrontmatterBlock + body;
// Check if content actually changed before writing (prevents unnecessary file modifications)
if (newContent === content) {
return { success: true, message: `Timestamp already effectively up-to-date.` };
}
await app.vault.modify(file, newContent);
return { success: true, message: `Updated timestamp for ${file.basename}` };
} catch (error: any) {
console.error(`FileUpdater: Error processing file ${file.basename}`, error);
return { success: false, message: `Error updating timestamp: ${error.message}` };
}
}