mirror of
https://github.com/poanse/obsidian-taskmap.git
synced 2026-07-22 06:05:58 +00:00
cleanup, rename, delete hooks
This commit is contained in:
parent
1f755a3bbb
commit
f21e0cfd9a
8 changed files with 109 additions and 97 deletions
|
|
@ -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<string, TaskData>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
</script>
|
||||
|
||||
<div class="button"
|
||||
bind:this={buttonEl}
|
||||
use:tooltip={getTooltipTextSettings(iconCode)}
|
||||
class:disabled={isButtonDisabled}
|
||||
class:no-pan={true}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@
|
|||
}
|
||||
|
||||
function onpointermove(e: PointerEvent) {
|
||||
console.debug('handlePointerMove', e.pointerId, e.button);
|
||||
if (context.draggedTaskId != NoTaskId) {
|
||||
context.taskDraggingManager.onPointerMove(e);
|
||||
if (context.taskDraggingManager.isDragging) {
|
||||
|
|
|
|||
|
|
@ -203,6 +203,18 @@ export class ProjectData {
|
|||
public addBlockerPair = (blockerPair: BlockerPair) => {
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue