devonthesofa_obsidian-note-.../main.tsx

334 lines
9.3 KiB
TypeScript
Raw Permalink Normal View History

2025-11-21 11:51:08 +00:00
import { Plugin, WorkspaceLeaf, TFile } from "obsidian";
2025-07-15 15:30:17 +00:00
import StatusBarIntegration from "integrations/status-bar/status-bar";
import eventBus from "core/eventBus";
import { PluginSettingIntegration } from "./integrations/settings/pluginSettings";
import settingsService from "./core/settingsService";
import {
BaseNoteStatusService,
NoteStatusService,
} from "./core/noteStatusService";
2025-07-15 15:30:17 +00:00
import { StatusModalIntegration } from "./integrations/modals/statusModalIntegration";
import ContextMenuIntegration from "./integrations/context-menu/contextMenuIntegration";
import { FileExplorerIntegration } from "./integrations/file-explorer/file-explorer-integration";
import { CommandsIntegration } from "./integrations/commands/commandsIntegration";
2025-08-06 19:08:40 +00:00
import EditorToolbarIntegration from "./integrations/toolbar/editorToolbarIntegration";
2025-07-15 15:30:17 +00:00
import {
2025-07-16 15:52:30 +00:00
GroupedStatusView,
2025-07-15 15:30:17 +00:00
VIEW_TYPE_EXAMPLE,
2025-07-16 15:52:30 +00:00
} from "./integrations/views/grouped-status-view";
2025-07-15 15:30:17 +00:00
import {
StatusDashboardView,
VIEW_TYPE_STATUS_DASHBOARD,
} from "./integrations/views/status-dashboard-view";
import { StatusesInfoPopup } from "./integrations/popups/statusesInfoPopupIntegration";
2025-11-21 11:51:08 +00:00
import statusStoreManager from "./core/statusStoreManager";
2025-11-21 15:02:23 +00:00
import { isExperimentalFeatureEnabled } from "@/utils/experimentalFeatures";
2025-07-15 15:30:17 +00:00
export default class NoteStatusPlugin extends Plugin {
private statusBarIntegration: StatusBarIntegration;
private pluginSettingsIntegration: PluginSettingIntegration;
private contextMenuIntegration: ContextMenuIntegration;
private fileExplorerIntegration: FileExplorerIntegration;
private commandsIntegration: CommandsIntegration;
2025-08-06 19:08:40 +00:00
private editorToolbarIntegration: EditorToolbarIntegration;
2025-11-21 15:02:23 +00:00
private experimentalRibbonShortcuts: Map<string, HTMLElement> = new Map();
2025-05-25 11:24:28 +00:00
async onload() {
2025-07-15 15:30:17 +00:00
BaseNoteStatusService.initialize(this.app);
await this.loadPluginSettings();
await statusStoreManager.initialize(this);
2025-07-15 15:30:17 +00:00
// INFO: initialize all integrations
Promise.all([
this.loadContextMenu(),
this.loadStatusBar(),
this.loadFileExplorer(),
this.loadCommands(),
2025-08-06 19:08:40 +00:00
this.loadEditorToolbar(),
2025-07-15 15:30:17 +00:00
this.loadEventBus(),
]);
this.registerView(
VIEW_TYPE_EXAMPLE,
2025-07-16 15:52:30 +00:00
(leaf) => new GroupedStatusView(leaf),
2025-05-25 11:24:28 +00:00
);
2025-07-15 15:30:17 +00:00
this.registerView(
VIEW_TYPE_STATUS_DASHBOARD,
(leaf) => new StatusDashboardView(leaf),
);
2025-05-25 11:24:28 +00:00
this.syncExperimentalFeatureShortcuts();
2025-07-15 15:30:17 +00:00
}
2025-05-25 11:24:28 +00:00
2025-07-15 15:30:17 +00:00
async onunload() {
// Clean up all integrations
this.statusBarIntegration?.destroy();
this.contextMenuIntegration?.destroy();
this.fileExplorerIntegration?.destroy();
this.commandsIntegration?.destroy();
2025-08-06 19:08:40 +00:00
this.editorToolbarIntegration?.destroy();
2025-07-15 15:30:17 +00:00
this.pluginSettingsIntegration?.destroy();
2025-11-21 15:02:23 +00:00
this.experimentalRibbonShortcuts.forEach((el) => el.remove());
this.experimentalRibbonShortcuts.clear();
2025-07-15 15:30:17 +00:00
// Clean up event subscriptions
eventBus.unsubscribe(
"triggered-open-modal",
"main-triggered-open-modal-subscriptor",
2025-05-25 11:24:28 +00:00
);
eventBus.unsubscribe(
"plugin-settings-changed",
"main-status-popup-setting-subscriptor",
);
2025-05-25 11:24:28 +00:00
}
2025-07-15 15:30:17 +00:00
async activateView() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE);
if (leaves.length > 0) {
// A leaf with our view already exists, use that
leaf = leaves[0];
} else {
// Our view could not be found in the workspace, create a new leaf
// in the right sidebar for it
leaf = workspace.getRightLeaf(false);
if (!leaf) {
console.error(
"getRightLeaf return null, unable to setup the view",
);
} else {
await leaf.setViewState({
type: VIEW_TYPE_EXAMPLE,
active: true,
});
}
}
2025-05-25 11:24:28 +00:00
2025-07-15 15:30:17 +00:00
// "Reveal" the leaf in case it is in a collapsed sidebar
if (!leaf) {
console.error("leaf not found, unable to activate the view");
} else {
workspace.revealLeaf(leaf);
}
}
2025-05-25 11:24:28 +00:00
2025-07-15 15:30:17 +00:00
async activateDashboard() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(VIEW_TYPE_STATUS_DASHBOARD);
if (leaves.length > 0) {
// A leaf with our view already exists, use that
leaf = leaves[0];
} else {
// Our view could not be found in the workspace, create a new leaf
// in the right sidebar for it
leaf = workspace.getRightLeaf(false);
if (!leaf) {
console.error(
"getRightLeaf return null, unable to setup the dashboard",
);
} else {
await leaf.setViewState({
type: VIEW_TYPE_STATUS_DASHBOARD,
active: true,
});
}
}
2025-05-25 11:24:28 +00:00
2025-07-15 15:30:17 +00:00
// "Reveal" the leaf in case it is in a collapsed sidebar
if (!leaf) {
console.error("leaf not found, unable to activate the dashboard");
} else {
workspace.revealLeaf(leaf);
}
2025-05-25 11:24:28 +00:00
}
2025-11-21 15:02:23 +00:00
private syncExperimentalFeatureShortcuts(): void {
const shortcuts = this.getExperimentalShortcuts();
Object.entries(shortcuts).forEach(([key, config]) => {
this.toggleExperimentalRibbon(key, config);
});
}
2025-11-21 15:02:23 +00:00
private getExperimentalShortcuts() {
return {
groupedStatusView: {
feature: "groupedStatusView" as const,
icon: "list-tree",
title: "Open grouped status view",
handler: () => this.activateView(),
},
statusDashboard: {
feature: "statusDashboard" as const,
icon: "activity",
title: "Open status dashboard",
handler: () => this.activateDashboard(),
},
};
}
2025-11-21 15:02:23 +00:00
private toggleExperimentalRibbon(
key: string,
config: {
feature: Parameters<typeof isExperimentalFeatureEnabled>[0];
icon: string;
title: string;
handler: () => void;
},
): void {
const isEnabled = isExperimentalFeatureEnabled(config.feature);
const existingIcon = this.experimentalRibbonShortcuts.get(key);
if (isEnabled && !existingIcon) {
const ribbonEl = this.addRibbonIcon(
config.icon,
config.title,
config.handler,
);
this.experimentalRibbonShortcuts.set(key, ribbonEl);
return;
}
2025-11-21 15:02:23 +00:00
if (!isEnabled && existingIcon) {
existingIcon.remove();
this.experimentalRibbonShortcuts.delete(key);
}
}
2025-07-15 15:30:17 +00:00
private async loadEventBus() {
// Propagate to custom event bus the new active file
this.app.workspace.on(
"active-leaf-change",
(leaf: WorkspaceLeaf | null) => {
eventBus.publish("active-file-change", { leaf });
},
2025-05-25 11:24:28 +00:00
);
2025-07-15 15:30:17 +00:00
// Propagate to custom event bus the manually frontmatter data
this.registerEvent(
this.app.metadataCache.on("changed", (file) => {
2025-11-21 11:51:08 +00:00
eventBus.publish("status-changed", { file });
2025-07-15 15:30:17 +00:00
}),
2025-05-25 11:24:28 +00:00
);
2025-07-15 15:30:17 +00:00
// Register listeners
eventBus.subscribe(
"triggered-open-modal",
({ statusService, activeStatus }) => {
StatusModalIntegration.open(
this.app,
statusService,
activeStatus,
);
2025-07-15 15:30:17 +00:00
},
"main-triggered-open-modal-subscriptor",
2025-05-25 11:24:28 +00:00
);
eventBus.subscribe(
"plugin-settings-changed",
({ key, value }) => {
if (key === "enableStatusOverviewPopup" && value === false) {
StatusesInfoPopup.close();
}
if (
key === "enableExperimentalFeatures" ||
key === "enableStatusDashboard" ||
key === "enableGroupedStatusView"
) {
this.syncExperimentalFeatureShortcuts();
}
},
"main-status-popup-setting-subscriptor",
);
2025-11-21 11:51:08 +00:00
this.registerEvent(
this.app.vault.on("create", async (file) => {
if (
file instanceof TFile &&
file.extension === "md" &&
settingsService.settings.defaultStatusForNewNotes
) {
// Wait a bit to ensure metadata is processed or templates are applied
setTimeout(async () => {
// Ensure file still exists
if (!this.app.vault.getAbstractFileByPath(file.path)) {
return;
}
const noteStatusService = new NoteStatusService(file);
// Populate statuses to see if it already has any
noteStatusService.populateStatuses();
const groupedStatuses =
noteStatusService.getStatusesByAllKeys();
const hasStatuses = Object.values(groupedStatuses).some(
(statuses) => statuses.length > 0,
);
if (!hasStatuses) {
await noteStatusService.addStatus(
settingsService.settings.tagPrefix,
settingsService.settings
.defaultStatusForNewNotes as string,
);
}
}, 500); // Increased delay to allow more time for templates
}
}),
);
2025-11-21 11:51:08 +00:00
this.registerEvent(
this.app.vault.on("rename", (file) => {
if (file instanceof TFile) {
eventBus.publish("status-changed", { file });
}
}),
);
this.registerEvent(
this.app.vault.on("delete", (file) => {
if (file instanceof TFile) {
eventBus.publish("status-changed", { file });
}
}),
);
2025-05-25 11:24:28 +00:00
}
2025-07-15 15:30:17 +00:00
async loadPluginSettings() {
// INFO: Loads the settings data
await settingsService.initialize(this);
// INFO: Integrates the plugin settings section
this.pluginSettingsIntegration = new PluginSettingIntegration(this);
await this.pluginSettingsIntegration.integrate();
2025-05-25 11:24:28 +00:00
}
2025-07-15 15:30:17 +00:00
private async loadContextMenu() {
this.contextMenuIntegration = new ContextMenuIntegration(this);
await this.contextMenuIntegration.integrate();
2025-05-25 11:24:28 +00:00
}
2025-07-15 15:30:17 +00:00
private async loadStatusBar() {
this.statusBarIntegration = new StatusBarIntegration(this);
await this.statusBarIntegration.integrate();
}
private async loadFileExplorer() {
this.fileExplorerIntegration = new FileExplorerIntegration(this);
await this.fileExplorerIntegration.integrate();
2025-05-25 11:24:28 +00:00
}
2025-07-15 15:30:17 +00:00
private async loadCommands() {
this.commandsIntegration = new CommandsIntegration(this);
await this.commandsIntegration.integrate();
2025-05-23 17:27:55 +00:00
}
2025-08-06 19:08:40 +00:00
private async loadEditorToolbar() {
this.editorToolbarIntegration = new EditorToolbarIntegration(this);
await this.editorToolbarIntegration.integrate();
}
2025-05-23 16:54:24 +00:00
}