mirror of
https://github.com/gtritchie/bulk-properties.git
synced 2026-07-22 06:08:37 +00:00
- Bump @types/node, esbuild, jiti, obsidian, typescript (6.0), and typescript-eslint to latest - Hold @eslint/js at 9.x: eslint-plugin-obsidianmd peer-pins ^9.30.1 - tsconfig: drop unused baseUrl and switch moduleResolution to bundler (both deprecated as errors in TS 6.0) - main.ts: declare settings to refine Plugin.settings (new in 1.13.0) - settings.ts: add override to display(); centralise the still-supported imperative re-render behind rerender() with one deprecation suppression - Keep setWarning() over setDestructive() to avoid raising minAppVersion from 1.8.7 to 1.13.0
192 lines
5.1 KiB
TypeScript
192 lines
5.1 KiB
TypeScript
import {Plugin, TFile} from "obsidian";
|
|
import {BulkPropertiesSettingTab, BulkPropertiesSettings, DEFAULT_SETTINGS, PROPERTY_TYPES} from "./settings";
|
|
import {BulkEditModal} from "./bulk-edit-modal";
|
|
import {deselectAll} from "./deselect-all";
|
|
import {getSelectedFiles} from "./files";
|
|
import {removeSelectionProperty} from "./remove-selection-property";
|
|
import {isFileSelected, setSelection} from "./toggle-selection";
|
|
|
|
export default class BulkPropertiesPlugin extends Plugin {
|
|
declare settings: BulkPropertiesSettings;
|
|
private statusBarEl: HTMLElement | null = null;
|
|
private statusBarTimer: number | null = null;
|
|
private saveQueue: Promise<void> = Promise.resolve();
|
|
|
|
override async onload() {
|
|
await this.loadSettings();
|
|
|
|
this.statusBarEl = this.addStatusBarItem();
|
|
|
|
this.addRibbonIcon("list-checks", "Bulk edit selected notes", () => {
|
|
new BulkEditModal(this.app, this).open();
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "bulk-edit-selected",
|
|
name: "Bulk edit selected notes",
|
|
callback: () => {
|
|
new BulkEditModal(this.app, this).open();
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "deselect-all",
|
|
name: "Deselect all notes",
|
|
callback: () => {
|
|
void deselectAll(this);
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "remove-selection-property",
|
|
name: "Remove selection property from all notes",
|
|
callback: () => {
|
|
removeSelectionProperty(this);
|
|
},
|
|
});
|
|
|
|
this.addSettingTab(new BulkPropertiesSettingTab(this.app, this));
|
|
|
|
this.registerEvent(
|
|
this.app.metadataCache.on("changed", () => {
|
|
this.debouncedUpdateStatusBar();
|
|
}),
|
|
);
|
|
|
|
this.registerEvent(
|
|
this.app.vault.on("delete", () => {
|
|
this.debouncedUpdateStatusBar();
|
|
}),
|
|
);
|
|
|
|
this.registerEvent(
|
|
this.app.workspace.on("editor-menu", (menu) => {
|
|
menu.addItem((item) => {
|
|
item.setTitle("Bulk edit selected notes")
|
|
.setIcon("list-checks")
|
|
.onClick(() => {
|
|
new BulkEditModal(this.app, this).open();
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
|
|
this.registerEvent(
|
|
this.app.workspace.on("file-menu", (menu, file) => {
|
|
if (!(file instanceof TFile) || file.extension !== "md") return;
|
|
const selProp = this.settings.selectionProperty;
|
|
const selected = isFileSelected(this.app, file, selProp);
|
|
menu.addItem((item) => {
|
|
item.setTitle(selected ? "Deselect for bulk edit" : "Select for bulk edit")
|
|
.setIcon("list-checks")
|
|
.onClick(() => {
|
|
void setSelection(
|
|
this.app,
|
|
file,
|
|
selProp,
|
|
!selected,
|
|
() => this.updateStatusBar(),
|
|
);
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
|
|
this.app.workspace.onLayoutReady(() => {
|
|
this.updateStatusBar();
|
|
});
|
|
}
|
|
|
|
override onunload() {
|
|
if (this.statusBarTimer !== null && this.statusBarEl) {
|
|
this.statusBarEl.win.clearTimeout(this.statusBarTimer);
|
|
this.statusBarTimer = null;
|
|
}
|
|
}
|
|
|
|
private debouncedUpdateStatusBar(): void {
|
|
if (!this.statusBarEl) return;
|
|
const win = this.statusBarEl.win;
|
|
if (this.statusBarTimer !== null) {
|
|
win.clearTimeout(this.statusBarTimer);
|
|
}
|
|
this.statusBarTimer = win.setTimeout(() => {
|
|
this.statusBarTimer = null;
|
|
this.updateStatusBar();
|
|
}, 500);
|
|
}
|
|
|
|
updateStatusBar(): void {
|
|
if (!this.statusBarEl) return;
|
|
if (!this.settings.showStatusBarCount) {
|
|
this.statusBarEl.empty();
|
|
return;
|
|
}
|
|
const count = getSelectedFiles(
|
|
this.app,
|
|
this.settings.selectionProperty,
|
|
).length;
|
|
if (count > 0) {
|
|
this.statusBarEl.setText(
|
|
`${count} note${count === 1 ? "" : "s"} selected`,
|
|
);
|
|
} else {
|
|
this.statusBarEl.empty();
|
|
}
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign(
|
|
{}, DEFAULT_SETTINGS,
|
|
await this.loadData() as Partial<BulkPropertiesSettings>,
|
|
);
|
|
|
|
if (typeof this.settings.selectionProperty !== "string"
|
|
|| this.settings.selectionProperty.trim() === "") {
|
|
this.settings.selectionProperty = DEFAULT_SETTINGS.selectionProperty;
|
|
}
|
|
|
|
if (typeof this.settings.showLargeOperationWarning !== "boolean") {
|
|
this.settings.showLargeOperationWarning =
|
|
DEFAULT_SETTINGS.showLargeOperationWarning;
|
|
}
|
|
|
|
if (!Array.isArray(this.settings.properties)) {
|
|
this.settings.properties = [];
|
|
} else {
|
|
const validTypes: ReadonlySet<string> = new Set(PROPERTY_TYPES);
|
|
const before = this.settings.properties.length;
|
|
this.settings.properties = this.settings.properties.filter(
|
|
(p): p is typeof p =>
|
|
typeof p === "object"
|
|
&& p !== null
|
|
&& typeof p.name === "string"
|
|
&& p.name.trim() !== ""
|
|
&& validTypes.has(p.type),
|
|
);
|
|
if (this.settings.properties.length < before) {
|
|
console.warn(
|
|
`bulk-properties: discarded ${before - this.settings.properties.length} malformed property entries from settings`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serializes settings writes through a queue with copy-on-write
|
|
* semantics. Builds a candidate snapshot per call; only assigns
|
|
* it to `this.settings` after persistence succeeds.
|
|
*/
|
|
updateSetting<K extends keyof BulkPropertiesSettings>(
|
|
key: K,
|
|
value: BulkPropertiesSettings[K],
|
|
): Promise<void> {
|
|
const save = this.saveQueue.then(async () => {
|
|
const candidate = {...this.settings, [key]: value};
|
|
await this.saveData(candidate);
|
|
this.settings = candidate;
|
|
});
|
|
this.saveQueue = save.then(() => {}, () => {});
|
|
return save;
|
|
}
|
|
}
|