diff --git a/src/FileWatcherWithCache.ts b/src/FileWatcherWithCache.ts index 7e6be21..7c6f395 100644 --- a/src/FileWatcherWithCache.ts +++ b/src/FileWatcherWithCache.ts @@ -1,22 +1,18 @@ -import { - Notice, - type Plugin, - TAbstractFile, - type App, - TFile, - TFolder, -} from "obsidian"; -import type { ProjectData } from "./data/ProjectData.svelte"; +import { type Plugin, TAbstractFile, type App, TFile, TFolder } from "obsidian"; import type { TaskData } from "./types"; import { TASKMAP_VIEW_TYPE, TaskmapView } from "./TaskmapView"; -import { deserializeProjectData, updateFile } from "./SaveManager"; +import { loadProjectData, updateFile } from "./SaveManager"; import { delink, generateMarkdownLink, pathIsUnderFolder } from "./LinkManager"; function taskPathAffectedByRename(taskPath: string, oldPath: string): boolean { return taskPath === oldPath || pathIsUnderFolder(taskPath, oldPath); } -function remapTaskPathAfterRename(taskPath: string, oldPath: string, newPath: string): string { +function remapTaskPathAfterRename( + taskPath: string, + oldPath: string, + newPath: string, +): string { if (taskPath === oldPath) { return newPath; } @@ -61,7 +57,7 @@ export class FileWatcherWithCache { ); plugin.registerEvent( app.vault.on("rename", (file, oldPath) => { - void this.onRenameLinkUpdate(file, oldPath, app); + void this.onRenameLinkUpdate(app, file, oldPath); }), ); @@ -114,29 +110,19 @@ export class FileWatcherWithCache { files = [...this.cachedTaskmapFiles].filter((f) => pathIsUnderFolder(f.path, file.path), ); + await this.handleNoteFolderDelete(app, file); } else { return; } await this.handleDeleteFiles(app, files); - } - async onRenameLinkUpdate( - file: TAbstractFile, - oldPath: string, - app: App, - ) { - console.debug(`${file.path} renamed`); - let files: TFile[]; - if (file instanceof TFile) { - files = [file]; - } else if (file instanceof TFolder) { - files = [...this.cachedTaskmapFiles].filter((f) => - pathIsUnderFolder(f.path, file.path), - ); - } else { - return; + // refresh all active views + for (const view of app.workspace + .getLeavesOfType(TASKMAP_VIEW_TYPE) + .map((l) => l.view) + .filter((view) => view instanceof TaskmapView)) { + await view.refreshUi(); } - await this.handleRenameFiles(app, files, oldPath, file.path); } async handleDeleteFiles(app: App, files: TFile[]) { @@ -146,13 +132,8 @@ export class FileWatcherWithCache { // Entire .taskmap was removed; link handler runs before cache — skip read/write. continue; } - let projectData: ProjectData; - try { - const projectDataRaw = await app.vault.read(taskmapFile); - projectData = deserializeProjectData(projectDataRaw); - } catch (e) { - console.error(`Taskmap delete hook: skipped invalid file ${taskmapFile.path}`); - new Notice(`Taskmap delete hook: skipped invalid file: ${String(e)}`); + const projectData = await loadProjectData(app, taskmapFile); + if (!projectData) { continue; } let taskmapFileChanged = false; @@ -167,10 +148,25 @@ export class FileWatcherWithCache { console.debug(`${taskmapFile.path} changed`); // resave file on the file system await updateFile(app, taskmapFile, projectData); - } else { - console.debug(`${taskmapFile.path} not changed`); } } + } + + async onRenameLinkUpdate(app: App, file: TAbstractFile, oldPath: string) { + console.debug(`${file.path} renamed`); + let files: TFile[]; + if (file instanceof TFile) { + files = [file]; + } else if (file instanceof TFolder) { + files = [...this.cachedTaskmapFiles].filter((f) => + pathIsUnderFolder(f.path, file.path), + ); + await this.handleNoteFolderRename(app, file, oldPath); + } else { + return; + } + await this.handleRenameFiles(app, files, oldPath, file.path); + // refresh all active views for (const view of app.workspace .getLeavesOfType(TASKMAP_VIEW_TYPE) @@ -180,6 +176,29 @@ export class FileWatcherWithCache { } } + async handleNoteFolderRename(app: App, folder: TFolder, oldPath: string) { + for (const taskmapFile of this.cachedTaskmapFiles) { + const projectData = await loadProjectData(app, taskmapFile); + if (projectData && projectData.folderPath == oldPath) { + console.debug( + `Changed folderPath for ${taskmapFile.name} from ${oldPath} to ${projectData.folderPath}`, + ); + projectData.setFolderPath(folder.path); + await updateFile(app, taskmapFile, projectData); + } + } + } + async handleNoteFolderDelete(app: App, folder: TFolder) { + for (const taskmapFile of this.cachedTaskmapFiles) { + const projectData = await loadProjectData(app, taskmapFile); + if (projectData && projectData.folderPath == folder.path) { + console.debug(`Removed folderPath for ${taskmapFile.name}`); + projectData.setFolderPath(""); + await updateFile(app, taskmapFile, projectData); + } + } + } + async handleRenameFiles( app: App, changedMdFiles: TFile[], @@ -188,26 +207,23 @@ export class FileWatcherWithCache { ) { // update paths in taskmap files for (const taskmapFile of [...this.cachedTaskmapFiles]) { - console.debug(`handling ${taskmapFile.path}`); - let projectData: ProjectData; - try { - const projectDataRaw = await app.vault.read(taskmapFile); - projectData = deserializeProjectData(projectDataRaw); - } catch (e) { - console.error(`Taskmap rename hook: skipped invalid file ${taskmapFile.path}`); - new Notice(`Taskmap rename hook: skipped invalid file: ${String(e)}`); + const projectData = await loadProjectData(app, taskmapFile); + if (!projectData) { continue; } const mapping = new Map(); projectData.tasks.forEach((t) => { if (t.path) { if (taskPathAffectedByRename(t.path, oldPath)) { - t.path = remapTaskPathAfterRename(t.path, oldPath, newPath); + t.path = remapTaskPathAfterRename( + t.path, + oldPath, + newPath, + ); } mapping.set(t.path, t); } }); - console.debug("mapping " + JSON.stringify(mapping.keys())); let changed = false; changedMdFiles .filter((mdFile) => mapping.has(mdFile.path)) @@ -220,17 +236,7 @@ export class FileWatcherWithCache { console.debug(`${taskmapFile.path} changed`); // resave file on the file system await updateFile(app, taskmapFile, projectData); - } else { - console.debug(`${taskmapFile.path} not changed`); } } - - // refresh all active views - for (const view of app.workspace - .getLeavesOfType(TASKMAP_VIEW_TYPE) - .map((l) => l.view) - .filter((view) => view instanceof TaskmapView)) { - await view.refreshUi(); - } } } diff --git a/src/TaskMapSettingsModal.ts b/src/ProjectSettingsModal.ts similarity index 79% rename from src/TaskMapSettingsModal.ts rename to src/ProjectSettingsModal.ts index cd3f556..b680304 100644 --- a/src/TaskMapSettingsModal.ts +++ b/src/ProjectSettingsModal.ts @@ -2,7 +2,7 @@ import { Modal, Setting, TFolder } from "obsidian"; import type { Context } from "./Context.svelte.js"; import { FolderSuggest } from "./helpers/FolderSuggest"; -export class TaskmapSettingsModal extends Modal { +export class ProjectSettingsModal extends Modal { constructor(private readonly context: Context) { super(context.app); } @@ -10,7 +10,7 @@ export class TaskmapSettingsModal extends Modal { onOpen() { const { contentEl } = this; - contentEl.createEl("h2", { text: "Project Settings" }); + contentEl.createEl("h2", { text: "Project settings" }); new Setting(contentEl) .setName("Note folder") @@ -19,14 +19,14 @@ export class TaskmapSettingsModal extends Modal { ) .addText((text) => { new FolderSuggest(this.app, text.inputEl); - text - .setPlaceholder("") + text.setPlaceholder("") .setValue(this.context.versionedData.getFolderPath() ?? "") .onChange(async (value) => { if ( value === "" || - this.app.vault.getAbstractFileByPath(value) instanceof - TFolder + this.app.vault.getAbstractFileByPath( + value, + ) instanceof TFolder ) { this.context.versionedData.setFolderPath(value); this.context.save(); diff --git a/src/SaveManager.ts b/src/SaveManager.ts index 3c39689..db91547 100644 --- a/src/SaveManager.ts +++ b/src/SaveManager.ts @@ -22,6 +22,27 @@ export function serializeProjectData(projectData: ProjectData) { ); } +export async function updateFile( + app: App, + file: TFile, + projectData: ProjectData, +) { + await app.vault.modify(file, serializeProjectData(projectData)); +} + +export async function loadProjectData(app: App, taskmapFile: TFile) { + let projectData: ProjectData | undefined = undefined; + try { + const projectDataRaw = await app.vault.read(taskmapFile); + projectData = deserializeProjectData(projectDataRaw); + } catch (e) { + console.error( + `Taskmap rename hook: skipped invalid file ${taskmapFile.path} ${e}`, + ); + } + return projectData; +} + export function deserializeProjectData(data: string) { let parsed: unknown; try { @@ -35,11 +56,3 @@ export function deserializeProjectData(data: string) { const input = parseProjectFileJson(parsed); return new ProjectData(input); } - -export async function updateFile( - app: App, - file: TFile, - projectData: ProjectData, -) { - await app.vault.modify(file, serializeProjectData(projectData)); -} diff --git a/src/Tooltip.ts b/src/Tooltip.ts index 39fdcf7..1157c9a 100644 --- a/src/Tooltip.ts +++ b/src/Tooltip.ts @@ -58,7 +58,7 @@ export function getTooltipText(code: IconCode) { export function getTooltipTextSettings(code: SettingsIconCode) { switch (code) { case SettingsIconCode.SETTINGS_MENU: - return "WIP (Settings)"; + return "Settings"; case SettingsIconCode.SETTINGS_REDO: return "Redo"; case SettingsIconCode.SETTINGS_UNDO: diff --git a/src/components/SettingsButton.svelte b/src/components/SettingsButton.svelte index 0be6851..8943c1d 100644 --- a/src/components/SettingsButton.svelte +++ b/src/components/SettingsButton.svelte @@ -3,13 +3,12 @@ import {SettingsIconCode} from "../types"; import {getTooltipTextSettings, tooltip} from "../Tooltip"; import {Redo2, Settings, Undo2} from 'lucide-svelte'; - import { TaskmapSettingsModal } from "src/TaskMapSettingsModal.js"; + import { ProjectSettingsModal } from "src/ProjectSettingsModal.js"; let { iconCode, context }: { iconCode: SettingsIconCode, context: Context } = $props(); let isPressedDown = $state(false); - let modal: TaskmapSettingsModal | null = null; - let buttonEl: HTMLDivElement | null = null; + let modal: ProjectSettingsModal | null = null; function onpointerdown(event: MouseEvent) { isPressedDown = true; @@ -43,19 +42,7 @@ } else if (iconCode == SettingsIconCode.SETTINGS_REDO) { context.versionedData.redo(); } else if (iconCode == SettingsIconCode.SETTINGS_MENU) { - modal = new TaskmapSettingsModal(context); - modal.containerEl.classList.add("taskmap-settings-modal"); - modal.open(); - if (buttonEl) { - const rect = buttonEl.getBoundingClientRect(); - modal.containerEl.style.position = "absolute"; - // modal.containerEl.style.top = `${rect.top + 20}px`; - - // 👇 THIS is the key line (left-align with button) - // modal.containerEl.style.left = `${rect.left}px`; - - modal.containerEl.style.transform = "none"; - } + new ProjectSettingsModal(context).open(); } } @@ -65,7 +52,6 @@
{ this.blockerPairs.push(blockerPair); }; + + public getFolderPath = (): string | undefined => { + return this.folderPath; + }; + + public setFolderPath = (path: string | undefined) => { + const next = path === "" ? undefined : path; + if (next === this.folderPath) { + return; + } + this.folderPath = next; + }; } export const DEFAULT_DATA = serializeProjectData(ProjectData.getDefault()); diff --git a/src/data/VersionedData.ts b/src/data/VersionedData.ts index 8915373..c38cbdb 100644 --- a/src/data/VersionedData.ts +++ b/src/data/VersionedData.ts @@ -125,15 +125,11 @@ export class VersionedData { }; public getFolderPath = (): string | undefined => { - return this.data.folderPath; + return this.data.getFolderPath(); }; public setFolderPath = (path: string | undefined) => { - const next = path === "" ? undefined : path; - if (next === this.data.folderPath) { - return; - } - this.data.folderPath = next; + this.data.setFolderPath(path); }; public getTasks = (includeDeleted = false) => {