2026-04-06 13:52:34 +00:00
|
|
|
import {
|
|
|
|
|
App,
|
|
|
|
|
normalizePath,
|
|
|
|
|
Notice,
|
|
|
|
|
PluginSettingTab,
|
|
|
|
|
Setting,
|
|
|
|
|
} from "obsidian";
|
|
|
|
|
|
|
|
|
|
import { AutoArchiveService } from "./autoarchive/AutoArchiveService";
|
|
|
|
|
import type { AutoArchiveRule } from "./autoarchive/AutoArchiveTypes";
|
|
|
|
|
import type SimpleArchiver from "./main";
|
|
|
|
|
import { AutoArchiveRuleModal } from "./modals";
|
|
|
|
|
|
|
|
|
|
export class SimpleArchiverSettingsTab extends PluginSettingTab {
|
|
|
|
|
plugin: SimpleArchiver;
|
|
|
|
|
autoArchiveService: AutoArchiveService;
|
|
|
|
|
activeTab: "general" | "autoArchive" = "general";
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
app: App,
|
|
|
|
|
plugin: SimpleArchiver,
|
|
|
|
|
autoArchiveService: AutoArchiveService,
|
|
|
|
|
) {
|
|
|
|
|
super(app, plugin);
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
this.autoArchiveService = autoArchiveService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display(): void {
|
|
|
|
|
const { containerEl } = this;
|
|
|
|
|
|
|
|
|
|
containerEl.empty();
|
|
|
|
|
|
|
|
|
|
const tabContainer = containerEl.createDiv({
|
|
|
|
|
cls: "setting-tab-container",
|
|
|
|
|
});
|
|
|
|
|
const tabButtonContainer = tabContainer.createDiv({
|
|
|
|
|
cls: "setting-tab-buttons",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const generalTabButton = tabButtonContainer.createEl("button", {
|
|
|
|
|
text: "General",
|
|
|
|
|
cls:
|
|
|
|
|
this.activeTab === "general"
|
|
|
|
|
? "setting-tab-button active"
|
|
|
|
|
: "setting-tab-button",
|
|
|
|
|
});
|
|
|
|
|
generalTabButton.addEventListener("click", () => {
|
|
|
|
|
this.activeTab = "general";
|
|
|
|
|
this.display();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const autoArchiveTabButton = tabButtonContainer.createEl("button", {
|
|
|
|
|
text: "Auto-Archive",
|
|
|
|
|
cls:
|
|
|
|
|
this.activeTab === "autoArchive"
|
|
|
|
|
? "setting-tab-button active"
|
|
|
|
|
: "setting-tab-button",
|
|
|
|
|
});
|
|
|
|
|
autoArchiveTabButton.addEventListener("click", () => {
|
|
|
|
|
this.activeTab = "autoArchive";
|
|
|
|
|
this.display();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const tabContent = containerEl.createDiv({
|
|
|
|
|
cls: "setting-tab-content",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (this.activeTab === "general") {
|
|
|
|
|
this.displayGeneralSettings(tabContent);
|
|
|
|
|
} else {
|
|
|
|
|
this.displayAutoArchiveSettings(tabContent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private displayGeneralSettings(containerEl: HTMLElement): void {
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Archive folder")
|
|
|
|
|
.setDesc(
|
|
|
|
|
"The folder to use as the Archive. If the folder doesn't exist, it will be created when archiving a " +
|
|
|
|
|
'note. Folder names must not contain "\\", "/" or ":" and must not start with ".".',
|
|
|
|
|
)
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("Archive folder")
|
|
|
|
|
.setValue(normalizePath(this.plugin.settings.archiveFolder))
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
if (this.setArchiveFolder(value)) {
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
} else {
|
|
|
|
|
text.setValue(this.plugin.settings.archiveFolder);
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private displayAutoArchiveSettings(containerEl: HTMLElement): void {
|
|
|
|
|
const frequencySetting = new Setting(containerEl)
|
|
|
|
|
.setName("Auto-archive frequency")
|
|
|
|
|
.setDesc("How often to check and process auto-archive rules");
|
|
|
|
|
|
|
|
|
|
const lastRunLineEl = frequencySetting.descEl.createDiv({
|
|
|
|
|
cls: "setting-item-description",
|
|
|
|
|
});
|
|
|
|
|
const refreshLastRunLine = () => {
|
|
|
|
|
lastRunLineEl.setText(
|
|
|
|
|
`Last auto-archive run: ${this.getLastAutoArchiveRunText()}`,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
refreshLastRunLine();
|
|
|
|
|
|
|
|
|
|
frequencySetting
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Auto Archive Now")
|
|
|
|
|
.setTooltip("Process auto-archive rules immediately")
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
button.setDisabled(true);
|
|
|
|
|
button.setButtonText("Processing...");
|
|
|
|
|
await this.autoArchiveService.processAutoArchiveRules();
|
|
|
|
|
refreshLastRunLine();
|
|
|
|
|
this.autoArchiveService.scheduleAutoArchive();
|
|
|
|
|
button.setButtonText("Auto Archive Now");
|
|
|
|
|
button.setDisabled(false);
|
|
|
|
|
new Notice("Auto-archive rules processed");
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.addDropdown((dropdown) =>
|
|
|
|
|
dropdown
|
|
|
|
|
.addOption("5", "Every 5 minutes")
|
|
|
|
|
.addOption("15", "Every 15 minutes")
|
|
|
|
|
.addOption("30", "Every 30 minutes")
|
|
|
|
|
.addOption("60", "Every 60 minutes")
|
|
|
|
|
.addOption("360", "Every 6 hours")
|
|
|
|
|
.addOption("720", "Every 12 hours")
|
|
|
|
|
.addOption("1440", "Every 24 hours")
|
|
|
|
|
.addOption("2880", "Every 48 hours")
|
|
|
|
|
.setValue(
|
|
|
|
|
this.plugin.settings.autoArchiveFrequency.toString(),
|
|
|
|
|
)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.autoArchiveFrequency = parseInt(
|
|
|
|
|
value,
|
|
|
|
|
10,
|
|
|
|
|
);
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
this.autoArchiveService.scheduleAutoArchive();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Auto-archive startup delay")
|
|
|
|
|
.setDesc(
|
|
|
|
|
"How long to wait after Obsidian starts before checking if auto-archive should run",
|
|
|
|
|
)
|
|
|
|
|
.addDropdown((dropdown) =>
|
|
|
|
|
dropdown
|
|
|
|
|
.addOption("5", "5 seconds")
|
|
|
|
|
.addOption("10", "10 seconds")
|
|
|
|
|
.addOption("30", "30 seconds")
|
|
|
|
|
.addOption("60", "60 seconds")
|
|
|
|
|
.addOption("120", "2 minutes")
|
|
|
|
|
.addOption("300", "5 minutes")
|
|
|
|
|
.setValue(
|
|
|
|
|
this.plugin.settings.autoArchiveStartupDelaySeconds.toString(),
|
|
|
|
|
)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.autoArchiveStartupDelaySeconds =
|
|
|
|
|
parseInt(value, 10);
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
this.autoArchiveService.scheduleStartupAutoArchiveCheck();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Auto-archive rules")
|
|
|
|
|
.setDesc(
|
|
|
|
|
"Define rules for automatically archiving files in specific folders",
|
|
|
|
|
)
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button.setButtonText("Add Rule").onClick(() => {
|
|
|
|
|
this.addAutoArchiveRule();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const rulesContainer = containerEl.createDiv({
|
|
|
|
|
cls: "auto-archive-rules-container",
|
|
|
|
|
});
|
|
|
|
|
this.displayAutoArchiveRules(rulesContainer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private displayAutoArchiveRules(containerEl: HTMLElement): void {
|
|
|
|
|
if (this.plugin.settings.autoArchiveRules.length === 0) {
|
|
|
|
|
containerEl.createEl("p", {
|
|
|
|
|
text: "No auto-archive rules configured yet.",
|
|
|
|
|
cls: "setting-item-description",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const rule of this.plugin.settings.autoArchiveRules) {
|
|
|
|
|
this.displayAutoArchiveRule(containerEl, rule);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private displayAutoArchiveRule(
|
|
|
|
|
containerEl: HTMLElement,
|
|
|
|
|
rule: AutoArchiveRule,
|
|
|
|
|
): void {
|
|
|
|
|
const ruleContainer = containerEl.createDiv({
|
|
|
|
|
cls: "auto-archive-rule",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
new Setting(ruleContainer)
|
|
|
|
|
.setName(`Folder: ${rule.folderPath || "(not set)"}`)
|
|
|
|
|
.setClass("auto-archive-rule-header")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle.setValue(rule.enabled).onChange(async (value) => {
|
|
|
|
|
rule.enabled = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Edit")
|
|
|
|
|
.setClass("mod-cta")
|
|
|
|
|
.onClick(() => {
|
|
|
|
|
this.editAutoArchiveRule(rule);
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Delete")
|
|
|
|
|
.setWarning()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
this.plugin.settings.autoArchiveRules =
|
|
|
|
|
this.plugin.settings.autoArchiveRules.filter(
|
|
|
|
|
(r) => r.id !== rule.id,
|
|
|
|
|
);
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
this.display();
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const conditionsEl = ruleContainer.createDiv({
|
|
|
|
|
cls: "auto-archive-rule-conditions",
|
|
|
|
|
});
|
|
|
|
|
if (rule.conditions.length === 0) {
|
2026-05-17 10:34:29 +00:00
|
|
|
conditionsEl.createSpan({
|
2026-04-06 13:52:34 +00:00
|
|
|
text: "No conditions set",
|
|
|
|
|
cls: "setting-item-description",
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
if (rule.conditions.length > 1) {
|
2026-05-17 10:34:29 +00:00
|
|
|
conditionsEl.createDiv({
|
2026-04-06 13:52:34 +00:00
|
|
|
text: `Logic: ${rule.logicOperator || "AND"}`,
|
|
|
|
|
cls: "auto-archive-rule-logic",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (const condition of rule.conditions) {
|
|
|
|
|
const conditionText =
|
|
|
|
|
this.autoArchiveService.getConditionText(condition);
|
2026-05-17 10:34:29 +00:00
|
|
|
conditionsEl.createDiv({
|
2026-04-06 13:52:34 +00:00
|
|
|
text: `• ${conditionText}`,
|
|
|
|
|
cls: "auto-archive-rule-condition",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private addAutoArchiveRule(): void {
|
|
|
|
|
const newRule: AutoArchiveRule = {
|
|
|
|
|
id: crypto.randomUUID(),
|
|
|
|
|
enabled: true,
|
|
|
|
|
folderPath: "",
|
|
|
|
|
useFolderRegex: false,
|
|
|
|
|
applyRecursively: false,
|
|
|
|
|
conditions: [],
|
|
|
|
|
logicOperator: "AND",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.plugin.settings.autoArchiveRules.push(newRule);
|
|
|
|
|
this.editAutoArchiveRule(newRule);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private editAutoArchiveRule(rule: AutoArchiveRule): void {
|
|
|
|
|
new AutoArchiveRuleModal(this.app, this.plugin, rule, async () => {
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
this.display();
|
|
|
|
|
}).open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private validateArchiveFolderName(value: string): boolean {
|
|
|
|
|
return !/^\.|[:/\\]\.|:/.test(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setArchiveFolder(value: string): boolean {
|
|
|
|
|
if (!this.validateArchiveFolderName(value)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.plugin.settings.archiveFolder = value;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getLastAutoArchiveRunText(): string {
|
|
|
|
|
const lastRunAt =
|
|
|
|
|
this.plugin.autoArchiveRuntimeData.lastAutoArchiveRunAt;
|
|
|
|
|
|
|
|
|
|
if (!Number.isFinite(lastRunAt) || lastRunAt <= 0) {
|
|
|
|
|
return "never";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Date(lastRunAt).toLocaleString();
|
|
|
|
|
}
|
|
|
|
|
}
|