Sync while typing not just on save

This commit is contained in:
Andras Schmelczer 2025-03-27 19:24:59 +00:00
parent 60ed4e5109
commit e5a0c5830a
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
4 changed files with 72 additions and 93 deletions

View file

@ -1,58 +0,0 @@
import type { SyncClient } from "sync-client";
import type { TAbstractFile } from "obsidian";
import { TFile } from "obsidian";
export class ObsidianFileEventHandler {
public constructor(private readonly client: SyncClient) {}
public async onCreate(file: TAbstractFile): Promise<void> {
if (file instanceof TFile) {
this.client.logger.info(`File created: ${file.path}`);
await this.client.syncLocallyCreatedFile(file.path);
} else {
this.client.logger.debug(`Folder created: ${file.path}, ignored`);
}
}
public async onDelete(file: TAbstractFile): Promise<void> {
if (file instanceof TFile) {
this.client.logger.info(`File deleted: ${file.path}`);
await this.client.syncLocallyDeletedFile(file.path);
} else {
this.client.logger.debug(`Folder deleted: ${file.path}, ignored`);
}
}
public async onRename(file: TAbstractFile, oldPath: string): Promise<void> {
if (file instanceof TFile) {
this.client.logger.info(`File renamed: ${oldPath} -> ${file.path}`);
await this.client.syncLocallyUpdatedFile({
oldPath,
relativePath: file.path
});
} else {
this.client.logger.debug(
`Folder renamed: ${oldPath} -> ${file.path}, ignored`
);
}
}
public async onModify(file: TAbstractFile): Promise<void> {
if (file instanceof TFile) {
if (file.basename.startsWith("console-log.iPhone")) {
return;
}
this.client.logger.info(`File modified: ${file.path}`);
await this.client.syncLocallyUpdatedFile({
relativePath: file.path
});
} else {
this.client.logger.debug(`Folder modified: ${file.path}, ignored`);
}
}
}

View file

@ -1,15 +1,23 @@
import type { Stat, Vault } from "obsidian";
import { normalizePath } from "obsidian";
import type { Stat, Vault, Workspace } from "obsidian";
import { MarkdownView, normalizePath } from "obsidian";
import type { FileSystemOperations, RelativePath } from "sync-client";
export class ObsidianFileSystemOperations implements FileSystemOperations {
public constructor(private readonly vault: Vault) {}
public constructor(
private readonly vault: Vault,
private readonly workspace: Workspace
) {}
public async listAllFiles(): Promise<RelativePath[]> {
return this.vault.getFiles().map((file) => file.path);
}
public async read(path: RelativePath): Promise<Uint8Array> {
const view = this.workspace.getActiveViewOfType(MarkdownView);
if (view?.file?.path === path) {
return new TextEncoder().encode(view.editor.getValue());
}
return new Uint8Array(
await this.vault.adapter.readBinary(normalizePath(path))
);

View file

@ -1,10 +1,15 @@
import type { WorkspaceLeaf } from "obsidian";
import { Platform, Plugin } from "obsidian";
import type {
Editor,
MarkdownFileInfo,
MarkdownView,
TAbstractFile,
WorkspaceLeaf
} from "obsidian";
import { Platform, Plugin, TFile } from "obsidian";
import "./styles.scss";
import "../manifest.json";
import { SyncSettingsTab } from "./views/settings-tab";
import { HistoryView } from "./views/history-view";
import { ObsidianFileEventHandler } from "./obisidan-event-handler";
import { StatusBar } from "./views/status-bar";
import { LogsView } from "./views/logs-view";
import { StatusDescription } from "./views/status-description";
@ -15,6 +20,7 @@ import { ObsidianFileSystemOperations } from "./obsidian-file-system";
export default class VaultLinkPlugin extends Plugin {
private settingsTab: SyncSettingsTab | undefined;
private client!: SyncClient;
private static registerConsoleForLogging(client: SyncClient): void {
client.logger.addOnMessageListener((logLine: LogLine) => {
const formatted = `${logLine.timestamp.toISOString()} ${logLine.level} ${logLine.message}`;
@ -38,7 +44,10 @@ export default class VaultLinkPlugin extends Plugin {
public async onload(): Promise<void> {
this.client = await SyncClient.create({
fs: new ObsidianFileSystemOperations(this.app.vault),
fs: new ObsidianFileSystemOperations(
this.app.vault,
this.app.workspace
),
persistence: {
load: this.loadData.bind(this),
save: this.saveData.bind(this)
@ -80,35 +89,9 @@ export default class VaultLinkPlugin extends Plugin {
async (_: MouseEvent) => this.activateView(LogsView.TYPE)
);
const eventHandler = new ObsidianFileEventHandler(this.client);
this.app.workspace.onLayoutReady(async () => {
this.client.logger.info("Initialising sync handlers");
[
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.registerEditorEvents();
void this.client.start();
this.client.logger.info("Sync handlers initialised");
});
}
@ -145,4 +128,51 @@ export default class VaultLinkPlugin extends Plugin {
await workspace.revealLeaf(leaf);
}
}
private registerEditorEvents() {
[
this.app.workspace.on(
"editor-change",
async (
_editor: Editor,
info: MarkdownView | MarkdownFileInfo
) => {
const file = info.file;
if (file) {
await this.client.syncLocallyUpdatedFile({
relativePath: file.path
});
}
}
),
this.app.vault.on("create", async (file: TAbstractFile) => {
if (file instanceof TFile) {
await this.client.syncLocallyCreatedFile(file.path);
}
}),
this.app.vault.on("modify", async (file: TAbstractFile) => {
if (file instanceof TFile) {
await this.client.syncLocallyUpdatedFile({
relativePath: file.path
});
}
}),
this.app.vault.on("delete", async (file: TAbstractFile) => {
await this.client.syncLocallyDeletedFile(file.path);
}),
this.app.vault.on(
"rename",
async (file: TAbstractFile, oldPath: string) => {
if (file instanceof TFile) {
await this.client.syncLocallyUpdatedFile({
oldPath,
relativePath: file.path
});
}
}
)
].forEach((event) => {
this.registerEvent(event);
});
}
}

View file

@ -1,6 +1,5 @@
import type { IconName, WorkspaceLeaf } from "obsidian";
import { ItemView, setIcon } from "obsidian";
import { intlFormatDistance } from "date-fns";
import type { HistoryEntry, SyncClient } from "sync-client";
import { SyncType } from "sync-client";