mirror of
https://github.com/poanse/obsidian-taskmap.git
synced 2026-07-22 06:05:58 +00:00
Add note folder project setting (#1)
Added note folder project settings Refactored events handling
This commit is contained in:
parent
13c7f3b131
commit
2aeed7bd70
13 changed files with 226 additions and 156 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "taskmap",
|
||||
"name": "Taskmap",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"minAppVersion": "1.12.4",
|
||||
"description": "Plan projects via interactive GUI task trees with automatic layout.",
|
||||
"author": "poanse",
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-taskmap",
|
||||
"version": "0.0.8",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-taskmap",
|
||||
"version": "0.0.8",
|
||||
"version": "0.1.2",
|
||||
"description": "Taskmap plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -494,15 +494,13 @@ export class Context {
|
|||
* Opens the note on the right side.
|
||||
*/
|
||||
public async createLinkedNote(taskId: TaskId, plugin: TaskmapPlugin) {
|
||||
const projectNoteFolder = this.versionedData.getFolderPath();
|
||||
const pluginNoteFolder = plugin.settings.newNoteFolder;
|
||||
const taskmapPath =
|
||||
plugin.app.workspace.getActiveViewOfType(TaskmapView)?.file?.parent
|
||||
?.path;
|
||||
let folderPath = "";
|
||||
if (plugin.settings.newNoteFolder) {
|
||||
folderPath = plugin.settings.newNoteFolder;
|
||||
} else if (taskmapPath) {
|
||||
folderPath = taskmapPath;
|
||||
}
|
||||
plugin.app.workspace.getActiveViewOfType(TaskmapView)?.file?.parent?.path;
|
||||
const folderPath =
|
||||
projectNoteFolder || pluginNoteFolder || taskmapPath || "";
|
||||
|
||||
const filepath = this.filePathFromTask(taskId, folderPath);
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,27 +1,8 @@
|
|||
import {
|
||||
Notice,
|
||||
type Plugin,
|
||||
TAbstractFile,
|
||||
type App,
|
||||
TFile,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import type { ProjectData } from "./data/ProjectData.svelte";
|
||||
import type { TaskData } from "./types";
|
||||
import { type App, type Plugin, TAbstractFile, TFile, TFolder } from "obsidian";
|
||||
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 {
|
||||
if (taskPath === oldPath) {
|
||||
return newPath;
|
||||
}
|
||||
return newPath + taskPath.slice(oldPath.length);
|
||||
}
|
||||
import type { ProjectData } from "./data/ProjectData.svelte";
|
||||
|
||||
/** In-memory index of `.taskmap` files; `TFile.path` stays in sync with vault renames. */
|
||||
export class FileWatcherWithCache {
|
||||
|
|
@ -61,7 +42,7 @@ export class FileWatcherWithCache {
|
|||
);
|
||||
plugin.registerEvent(
|
||||
app.vault.on("rename", (file, oldPath) => {
|
||||
void this.onRenameLinkUpdate(file, oldPath, app);
|
||||
void this.onRenameLinkUpdate(app, file, oldPath);
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -93,144 +74,158 @@ export class FileWatcherWithCache {
|
|||
|
||||
/**
|
||||
* Path-only renames update `TFile.path` on the same instance — no Set mutation.
|
||||
* Still handle extension changes (e.g. `.taskmap` → `.md`).
|
||||
* Still handle extension changes (e.g. `.md` -> `.taskmap`).
|
||||
*/
|
||||
onRenameCacheUpdate(file: TAbstractFile): void {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
this.cachedTaskmapFiles.delete(file);
|
||||
if (file.extension === "taskmap") {
|
||||
this.cachedTaskmapFiles.add(file);
|
||||
if (file instanceof TFile) {
|
||||
this.cachedTaskmapFiles.delete(file);
|
||||
if (file.extension === "taskmap") {
|
||||
this.cachedTaskmapFiles.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onDeleteLinkUpdate(file: TAbstractFile, app: App) {
|
||||
console.debug(`${file.path} deleted`);
|
||||
let files: TFile[];
|
||||
if (file instanceof TFile) {
|
||||
files = [file];
|
||||
await this.handleDeleteFile(app, file);
|
||||
} else if (file instanceof TFolder) {
|
||||
files = [...this.cachedTaskmapFiles].filter((f) =>
|
||||
pathIsUnderFolder(f.path, file.path),
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
// files in folder emit separate events per file
|
||||
await this.handleNoteFolderDelete(app, file);
|
||||
}
|
||||
await this.handleDeleteFiles(app, files);
|
||||
}
|
||||
|
||||
async onRenameLinkUpdate(
|
||||
file: TAbstractFile,
|
||||
oldPath: string,
|
||||
app: App,
|
||||
) {
|
||||
console.debug(`${file.path} renamed`);
|
||||
let files: TFile[];
|
||||
async onRenameLinkUpdate(app: App, file: TAbstractFile, oldPath: string) {
|
||||
if (file instanceof TFile) {
|
||||
files = [file];
|
||||
await this.handleRenameFiles(app, file, oldPath);
|
||||
} else if (file instanceof TFolder) {
|
||||
files = [...this.cachedTaskmapFiles].filter((f) =>
|
||||
pathIsUnderFolder(f.path, file.path),
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
// files in folder emit separate events per file
|
||||
await this.handleNoteFolderRename(app, file, oldPath);
|
||||
}
|
||||
await this.handleRenameFiles(app, files, oldPath, file.path);
|
||||
}
|
||||
|
||||
async handleDeleteFiles(app: App, files: TFile[]) {
|
||||
const deletedPaths = new Set<string>(files.map((f) => f.path));
|
||||
async handleDeleteFile(app: App, deletedFile: TFile) {
|
||||
for (const taskmapFile of [...this.cachedTaskmapFiles]) {
|
||||
if (deletedPaths.has(taskmapFile.path)) {
|
||||
if (deletedFile.path === taskmapFile.path) {
|
||||
// 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 this.getProjectData(app, taskmapFile);
|
||||
if (!projectData) {
|
||||
continue;
|
||||
}
|
||||
let taskmapFileChanged = false;
|
||||
projectData.tasks
|
||||
.filter((t) => t.path && deletedPaths.has(t.path))
|
||||
.forEach((t) => {
|
||||
t.name = delink(t.name);
|
||||
t.path = undefined;
|
||||
taskmapFileChanged = true;
|
||||
});
|
||||
if (taskmapFileChanged) {
|
||||
console.debug(`${taskmapFile.path} changed`);
|
||||
// resave file on the file system
|
||||
await updateFile(app, taskmapFile, projectData);
|
||||
} else {
|
||||
console.debug(`${taskmapFile.path} not changed`);
|
||||
const affectedTasks = projectData.tasks.filter(
|
||||
(t) => t.path && deletedFile.path === t.path,
|
||||
);
|
||||
for (const t of affectedTasks) {
|
||||
t.name = delink(t.name);
|
||||
t.path = undefined;
|
||||
}
|
||||
if (affectedTasks) {
|
||||
console.debug(`${taskmapFile.path} changed`);
|
||||
await this.updateFileAndRefreshActiveView(
|
||||
app,
|
||||
taskmapFile,
|
||||
projectData,
|
||||
);
|
||||
}
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
async handleRenameFiles(
|
||||
app: App,
|
||||
changedMdFiles: TFile[],
|
||||
oldPath: string,
|
||||
newPath: string,
|
||||
) {
|
||||
// update paths in taskmap files
|
||||
async handleNoteFolderDelete(app: App, folder: TFolder) {
|
||||
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)}`);
|
||||
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);
|
||||
}
|
||||
mapping.set(t.path, t);
|
||||
}
|
||||
});
|
||||
console.debug("mapping " + JSON.stringify(mapping.keys()));
|
||||
let changed = false;
|
||||
changedMdFiles
|
||||
.filter((mdFile) => mapping.has(mdFile.path))
|
||||
.forEach((mdFile) => {
|
||||
const t = mapping.get(mdFile.path)!;
|
||||
t.name = generateMarkdownLink(app, mdFile);
|
||||
changed = true;
|
||||
});
|
||||
if (changed) {
|
||||
console.debug(`${taskmapFile.path} changed`);
|
||||
// resave file on the file system
|
||||
await updateFile(app, taskmapFile, projectData);
|
||||
} else {
|
||||
console.debug(`${taskmapFile.path} not changed`);
|
||||
const projectData = await this.getProjectData(app, taskmapFile);
|
||||
if (projectData && projectData.folderPath == folder.path) {
|
||||
console.debug(`Removed folderPath for ${taskmapFile.name}`);
|
||||
projectData.setFolderPath("");
|
||||
await this.updateFileAndRefreshActiveView(
|
||||
app,
|
||||
taskmapFile,
|
||||
projectData,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refresh all active views
|
||||
for (const view of app.workspace
|
||||
async handleRenameFiles(app: App, changedMdFile: TFile, oldPath: string) {
|
||||
// update paths in taskmap files
|
||||
for (const taskmapFile of [...this.cachedTaskmapFiles]) {
|
||||
const projectData = await this.getProjectData(app, taskmapFile);
|
||||
if (!projectData) {
|
||||
continue;
|
||||
}
|
||||
const affectedTasks = projectData.tasks.filter(
|
||||
(t) => t.path && t.path === oldPath,
|
||||
);
|
||||
for (const t of affectedTasks) {
|
||||
t.path = changedMdFile.path;
|
||||
t.name = generateMarkdownLink(app, changedMdFile);
|
||||
}
|
||||
if (affectedTasks) {
|
||||
console.debug(`${taskmapFile.path} changed`);
|
||||
// resave file on the file system
|
||||
await this.updateFileAndRefreshActiveView(
|
||||
app,
|
||||
taskmapFile,
|
||||
projectData,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleNoteFolderRename(app: App, folder: TFolder, oldPath: string) {
|
||||
for (const taskmapFile of [...this.cachedTaskmapFiles]) {
|
||||
const projectData = await this.getProjectData(app, taskmapFile);
|
||||
if (projectData && projectData.folderPath == oldPath) {
|
||||
projectData.setFolderPath(folder.path);
|
||||
console.debug(
|
||||
`Changed folderPath for ${taskmapFile.name} from ${oldPath} to ${folder.path}`,
|
||||
);
|
||||
await this.updateFileAndRefreshActiveView(
|
||||
app,
|
||||
taskmapFile,
|
||||
projectData,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getProjectData(app: App, taskmapFile: TFile) {
|
||||
const viewByName = new Map<string, TaskmapView>();
|
||||
app.workspace
|
||||
.getLeavesOfType(TASKMAP_VIEW_TYPE)
|
||||
.map((l) => l.view)
|
||||
.filter((view) => view instanceof TaskmapView)) {
|
||||
await view.refreshUi();
|
||||
.forEach((v) => {
|
||||
if (v instanceof TaskmapView && v.file) {
|
||||
viewByName.set(v.file.name, v);
|
||||
}
|
||||
});
|
||||
const view = viewByName.get(taskmapFile.name);
|
||||
if (view) {
|
||||
// ensure that view data is consistent with disk data
|
||||
await view.debouncedSave.run();
|
||||
return view.projectData;
|
||||
}
|
||||
const loadedProjectData = await loadProjectData(app, taskmapFile);
|
||||
if (loadedProjectData) {
|
||||
return loadedProjectData;
|
||||
}
|
||||
}
|
||||
|
||||
async updateFileAndRefreshActiveView(
|
||||
app: App,
|
||||
taskmapFile: TFile,
|
||||
projectData: ProjectData,
|
||||
) {
|
||||
await updateFile(app, taskmapFile, projectData);
|
||||
for (const view of app.workspace
|
||||
.getLeavesOfType(TASKMAP_VIEW_TYPE)
|
||||
.map((l) => l.view)) {
|
||||
if (
|
||||
view instanceof TaskmapView &&
|
||||
view.file?.name === taskmapFile.name
|
||||
) {
|
||||
await view.refreshUi();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
src/ProjectSettingsModal.ts
Normal file
42
src/ProjectSettingsModal.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Modal, Setting, TFolder } from "obsidian";
|
||||
import type { Context } from "./Context.svelte.js";
|
||||
import { FolderSuggest } from "./helpers/FolderSuggest";
|
||||
|
||||
export class ProjectSettingsModal extends Modal {
|
||||
constructor(private readonly context: Context) {
|
||||
super(context.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl("h2", { text: "Project settings" });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName("Note folder")
|
||||
.setDesc(
|
||||
"Notes created from tasks will be placed in this folder. If blank, they will be placed in the default location for this vault.",
|
||||
)
|
||||
.addText((text) => {
|
||||
new FolderSuggest(this.app, text.inputEl);
|
||||
text.setPlaceholder("")
|
||||
.setValue(this.context.versionedData.getFolderPath() ?? "")
|
||||
.onChange(async (value) => {
|
||||
if (
|
||||
value === "" ||
|
||||
this.app.vault.getAbstractFileByPath(
|
||||
value,
|
||||
) instanceof TFolder
|
||||
) {
|
||||
this.context.versionedData.setFolderPath(value);
|
||||
this.context.save();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ export function serializeProjectData(projectData: ProjectData) {
|
|||
schemaVersion: TASKMAP_FILE_SCHEMA_VERSION,
|
||||
tasks: projectData.tasks,
|
||||
blockerPairs: projectData.blockerPairs,
|
||||
folderPath: projectData.folderPath,
|
||||
curTaskId: projectData.curTaskId,
|
||||
},
|
||||
null,
|
||||
|
|
@ -21,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 {
|
||||
|
|
@ -34,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,10 +3,12 @@
|
|||
import {SettingsIconCode} from "../types";
|
||||
import {getTooltipTextSettings, tooltip} from "../Tooltip";
|
||||
import {Redo2, Settings, Undo2} from 'lucide-svelte';
|
||||
import { ProjectSettingsModal } from "src/ProjectSettingsModal.js";
|
||||
|
||||
let { iconCode, context }: { iconCode: SettingsIconCode, context: Context } = $props();
|
||||
|
||||
let isPressedDown = $state(false);
|
||||
let modal: ProjectSettingsModal | null = null;
|
||||
|
||||
function onpointerdown(event: MouseEvent) {
|
||||
isPressedDown = true;
|
||||
|
|
@ -20,7 +22,6 @@
|
|||
|
||||
let isButtonDisabled = $derived(
|
||||
(iconCode == SettingsIconCode.NONE)
|
||||
|| (iconCode == SettingsIconCode.SETTINGS_MENU)
|
||||
|| (iconCode == SettingsIconCode.SETTINGS_UNDO && !context.versionedData.canUndo())
|
||||
|| (iconCode == SettingsIconCode.SETTINGS_REDO && !context.versionedData.canRedo())
|
||||
);
|
||||
|
|
@ -39,6 +40,8 @@
|
|||
context.versionedData.undo();
|
||||
} else if (iconCode == SettingsIconCode.SETTINGS_REDO) {
|
||||
context.versionedData.redo();
|
||||
} else if (iconCode == SettingsIconCode.SETTINGS_MENU) {
|
||||
new ProjectSettingsModal(context).open();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -6,27 +6,27 @@
|
|||
} from "../types";
|
||||
import { NoTaskId, RootTaskId } from "../NodePositionsCalculator";
|
||||
import { serializeProjectData } from "../SaveManager";
|
||||
import type { ProjectFileParsed } from "./ProjectDataSchema";
|
||||
|
||||
export class ProjectData {
|
||||
tasks = $state(new Array<TaskData>());
|
||||
blockerPairs = $state(new Array<BlockerPair>());
|
||||
folderPath: string | undefined;
|
||||
curTaskId = RootTaskId;
|
||||
|
||||
public static getDefault(): ProjectData {
|
||||
return new ProjectData({
|
||||
tasks: new Array<TaskData>(),
|
||||
blockerPairs: new Array<BlockerPair>(),
|
||||
folderPath: undefined,
|
||||
curTaskId: 0,
|
||||
});
|
||||
}
|
||||
|
||||
constructor(obj: {
|
||||
tasks: TaskData[];
|
||||
blockerPairs?: BlockerPair[];
|
||||
curTaskId: number;
|
||||
}) {
|
||||
constructor(obj: ProjectFileParsed) {
|
||||
this.tasks = obj.tasks;
|
||||
this.blockerPairs = obj.blockerPairs ?? [];
|
||||
this.folderPath = obj.folderPath;
|
||||
this.curTaskId = obj.curTaskId;
|
||||
if (this.tasks.length == 0) {
|
||||
this.addRootTask();
|
||||
|
|
@ -203,6 +203,14 @@ export class ProjectData {
|
|||
public addBlockerPair = (blockerPair: BlockerPair) => {
|
||||
this.blockerPairs.push(blockerPair);
|
||||
};
|
||||
|
||||
public getFolderPath = (): string | undefined => {
|
||||
return this.folderPath;
|
||||
};
|
||||
|
||||
public setFolderPath = (path: string | undefined) => {
|
||||
this.folderPath = path === "" ? undefined : path;
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_DATA = serializeProjectData(ProjectData.getDefault());
|
||||
|
|
|
|||
|
|
@ -40,12 +40,14 @@ export const projectFileSchema = v.object({
|
|||
),
|
||||
tasks: v.array(taskDataSchema),
|
||||
blockerPairs: v.optional(v.array(blockerPairSchema)),
|
||||
folderPath: v.optional(v.string()),
|
||||
curTaskId: v.pipe(v.number(), v.integer()),
|
||||
});
|
||||
|
||||
export type ProjectFileParsed = {
|
||||
tasks: TaskData[];
|
||||
blockerPairs: BlockerPair[];
|
||||
folderPath: string | undefined;
|
||||
curTaskId: number;
|
||||
};
|
||||
|
||||
|
|
@ -89,6 +91,7 @@ export function parseProjectFileJson(parsed: unknown): ProjectFileParsed {
|
|||
return {
|
||||
tasks: o.tasks,
|
||||
blockerPairs: o.blockerPairs ?? [],
|
||||
folderPath: o.folderPath,
|
||||
curTaskId: o.curTaskId,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,14 @@ export class VersionedData {
|
|||
);
|
||||
};
|
||||
|
||||
public getFolderPath = (): string | undefined => {
|
||||
return this.data.getFolderPath();
|
||||
};
|
||||
|
||||
public setFolderPath = (path: string | undefined) => {
|
||||
this.data.setFolderPath(path);
|
||||
};
|
||||
|
||||
public getTasks = (includeDeleted = false) => {
|
||||
return this.data.tasks.filter((t) => includeDeleted || !t.deleted);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue