Files
obsidian-vault/.obsidian/plugins/front-matter-timestamps/main.js
2026-06-23 00:24:32 +08:00

480 lines
17 KiB
JavaScript

/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => FrontMatterTimestampsPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian = require("obsidian");
var DEFAULT_SETTINGS = {
autoUpdate: true,
autoAddTimestamps: true,
createdPropertyName: "created",
modifiedPropertyName: "modified",
dateFormat: "YYYY-MM-DDTHH:mm:ssZ",
allowNonEmptyNewFile: false,
delayAddingTimestamps: 1e3,
excludedFolders: [],
customCommand: "",
debug: false
};
async function calculateChecksum(file, vault) {
const fileContent = await vault.read(file);
const buffer = new TextEncoder().encode(fileContent);
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
return hashHex;
}
var FrontMatterTimestampsPlugin = class extends import_obsidian.Plugin {
constructor() {
super(...arguments);
this.lastActiveFile = null;
this.lastChecksum = null;
this.pendingNewFiles = /* @__PURE__ */ new Set();
}
isPathExcluded(filePath) {
if (this.settings.excludedFolders.length === 0) {
return false;
}
const pathSegments = filePath.split("/");
const fullPathChecks = pathSegments.map(
(_, index) => pathSegments.slice(0, index + 1).join("/")
);
return this.settings.excludedFolders.some(
(excludedPath) => fullPathChecks.includes(excludedPath)
);
}
async onload() {
await this.loadSettings();
this.addSettingTab(new FrontMatterTimestampsSettingTab(this.app, this));
this.addCommand({
id: "update-modified-time",
name: "Update modified time",
checkCallback: (checking) => {
const markdownView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView);
if (markdownView) {
if (!checking) {
this.updateModifiedTime(markdownView.file);
}
return true;
}
return false;
}
});
this.registerEvent(
this.app.vault.on("create", (f) => {
if (this.settings.debug) {
console.log(`File created: ${f.path}`);
}
switch (f.constructor) {
case import_obsidian.TFile:
break;
default:
return;
}
const file = f;
if (Date.now() - file.stat.ctime > 3e4) {
if (this.settings.debug) {
console.log(
`Skipping timestamps on ${file.path}; ctime is older than 60s.`
);
}
return;
}
if (this.settings.autoAddTimestamps && file.extension === "md" && !this.isPathExcluded(file.path)) {
this.pendingNewFiles.add(file.path);
this.handleNewFileTimestamps(file);
}
})
);
if (this.settings.autoUpdate) {
this.registerEvent(
this.app.workspace.on(
"active-leaf-change",
() => this.handleFileChange()
)
);
}
}
async handleNewFileTimestamps(file) {
const { debug } = this.settings;
if (!this.settings.allowNonEmptyNewFile) {
const fileContent = await this.app.vault.read(file);
if (fileContent.trim()) {
if (debug) {
console.log(
`File ${file.path} is not empty, skipping initial timestamps.`
);
}
this.pendingNewFiles.delete(file.path);
return;
}
}
if (debug) {
console.log(
`Waiting ${this.settings.delayAddingTimestamps}ms before adding timestamps to ${file.path}.`
);
}
await new Promise(
(resolve) => setTimeout(resolve, this.settings.delayAddingTimestamps)
);
if (!await this.app.vault.adapter.exists(file.path)) {
if (debug) {
console.log(
`File ${file.path} no longer exists after delay, skipping timestamp addition.`
);
}
this.pendingNewFiles.delete(file.path);
return;
}
const currentTime = (0, import_obsidian.moment)().format(this.settings.dateFormat);
try {
await this.app.fileManager.processFrontMatter(
file,
(frontmatter) => {
if (!frontmatter[this.settings.createdPropertyName]) {
frontmatter[this.settings.createdPropertyName] = currentTime;
}
if (!frontmatter[this.settings.modifiedPropertyName]) {
frontmatter[this.settings.modifiedPropertyName] = currentTime;
}
}
);
if (debug) {
console.log(`Timestamps added to new file ${file.path}`);
}
} catch (error) {
console.error(
`Error adding timestamps to new file ${file.path}`,
error
);
} finally {
this.pendingNewFiles.delete(file.path);
}
}
async handleFileChange() {
const { debug } = this.settings;
const markdownView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView);
const currentFile = markdownView ? markdownView.file : null;
if (debug) {
console.log("handleFileChange called");
}
if (!currentFile) {
if (this.lastActiveFile && !this.isPathExcluded(this.lastActiveFile.path)) {
try {
const fileExists = await this.app.vault.adapter.exists(
this.lastActiveFile.path
);
if (fileExists) {
const currentChecksum = await calculateChecksum(
this.lastActiveFile,
this.app.vault
);
if (this.lastChecksum !== currentChecksum) {
if (debug) {
console.log(
`File ${this.lastActiveFile.path} changed while inactive, updating modified time.`
);
}
await this.updateModifiedTime(this.lastActiveFile);
}
} else if (debug) {
console.log(
`Last active file ${this.lastActiveFile.path} no longer exists.`
);
}
} catch (error) {
console.error(
`Error checking checksum for ${this.lastActiveFile.path}:`,
error
);
}
}
this.lastActiveFile = null;
this.lastChecksum = null;
return;
}
if (this.isPathExcluded(currentFile.path))
return;
if (this.lastActiveFile && this.lastActiveFile.path !== currentFile.path) {
try {
const lastFileExists = await this.app.vault.adapter.exists(
this.lastActiveFile.path
);
if (lastFileExists) {
const lastFileChecksum = await calculateChecksum(
this.lastActiveFile,
this.app.vault
);
if (this.lastChecksum !== lastFileChecksum) {
if (debug) {
console.log(
`File ${this.lastActiveFile.path} changed before switching, updating modified time.`
);
}
await this.updateModifiedTime(this.lastActiveFile);
}
}
} catch (error) {
console.error(
`Error checking checksum for ${this.lastActiveFile.path}:`,
error
);
}
}
if (!this.lastActiveFile || this.lastActiveFile.path !== currentFile.path) {
this.lastActiveFile = currentFile;
this.lastChecksum = await calculateChecksum(
currentFile,
this.app.vault
);
}
}
async updateModifiedTime(file) {
const { debug } = this.settings;
if (!file || !file.path)
return;
if (this.isPathExcluded(file.path))
return;
if (file.extension !== "md") {
if (debug) {
console.log("File is not a markdown file, skipping.");
}
return;
}
const currentTime = (0, import_obsidian.moment)().format(this.settings.dateFormat);
if (debug) {
console.log(
`Updating modified time for ${file.path} after a delay of ${this.settings.delayAddingTimestamps}ms.`
);
}
await new Promise(
(resolve) => setTimeout(resolve, this.settings.delayAddingTimestamps)
);
if (!await this.app.vault.adapter.exists(file.path)) {
if (debug) {
console.log(
`File ${file.path} no longer exists after delay, skipping modified time update.`
);
}
return;
}
try {
await this.app.fileManager.processFrontMatter(
file,
(frontmatter) => {
frontmatter[this.settings.modifiedPropertyName] = currentTime;
}
);
if (debug) {
console.log(`File frontmatter updated for ${file.path}`);
}
if (this.settings.customCommand) {
this.app.commands.executeCommandById(
this.settings.customCommand
);
}
} catch (error) {
console.error(
`Error updating frontmatter for file ${file.path}`,
error
);
}
}
onunload() {
console.log("Unloading Front Matter Timestamps plugin");
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
};
var FrontMatterTimestampsSettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
new import_obsidian.Setting(containerEl).setName("Automatic update").setDesc("Automatically update modified time on file change").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.autoUpdate).onChange(async (value) => {
this.plugin.settings.autoUpdate = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Automatic timestamps").setDesc(
"Automatically add created and modified timestamps to new notes"
).addToggle(
(toggle) => toggle.setValue(this.plugin.settings.autoAddTimestamps).onChange(async (value) => {
this.plugin.settings.autoAddTimestamps = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Created property name").setDesc("Customise the property name for creation timestamp").addText(
(text) => text.setValue(this.plugin.settings.createdPropertyName).onChange(async (value) => {
this.plugin.settings.createdPropertyName = value.trim();
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Modified property name").setDesc("Customise the property name for modification timestamp").addText(
(text) => text.setValue(this.plugin.settings.modifiedPropertyName).onChange(async (value) => {
this.plugin.settings.modifiedPropertyName = value.trim();
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Date and time format").setDesc(
"Specify the desired date and time format using Moment.js tokens. Example: YYYY-MM-DDTHH:mm"
).addText((text) => {
text.setPlaceholder("YYYY-MM-DDTHH:mm:ssZ").setValue(this.plugin.settings.dateFormat).onChange(async (value) => {
this.plugin.settings.dateFormat = value.trim() || DEFAULT_SETTINGS.dateFormat;
await this.plugin.saveSettings();
});
const resetButton = text.inputEl.parentElement.createEl(
"button",
{
text: "Reset",
cls: "mod-ghost"
}
);
resetButton.addEventListener("click", async () => {
text.setValue(DEFAULT_SETTINGS.dateFormat);
this.plugin.settings.dateFormat = DEFAULT_SETTINGS.dateFormat;
await this.plugin.saveSettings();
});
});
new import_obsidian.Setting(containerEl).setName("Allow non-empty file to be treated as new note").setDesc(
"Newly created file does not have to be empty to add timestamps. Enable if using plugins that automatically add content to new notes (Templater, Daily Notes, etc.)."
).addToggle(
(toggle) => toggle.setValue(this.plugin.settings.allowNonEmptyNewFile).onChange(async (value) => {
this.plugin.settings.allowNonEmptyNewFile = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Delay adding timestamps to new notes").setDesc(
"Delay in milliseconds before adding timestamps to new notes to avoid conflicts with other plugins that also add content to new notes. The default value of 1000 milliseconds should be sufficient for most cases, but you can adjust it as needed. Set to 0 to disable the delay if you are not experiencing any issues or not using such plugins."
).addText(
(text) => text.setValue(
this.plugin.settings.delayAddingTimestamps.toString()
).onChange(async (value) => {
const delay = parseInt(value.trim(), 10);
if (!isNaN(delay)) {
this.plugin.settings.delayAddingTimestamps = delay;
await this.plugin.saveSettings();
}
})
);
const excludedFoldersSetting = new import_obsidian.Setting(containerEl).setName("Excluded folders").setDesc(
"Manage folders that are excluded from timestamp updates. You can add subfolder paths as needed. For example, 'folder/subfolder' will exclude 'subfolder' but not 'folder'."
);
const listContainer = excludedFoldersSetting.settingEl.createDiv();
const updateFolderList = () => {
listContainer.empty();
this.plugin.settings.excludedFolders.forEach((folder, index) => {
const folderDiv = listContainer.createDiv("folder-entry");
folderDiv.style.display = "flex";
folderDiv.style.alignItems = "center";
folderDiv.style.justifyContent = "space-between";
folderDiv.style.marginBottom = "10px";
const folderNameSpan = folderDiv.createSpan({ text: folder });
folderNameSpan.style.flex = "1";
folderNameSpan.style.marginRight = "20px";
const removeButton = folderDiv.createEl("button", {
text: "Remove"
});
removeButton.onclick = async () => {
this.plugin.settings.excludedFolders.splice(index, 1);
await this.plugin.saveSettings();
updateFolderList();
};
});
const addButtonDiv = listContainer.createDiv();
addButtonDiv.style.display = "flex";
addButtonDiv.style.alignItems = "center";
const addInput = addButtonDiv.createEl("input", {
type: "text",
placeholder: "Add new folder path..."
});
addInput.style.flex = "1";
addInput.style.marginRight = "10px";
addInput.addEventListener("keypress", async (event) => {
if (event.key === "Enter" && addInput.value.trim().length > 0) {
const trimmedValue = addInput.value.trim().replace(/\/+$/, "");
this.plugin.settings.excludedFolders.push(trimmedValue);
await this.plugin.saveSettings();
addInput.value = "";
updateFolderList();
event.preventDefault();
}
});
const addButton = addButtonDiv.createEl("button", { text: "Add" });
addButton.onclick = async () => {
if (addInput.value.trim().length > 0) {
const trimmedValue = addInput.value.trim().replace(/\/+$/, "");
this.plugin.settings.excludedFolders.push(trimmedValue);
await this.plugin.saveSettings();
addInput.value = "";
updateFolderList();
}
};
};
updateFolderList();
const commandSetting = new import_obsidian.Setting(containerEl).setName("Execute command after update").setDesc(
"Select a command to run after the modified time is successfully updated"
);
const select = commandSetting.controlEl.createEl("select");
let noneOption = select.createEl("option", { text: "None" });
noneOption.value = "";
if (this.plugin.settings.customCommand === "") {
noneOption.selected = true;
}
this.app.commands.listCommands().forEach((command) => {
let option = select.createEl("option", { text: command.name });
option.value = command.id;
if (command.id === this.plugin.settings.customCommand) {
option.selected = true;
}
});
select.addEventListener("change", async () => {
this.plugin.settings.customCommand = select.value;
await this.plugin.saveSettings();
});
new import_obsidian.Setting(containerEl).setName("Debug mode").setDesc("Enable debug mode to display detailed logs").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.debug).onChange(async (value) => {
this.plugin.settings.debug = value;
await this.plugin.saveSettings();
})
);
}
};
/* nosourcemap */