From 8a7cc65e88fb36af886ef21b5ac2a34bf7f6acfb Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 8 Dec 2024 10:58:03 +0000 Subject: [PATCH] Add skeleton for file event handling --- plugin/src/events/file-event-handler.ts | 8 ++ plugin/src/events/sync-event-handler.ts | 21 +++++ plugin/src/logger.ts | 68 ++++++++++++++ plugin/src/plugin.ts | 119 ++++++++++++++++++++++++ plugin/src/settings/settings.ts | 5 +- plugin/src/views/sync-view.ts | 42 +++++++++ 6 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 plugin/src/events/file-event-handler.ts create mode 100644 plugin/src/events/sync-event-handler.ts create mode 100644 plugin/src/logger.ts create mode 100644 plugin/src/plugin.ts create mode 100644 plugin/src/views/sync-view.ts diff --git a/plugin/src/events/file-event-handler.ts b/plugin/src/events/file-event-handler.ts new file mode 100644 index 00000000..bd0124f0 --- /dev/null +++ b/plugin/src/events/file-event-handler.ts @@ -0,0 +1,8 @@ +import { TAbstractFile } from "obsidian"; + +export interface FileEventHandler { + onCreate: (path: TAbstractFile) => void; + onDelete: (path: TAbstractFile) => void; + onRename: (path: TAbstractFile, oldPath: string) => void; + onModify: (path: TAbstractFile) => void; +} diff --git a/plugin/src/events/sync-event-handler.ts b/plugin/src/events/sync-event-handler.ts new file mode 100644 index 00000000..0d1034eb --- /dev/null +++ b/plugin/src/events/sync-event-handler.ts @@ -0,0 +1,21 @@ +import { TAbstractFile } from "obsidian"; +import { FileEventHandler } from "./file-event-handler"; +import { Logger } from "src/logger"; + +export class SyncEventHandler implements FileEventHandler { + onCreate(path: TAbstractFile) { + Logger.getInstance().info(`File created: ${path}`); + } + + onDelete(path: TAbstractFile) { + Logger.getInstance().info(`File deleted: ${path}`); + } + + onRename(path: TAbstractFile, oldPath: string) { + Logger.getInstance().info(`File renamed: ${oldPath} -> ${path}`); + } + + onModify(path: TAbstractFile) { + Logger.getInstance().info(`File modified: ${path}`); + } +} diff --git a/plugin/src/logger.ts b/plugin/src/logger.ts new file mode 100644 index 00000000..b6a93ed6 --- /dev/null +++ b/plugin/src/logger.ts @@ -0,0 +1,68 @@ +enum LogLevel { + INFO, + WARNING, + ERROR, +} + +class LogLine { + constructor(public level: LogLevel, public message: string) {} + + public toString(): string { + return `${this.formatLevel()}: ${this.message}`; + } + + private formatLevel(): string { + switch (this.level) { + case LogLevel.INFO: + return "INFO"; + case LogLevel.WARNING: + return "WARNING"; + case LogLevel.ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } + } +} + +export class Logger { + private static readonly MAX_MESSAGES = 1000; + + private static instance: Logger; + private messages: LogLine[] = []; + + private constructor() {} + + static getInstance(): Logger { + if (!Logger.instance) { + Logger.instance = new Logger(); + } + return Logger.instance; + } + + public info(message: string): void { + this.pushMessage(message, LogLevel.INFO); + console.log(message); + } + + public warn(message: string): void { + this.pushMessage(message, LogLevel.WARNING); + console.warn(message); + } + + public error(message: string): void { + this.pushMessage(message, LogLevel.ERROR); + console.error(message); + } + + public getMessages(): LogLine[] { + return this.messages; + } + + private pushMessage(message: string, level: LogLevel): void { + this.messages.push(new LogLine(level, message)); + if (this.messages.length > Logger.MAX_MESSAGES) { + this.messages.shift(); + } + } +} diff --git a/plugin/src/plugin.ts b/plugin/src/plugin.ts new file mode 100644 index 00000000..7484c02f --- /dev/null +++ b/plugin/src/plugin.ts @@ -0,0 +1,119 @@ +import { + App, + Editor, + MarkdownView, + Modal, + Notice, + Plugin, + PluginSettingTab, + Setting, + WorkspaceLeaf, +} from "obsidian"; + +import * as plugin from "../../backend/sync_wasm/pkg/sync_wasm.js"; +import * as wasmBin from "../../backend/sync_wasm/pkg/sync_wasm_bg.wasm"; +import { getSystemErrorName } from "util"; +import { SyncSettingsTab } from "./settings/settings-tab.js"; +import { SyncView } from "./views/sync-view.js"; +import { + DEFAULT_SETTINGS, + SettingsContainer, + SyncSettings, +} from "./settings/settings.js"; +import { Logger } from "./logger.js"; +import { SyncEventHandler } from "./events/sync-event-handler.js"; + +export default class SyncPlugin extends Plugin { + async onload() { + Logger.getInstance().info('Starting plugin "Sample Plugin"'); + + await plugin.default(Promise.resolve((wasmBin as any).default)); + + const eventHandler = new SyncEventHandler(); + + [ + this.app.vault.on( + "create", + eventHandler.onCreate.bind(eventHandler) + ), + this.app.vault.on( + "modify", + eventHandler.onModify.bind(eventHandler) + ), + this.app.vault.on( + "delete", + eventHandler.onDelete.bind(eventHandler) + ), + this.app.vault.on( + "rename", + eventHandler.onRename.bind(eventHandler) + ), + ].forEach((event) => this.registerEvent(event)); + + // This creates an icon in the left ribbon. + const ribbonIconEl = this.addRibbonIcon( + "dice", + "Sample Plugin", + (evt: MouseEvent) => { + // Called when the user clicks the icon. + new Notice("This is a notice!"); + } + ); + // Perform additional things with the ribbon + ribbonIconEl.addClass("my-plugin-ribbon-class"); + + // This adds a status bar item to the bottom of the app. Does not work on mobile apps. + const statusBarItemEl = this.addStatusBarItem(); + statusBarItemEl.setText("Status Bar Text"); + + // This adds an editor command that can perform some operation on the current editor instance + this.addCommand({ + id: "sample-editor-command", + name: "Sample editor command", + editorCallback: (editor: Editor, view: MarkdownView) => { + console.log(editor.getSelection()); + editor.replaceSelection("Sample Editor Command"); + }, + }); + + const settingsContainer = new SettingsContainer( + this, + await this.loadData() + ); + this.addSettingTab( + new SyncSettingsTab(this.app, this, settingsContainer) + ); + + // When registering intervals, this function will automatically clear the interval when the plugin is disabled. + this.registerInterval( + window.setInterval(() => console.log("setInterval"), 5 * 60 * 1000) + ); + this.registerView(SyncView.TYPE, (leaf) => new SyncView(leaf)); + + this.addRibbonIcon("dice", "Activate view", () => { + this.activateView(); + }); + } + + onunload() {} + + async activateView() { + const { workspace } = this.app; + + let leaf: WorkspaceLeaf | null = null; + const leaves = workspace.getLeavesOfType(SyncView.TYPE); + + 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); + await leaf?.setViewState({ type: SyncView.TYPE, active: true }); + } + + // "Reveal" the leaf in case it is in a collapsed sidebar + workspace.revealLeaf(leaf!); + } +} diff --git a/plugin/src/settings/settings.ts b/plugin/src/settings/settings.ts index c509cf3a..6ed45463 100644 --- a/plugin/src/settings/settings.ts +++ b/plugin/src/settings/settings.ts @@ -19,10 +19,7 @@ export class SettingsContainer { private onChangeHandlers: Array<(settings: SyncSettings) => void> = []; - public constructor( - private plugin: SyncPlugin, - private loadedSettings: any - ) { + public constructor(private plugin: SyncPlugin, loadedSettings: any) { this._settings = Object.assign({}, DEFAULT_SETTINGS, loadedSettings); } diff --git a/plugin/src/views/sync-view.ts b/plugin/src/views/sync-view.ts new file mode 100644 index 00000000..4c53593a --- /dev/null +++ b/plugin/src/views/sync-view.ts @@ -0,0 +1,42 @@ +import { ItemView, WorkspaceLeaf } from "obsidian"; +import { Logger } from "src/logger"; + +export class SyncView extends ItemView { + public static TYPE = "example-view"; + + constructor(leaf: WorkspaceLeaf) { + super(leaf); + } + + getViewType() { + return SyncView.TYPE; + } + + getDisplayText() { + return "Example view"; + } + + async onOpen() { + const container = this.containerEl.children[1]; + container.empty(); + container.createEl("h4", { text: "Example view" }); + + setInterval(() => this.updateView(), 1000); + } + + async updateView() { + const container = this.containerEl.children[1]; + container.empty(); + + const messages = Logger.getInstance() + .getMessages() + .map((message) => message.toString()) + .join("\n"); + + container.createEl("pre", { text: messages }); + } + + async onClose() { + // Nothing to clean up. + } +}