changed logo, added folder setting

This commit is contained in:
poanse 2026-04-17 01:09:02 +03:00
parent 7caa10f309
commit 3f659d1363
12 changed files with 135 additions and 39 deletions

View file

@ -26,3 +26,15 @@ export function parseNumber(value: string | null | undefined): number | null {
const num = Number(value);
return isNaN(num) || value.trim() === "" ? null : num;
}
export const LOGO_NAME = "TaskmapLogo";
export const LOGO_CONTENT = `
<g transform="translate(50,50) scale(0.9) translate(-50,-50)">
<path d="M66.66667 12.5L66.66667 29.16667C66.66667 31.46783 68.53217 33.33333 70.83333 33.33333L87.5 33.33333C89.80083 33.33333 91.66667 31.46783 91.66667 29.16667L91.66667 12.5C91.66667 10.19883 89.80083 8.33333 87.5 8.33333L70.83333 8.33333C68.53217 8.33333 66.66667 10.19883 66.66667 12.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="8"/>
<path d="M66.66667 70.83333L66.66667 87.5C66.66667 89.80083 68.53217 91.66667 70.83333 91.66667L87.5 91.66667C89.80083 91.66667 91.66667 89.80083 91.66667 87.5L91.66667 70.83333C91.66667 68.53217 89.80083 66.66667 87.5 66.66667L70.83333 66.66667C68.53217 66.66667 66.66667 68.53217 66.66667 70.83333Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="8"/>
<path d="M8.33333 41.66667L8.33333 58.33333C8.33333 60.6345 10.19883 62.5 12.5 62.5L29.16667 62.5C31.46783 62.5 33.33333 60.6345 33.33333 58.33333L33.33333 41.66667C33.33333 39.3655 31.46783 37.5 29.16667 37.5L12.5 37.5C10.19883 37.5 8.33333 39.3655 8.33333 41.66667Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="8"/>
<path d="M66.66667 79.16667L54.16667 79.16667C53.06158 79.16667 52.00175 78.72767 51.22042 77.94625C50.439 77.16492 50 76.10508 50 75L50 25C50 23.89492 50.439 22.83508 51.22042 22.05375C52.00175 21.27233 53.06158 20.83333 54.16667 20.83333L66.66667 20.83333" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="8"/>
<path d="M50 50L33.33333 50" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="8"/>
</g>
`;

View file

@ -88,7 +88,10 @@ export class Context {
* so pan/zoom and the scene stay intact.
*/
public reloadFromDisk(projectData: ProjectData): void {
this.versionedData = new VersionedData(projectData, new HistoryManager());
this.versionedData = new VersionedData(
projectData,
new HistoryManager(),
);
this.taskDraggingManager.reset();
this.draggedTaskId = NoTaskId;
this.reparentingTaskId = NoTaskId;
@ -409,7 +412,8 @@ export class Context {
public save() {
// Cannot have reference to the view, so save all opened views
this.app.workspace.getLeavesOfType(TASKMAP_VIEW_TYPE)
this.app.workspace
.getLeavesOfType(TASKMAP_VIEW_TYPE)
.map((leaf) => leaf.view)
.filter((view) => view instanceof TaskmapView)
.forEach((view) => view.debouncedSave());
@ -483,10 +487,18 @@ export class Context {
* Creates note named after task name if not exists already.
* Changes task name to a link to the node.
* Opens the note on the right side.
* @param taskId
*/
public async createLinkedNote(taskId: TaskId) {
const filepath = this.filePathFromTask(taskId);
public async createLinkedNote(taskId: TaskId, plugin: TaskmapPlugin) {
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;
}
const filepath = this.filePathFromTask(taskId, folderPath);
try {
const abstractFile = this.app.vault.getAbstractFileByPath(filepath);
@ -499,7 +511,7 @@ export class Context {
);
this.versionedData.setName(
taskId,
tasknameFromFilePath(filepath),
tasknameFromFilePath(this.versionedData.getTask(taskId).name),
filepath,
);
this.save();
@ -509,19 +521,25 @@ export class Context {
await this.openOrFocusNote(tfile);
} catch (error) {
new Notice(
"Error creating note. It might already exist or the name is invalid.",
`Error creating note. It might already exist or the name is invalid. ${String(error)}`,
);
console.error(error);
}
}
public filePathFromTask(taskId: TaskId) {
public filePathFromTask(taskId: TaskId, folder: string = "") {
const task = this.versionedData.getTask(taskId);
const taskName = task.name;
// Sanitize the name for Obsidian filenames
let sanitizedName = taskName.replace(/[\\/:*?"<>|]/g, "-");
sanitizedName = delink(sanitizedName);
return `${sanitizedName}.md`;
if (folder === "" || folder === "/") {
return `${sanitizedName}.md`;
}
if (!folder.endsWith("/")) {
folder = folder + "/";
}
return `${folder}${sanitizedName}.md`;
}
/**
@ -569,7 +587,8 @@ export class Context {
const candidateLeaves = rootLeaves
.reverse()
.filter(
(leaf) => leaf !== activeLeaf && !(leaf.view instanceof TaskmapView),
(leaf) =>
leaf !== activeLeaf && !(leaf.view instanceof TaskmapView),
);
let anotherUnpinnedLeaf: WorkspaceLeaf | null = null;

View file

@ -1,12 +0,0 @@
import { IconCode, StatusCode } from "./types";
export const LOGO_NAME = "TaskmapLogo";
export const LOGO_CONTENT = `
<path d="M88.9366 24.8308V33.8571H55.7464V24.8308H88.9366ZM91.1514 22.5714V11.2857C91.1514 10.0391 90.1586 9.02637 88.9366 9.02637H55.7464C54.5244 9.02637 53.5316 10.0391 53.5316 11.2857V22.5714C53.5316 23.818 54.5244 24.8308 55.7464 24.8308V33.8571L54.612 33.802C49.4075 33.2615 45.267 29.0378 44.7371 23.7287L44.683 22.5714V11.2857C44.683 5.05279 49.6363 9.0886e-08 55.7464 0H88.9366C95.0467 0 100 5.05279 100 11.2857V22.5714C100 28.4138 95.6484 33.2227 90.071 33.802L88.9366 33.8571V24.8308C90.1586 24.8308 91.1514 23.818 91.1514 22.5714Z" fill="currentColor"/>
<path d="M88.9366 69.9736V79H55.7464V69.9736H88.9366ZM91.1514 67.7143V56.4286C91.1514 55.182 90.1586 54.1692 88.9366 54.1692H55.7464C54.5244 54.1692 53.5316 55.182 53.5316 56.4286V67.7143C53.5316 68.9609 54.5244 69.9736 55.7464 69.9736V79L54.612 78.9449C49.4075 78.4043 45.267 74.1806 44.7371 68.8715L44.683 67.7143V56.4286C44.683 50.1956 49.6363 45.1429 55.7464 45.1429H88.9366C95.0467 45.1429 100 50.1956 100 56.4286V67.7143C100 73.5566 95.6484 78.3656 90.071 78.9449L88.9366 79V69.9736C90.1586 69.9736 91.1514 68.9609 91.1514 67.7143Z" fill="currentColor"/>
<path d="M22.8099 52.5212V26.4781C22.8099 17.752 29.7487 10.6737 38.3029 10.6737H47.6701V19.7111H38.3029C34.6368 19.7111 31.6692 22.7384 31.6692 26.4781V52.5212C31.6692 56.261 34.6368 59.2993 38.3029 59.2993H47.6701V68.3256H38.3029C29.7487 68.3256 22.8099 61.2473 22.8099 52.5212Z" fill="currentColor"/>
<path d="M27.2371 34.9806V44.018H0V34.9806H27.2371Z" fill="currentColor"/>
`;
export const LOGO_CONTENT_SMALL = `
`;

View file

@ -1,5 +1,6 @@
import { PluginSettingTab, App, Setting } from "obsidian";
import { PluginSettingTab, App, Setting, TFolder, Notice } from "obsidian";
import type TaskmapPlugin from "./main";
import { FolderSuggest } from "./helpers/FolderSuggest";
export class TaskmapSettingTab extends PluginSettingTab {
plugin: TaskmapPlugin;
@ -38,5 +39,26 @@ export class TaskmapSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.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.plugin.settings.newNoteFolder)
.onChange(async (value) => {
if (
value === "" ||
this.plugin.app.vault.getAbstractFileByPath(
value,
) instanceof TFolder
) {
this.plugin.settings.newNoteFolder = value;
await this.plugin.saveSettings();
}
});
});
}
}

View file

@ -1,9 +1,11 @@
export interface TaskmapPluginSettings {
export interface TaskmapSettings {
zoomSensitivityTouchpad: string;
zoomSensitivityMouse: string;
newNoteFolder: string;
}
export const DEFAULT_SETTINGS: TaskmapPluginSettings = {
export const DEFAULT_SETTINGS: TaskmapSettings = {
zoomSensitivityTouchpad: "100",
zoomSensitivityMouse: "100",
newNoteFolder: "",
};

View file

@ -89,7 +89,7 @@
} else if (iconCode == IconCode.REMOVE_MULTIPLE) {
context.removeTaskBranch(context.selectedTaskId);
} else if (iconCode == IconCode.CREATE_LINKED_NOTE) {
context.createLinkedNote(context.selectedTaskId);
context.createLinkedNote(context.selectedTaskId, context.plugin);
} else if (iconCode == IconCode.KEY) {
context.chosenBlockedId = context.chosenBlockedId === NoTaskId ? context.selectedTaskId : NoTaskId;
} else if (iconCode == IconCode.LOCK) {

View file

@ -1,7 +1,7 @@
<script lang="ts">
import {onMount, tick} from "svelte";
import {Component, MarkdownRenderer, Notice} from "obsidian";
import {LinkSuggest} from "../LinkSuggest";
import {LinkSuggest} from "../helpers/LinkSuggest";
import type {Context} from "../Context.svelte.js";
import {delink, getFromRelativePath, isLink} from "../LinkManager";
import {NoTaskId} from "../NodePositionsCalculator";

View file

@ -8,7 +8,7 @@
SUBTOOLBAR_PADDING
} from "../Constants";
import {quintOut} from 'svelte/easing';
import {slideCustom} from '../Custom';
import {slideCustom} from '../helpers/svelte';
import type {Context} from "../Context.svelte.js";
import {
IconCode, StatusCode, type TaskId, toIconCode

View file

@ -0,0 +1,56 @@
import {
AbstractInputSuggest,
App,
prepareFuzzySearch,
TFolder,
} from "obsidian";
export class FolderSuggest extends AbstractInputSuggest<TFolder> {
textInputEl: HTMLInputElement;
constructor(app: App, textInputEl: HTMLInputElement) {
// Cast the textarea to HTMLInputElement to satisfy TypeScript.
// This is a dirty hack, but runtime behavior is compatible
super(app, textInputEl as unknown as HTMLInputElement);
this.textInputEl = textInputEl;
}
getSuggestions(): TFolder[] {
const query = this.textInputEl.value;
// Perform the search
const search = prepareFuzzySearch(query);
return this.app.vault
.getAllFolders()
.filter((file) => search(file.path))
.sort((a, b) => {
const resA = search(a.path);
const resB = search(b.path);
return (resB?.score || 0) - (resA?.score || 0);
})
.slice(0, 10);
}
renderSuggestion(file: TFolder, el: HTMLElement) {
el.createEl("div", { text: file.path });
el.createEl("small", { text: file.path, cls: "text-muted" });
}
selectSuggestion(file: TFolder) {
const newLinkText = file.path;
this.textInputEl.value = newLinkText;
// Trigger Svelte update
this.textInputEl.dispatchEvent(new Event("input"));
// Reset cursor
const newCursor = newLinkText.length;
this.textInputEl.setSelectionRange(newCursor, newCursor);
this.textInputEl.focus();
this.textInputEl.blur();
this.close();
}
}

View file

@ -1,4 +1,4 @@
import { App, TFile, AbstractInputSuggest, prepareFuzzySearch } from "obsidian";
import { AbstractInputSuggest, App, prepareFuzzySearch, TFile } from "obsidian";
export class LinkSuggest extends AbstractInputSuggest<TFile> {
textInputEl: HTMLTextAreaElement;
@ -10,7 +10,7 @@ export class LinkSuggest extends AbstractInputSuggest<TFile> {
this.textInputEl = textInputEl;
}
getSuggestions(query: string): TFile[] {
getSuggestions(_query: string): TFile[] {
const cursor = this.textInputEl.selectionStart;
const text = this.textInputEl.value;
@ -31,9 +31,9 @@ export class LinkSuggest extends AbstractInputSuggest<TFile> {
// Perform the search
const search = prepareFuzzySearch(linkQuery);
const files = this.app.vault.getFiles();
return files
return this.app.vault
.getFiles()
.filter((file) => search(file.path) || search(file.basename))
.sort((a, b) => {
const resA = search(a.path);

View file

@ -1,18 +1,15 @@
import { addIcon, Plugin } from "obsidian";
import { TASKMAP_VIEW_TYPE, TaskmapView } from "./TaskmapView";
import {
DEFAULT_SETTINGS,
type TaskmapPluginSettings,
} from "./TaskmapPluginSettings";
import { DEFAULT_SETTINGS, type TaskmapSettings } from "./TaskmapSettings";
import { TaskmapSettingTab } from "./TaskmapSettingTab";
import { DEFAULT_DATA } from "./data/ProjectData.svelte";
import { LOGO_CONTENT, LOGO_NAME } from "./IconService";
import { FileWatcherWithCache } from "./FileWatcherWithCache";
import { LOGO_CONTENT, LOGO_NAME } from "./Constants";
export const FILE_EXTENSION = "taskmap";
export default class TaskmapPlugin extends Plugin {
settings: TaskmapPluginSettings;
settings: TaskmapSettings;
private readonly filewatcher = new FileWatcherWithCache();
async onload() {
@ -59,7 +56,7 @@ export default class TaskmapPlugin extends Plugin {
{},
DEFAULT_SETTINGS,
await this.loadData(),
) as TaskmapPluginSettings;
) as TaskmapSettings;
}
async saveSettings() {