### Configure Modified Date History Tracking Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt These YAML examples illustrate different ways to configure the history tracking for the 'modified' date in frontmatter. It shows single-value mode, history mode with appending updates, and global history mode, along with the option to limit entries by frequency. ```yaml # Single value mode (default): --- modified: 2025-11-05T14:32:10Z --- # History mode with append_modified_update: true --- modified: - 2025-11-05T14:32:10Z - 2025-11-04T09:15:30Z - 2025-11-03T16:45:22Z append_modified_update: true --- # Global history mode (storeHistoryLog: true in settings): --- modified: - 2025-11-05T14:32:10Z - 2025-11-05T09:00:00Z - 2025-11-04T15:30:00Z --- # With maximum frequency = "day", only one entry per day is kept # Editing multiple times on 2025-11-05 updates the same entry ``` -------------------------------- ### Core Plugin Class Initialization (TypeScript) Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt The main plugin class 'FrontmatterModified' extends Obsidian's 'Plugin'. It handles loading settings, registering editor extensions for detecting changes (either typing or editor-change events), and persisting settings. The 'onload' method sets up the plugin's core functionality upon Obsidian startup. ```typescript import { Plugin, TFile, moment } from 'obsidian' import { DEFAULT_SETTINGS, FrontmatterModifiedSettings, FrontmatterModifiedSettingTab } from './settings' import { userChangeListenerExtension } from './editor' // Initialize plugin with default settings export default class FrontmatterModified extends Plugin { settings: FrontmatterModifiedSettings timer: { [key: string]: number } = {} async onload() { await this.loadSettings() // Register editor extension for typing events this.registerEditorExtension(userChangeListenerExtension(this)) // Default mode: watch for editor changes if (!this.settings.useKeyupEvents) { this.registerEvent(this.app.workspace.on('editor-change', (_editor, info) => { if (info.file instanceof TFile) { this.updateFrontmatter(info.file) } })) } this.addSettingTab(new FrontmatterModifiedSettingTab(this.app, this)) } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) } async saveSettings() { await this.saveData(this.settings) } } // Example usage: Plugin loads automatically when Obsidian starts // User edits a note → editor-change event fires → updateFrontmatter called ``` -------------------------------- ### Define Plugin Settings Interface and Defaults (TypeScript) Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt Defines the structure for plugin settings, including properties for frontmatter key names, date formatting, history logging, and exclusions. It also provides default values for these settings. ```typescript export interface FrontmatterModifiedSettings { frontmatterProperty: string; // Property name to update (default: "modified") createdDateProperty: string; // Optional created date property momentFormat: string; // Date format (MomentJS) storeHistoryLog: boolean; // Store array of all edit times historyNewestFirst: boolean; // Order of history array historyMaxItems: number; // Max history entries (0 = unlimited) excludedFolders: string[]; // Folders to exclude useKeyupEvents: boolean; // Use typing events instead of editor-change onlyUpdateExisting: boolean; // Only update if property exists timeout: number; // Debounce timeout in seconds excludeField: string; // Field name for per-file exclusion appendField: string; // Field name to enable history per-file appendMaximumFrequency: unitOfTime.StartOf; // Max frequency for history entries } export const DEFAULT_SETTINGS: FrontmatterModifiedSettings = { frontmatterProperty: 'modified', createdDateProperty: '', momentFormat: '', // Empty = ATOM format storeHistoryLog: false, historyNewestFirst: false, historyMaxItems: 0, excludedFolders: [], useKeyupEvents: false, onlyUpdateExisting: false, timeout: 10, excludeField: 'exclude_modified_update', appendField: 'append_modified_update', appendMaximumFrequency: 'day' } // Example configuration in data.json: // { // "frontmatterProperty": "updated", // "momentFormat": "YYYY-MM-DD HH:mm", // "excludedFolders": ["Templates", "Scripts"], // "timeout": 15, // "storeHistoryLog": true, // "appendMaximumFrequency": "hour", // "historyMaxItems": 50 // } ``` -------------------------------- ### Create Obsidian Settings Tab UI (TypeScript) Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt Implements the settings tab for the Obsidian plugin, allowing users to configure various options through interactive controls like text fields, toggles, dropdowns, and text areas. It dynamically saves changes to plugin settings. ```typescript export class FrontmatterModifiedSettingTab extends PluginSettingTab { plugin: FrontmatterModified display(): void { const { containerEl } = this containerEl.empty() // Modified date property name new Setting(containerEl) .setName('Modified date property') .setDesc('The name of the YAML/frontmatter property to update') .addText(text => text .setPlaceholder('modified') .setValue(this.plugin.settings.frontmatterProperty) .onChange(async value => { this.plugin.settings.frontmatterProperty = value await this.plugin.saveSettings() })) // Store history toggle new Setting(containerEl) .setName('Store history of all updates') .setDesc('Turn property into a list of all edit dates/times') .addToggle(toggle => { toggle .setValue(this.plugin.settings.storeHistoryLog) .onChange(async value => { this.plugin.settings.storeHistoryLog = value await this.plugin.saveSettings() this.display() // Refresh to show/hide frequency options }) }) // Conditional frequency dropdown (only if history enabled) if (this.plugin.settings.storeHistoryLog) { new Setting(containerEl) .setName('Frequency of updates') .setDesc('Store maximum 1 entry per minute/hour/day/etc') .addDropdown(dropdown => { ['minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'] .forEach(unit => dropdown.addOption(unit, unit)) dropdown .setValue(this.plugin.settings.appendMaximumFrequency || '') .onChange(async (value) => { this.plugin.settings.appendMaximumFrequency = value as unitOfTime.StartOf await this.plugin.saveSettings() }) }) } // Exclude folders new Setting(containerEl) .setName('Exclude folders') .setDesc('Add folders to exclude, one per line. Subfolders also excluded.') .addTextArea(text => text .setValue(this.plugin.settings.excludedFolders.join('\n')) .onChange(async value => { this.plugin.settings.excludedFolders = value.split('\n') .map(x => x.trim()) .filter(x => !!x) await this.plugin.saveSettings() })) } } // Example: User opens Settings → Configures: // - Property: "last_edited" // - Date format: "YYYY-MM-DD" // - Exclude folders: "Templates\nArchive" // - Timeout: 15 seconds ``` -------------------------------- ### Date Formatting Utility (TypeScript) Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt This TypeScript function formats a Moment.js date object into a string or number based on the configured format string in the settings. It intelligently parses the output, returning an integer for numeric-only formats (like Unix timestamps) and a string for other formatted dates. This ensures flexibility in how date information is stored in frontmatter. ```typescript formatFrontmatterDate(date: moment.Moment): string | number { const output = date.format(this.settings.momentFormat) if (output.match(/^\d+$/)) { return parseInt(output, 10) // Return integer for numeric formats } else { return output // Return string for formatted dates } } ``` -------------------------------- ### Detect User Typing Events in CodeMirror 6 Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt This TypeScript code implements a CodeMirror 6 plugin to monitor editor events. It specifically detects user typing actions to trigger frontmatter updates, differentiating them from external file modifications. It requires Obsidian API imports and the FrontmatterModified plugin instance. ```typescript import { EditorView, PluginValue, ViewPlugin, ViewUpdate } from '@codemirror/view' import { editorInfoField, TFile } from 'obsidian' // Register extension wrapper export const userChangeListenerExtension = (plugin: FrontmatterModified) => ViewPlugin.define(view => new UserChangeListener(plugin, view)) class UserChangeListener implements PluginValue { plugin: FrontmatterModified file: TFile | null constructor(plugin: FrontmatterModified, view: EditorView) { this.plugin = plugin this.file = view.state.field(editorInfoField).file } update(update: ViewUpdate) { // Only process if useKeyupEvents is enabled if (!this.file || !this.plugin.settings.useKeyupEvents) { return } if (isUserChange(update)) { this.plugin.updateFrontmatter(this.file).then() } } } // Check if update resulted from user input events function isUserChange(update: ViewUpdate) { // Ignore if no change or opening another note if (!update.docChanged || update.transactions.some(tr => tr.isUserEvent('set'))) { return false } // Only accept input, delete, or move events return update.transactions.some(tr => { return tr.isUserEvent('input') || tr.isUserEvent('delete') || tr.isUserEvent('move') }) } ``` -------------------------------- ### Update Frontmatter Logic with Debounce and Exclusions (TypeScript) Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt This TypeScript method manages the updating of frontmatter properties for a given file. It incorporates debouncing to prevent rapid updates, checks for exclusion conditions (e.g., specific fields, folders), and supports both single-value and array-based history tracking for date properties. It also includes logic to prevent sync conflicts by enforcing a minimum update interval and optionally adds a created date property if it doesn't already exist. ```typescript async updateFrontmatter(file: TFile) { // Clear existing timer for this file to debounce updates clearTimeout(this.timer[file.path]) this.timer[file.path] = window.setTimeout(() => { const cache = this.app.metadataCache.getFileCache(file) // Check exclusion conditions if (this.settings.onlyUpdateExisting && !cache?.frontmatter?.hasOwnProperty(this.settings.frontmatterProperty)) { return // Property doesn't exist and onlyUpdateExisting is true } if (cache?.frontmatter?.[this.settings.excludeField]) { return // File explicitly excluded via exclude_modified_update: true } if (this.settings.excludedFolders.some(folder => file.path.startsWith(folder + '/'))) { return // File in excluded folder (e.g., "Templates/") } const now = moment() const isAppendArray = this.settings.storeHistoryLog || cache?.frontmatter?.[this.settings.appendField] === true // Check minimum update interval (30 seconds) to prevent sync conflicts let secondsSinceLastUpdate = Infinity if (cache?.frontmatter?.[this.settings.frontmatterProperty]) { let previousEntry = cache?.frontmatter?.[this.settings.frontmatterProperty] if (isAppendArray && Array.isArray(previousEntry)) { previousEntry = previousEntry[this.settings.historyNewestFirst ? 0 : previousEntry.length - 1] } const previousMoment = moment(previousEntry, this.settings.momentFormat) if (previousMoment.isValid()) { secondsSinceLastUpdate = now.diff(previousMoment, 'seconds') } } if (secondsSinceLastUpdate > 30) { let newEntry = this.formatFrontmatterDate(now) // Handle array-based history if (isAppendArray) { let entries = cache?.frontmatter?.[this.settings.frontmatterProperty] || [] if (!Array.isArray(entries)) entries = [entries] if (entries.length) { const previousMoment = moment(entries[this.settings.historyNewestFirst ? 0 : entries.length - 1]) if (now.isSame(previousMoment, this.settings.appendMaximumFrequency)) { // Replace entry if within same frequency unit (e.g., same day) entries[this.settings.historyNewestFirst ? 0 : entries.length - 1] = newEntry } else { this.settings.historyNewestFirst ? entries.unshift(newEntry) : entries.push(newEntry) } // Trim to max items if (this.settings.historyMaxItems && entries.length > this.settings.historyMaxItems) { entries = this.settings.historyNewestFirst ? entries.slice(0, this.settings.historyMaxItems) : entries.slice(-this.settings.historyMaxItems) } } else { entries.push(newEntry) } newEntry = entries } // Update frontmatter this.app.fileManager.processFrontMatter(file, frontmatter => { frontmatter[this.settings.frontmatterProperty] = newEntry // Optionally add created date if (!this.settings.onlyUpdateExisting && this.settings.createdDateProperty && !frontmatter[this.settings.createdDateProperty]) { frontmatter[this.settings.createdDateProperty] = this.formatFrontmatterDate(moment(file.stat.ctime || now)) } }) } }, this.settings.timeout * 1000) } ``` -------------------------------- ### Exclude Files with Frontmatter Property Source: https://context7.com/alangrainger/obsidian-frontmatter-modified-date/llms.txt This YAML snippet demonstrates how to exclude a specific file from automatic 'modified' date updates. By adding the `exclude_modified_update: true` property in the file's frontmatter, the plugin will ignore this file. This is useful for template files or other documents that should not have their modification times altered by the plugin. ```yaml --- exclude_modified_update: true title: My Template --- This file will not have its modified date updated automatically. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.