From 5b0dddedc55cd11ba656b4ffab4c48af22761791 Mon Sep 17 00:00:00 2001 From: Ross Keenan Date: Sat, 5 Feb 2022 11:06:25 +0200 Subject: [PATCH] feat(Threading): :sparkles: Option to threadUnderCursor (Fix #276) --- main.js | 110 ++++++++++++++++--------- src/Commands/threading.ts | 132 ++++++++++++++++++++---------- src/Settings/ThreadingSettings.ts | 21 ++++- src/constants.ts | 1 + src/interfaces.ts | 1 + 5 files changed, 180 insertions(+), 85 deletions(-) diff --git a/main.js b/main.js index 9b8fcfd7..b735de20 100644 --- a/main.js +++ b/main.js @@ -3466,6 +3466,7 @@ const DEFAULT_SETTINGS = { threadIntoNewPane: false, threadingTemplate: "{{field}} of {{current}}", threadingDirTemplates: { up: "", same: "", down: "", next: "", prev: "" }, + threadUnderCursor: false, trailSeperator: "→", treatCurrNodeAsImpliedSibling: false, trimDendronNotes: false, @@ -24889,36 +24890,26 @@ async function jumpToFirstDir(plugin, dir) { await plugin.app.workspace.activeLeaf.openFile(toFile); } -async function thread(plugin, field) { - var _a; - const { app, settings } = plugin; - const { userHiers, writeBCsInline, threadingTemplate, dateFormat, threadingDirTemplates, threadIntoNewPane, } = settings; - const currFile = app.workspace.getActiveFile(); - if (!currFile) - return; - const newFileParent = app.fileManager.getNewFileParent(currFile.path); - const dir = getFieldInfo(userHiers, field).fieldDir; - const oppField = getOppFields(userHiers, field, dir)[0]; - let newBasename = threadingTemplate - ? threadingTemplate - .replace("{{current}}", currFile.basename) - .replace("{{field}}", field) - .replace("{{dir}}", dir) - //@ts-ignore - .replace("{{date}}", moment().format(dateFormat)) - : "Untitled"; - let i = 1; - while (app.metadataCache.getFirstLinkpathDest(newBasename, "")) { +const resolveThreadingNameTemplate = (template, currFile, field, dir, dateFormat) => template + ? template + .replace("{{current}}", currFile.basename) + .replace("{{field}}", field) + .replace("{{dir}}", dir) + //@ts-ignore + .replace("{{date}}", moment().format(dateFormat)) + : "Untitled"; +function makeFilenameUnique(app, filename) { + let i = 1, newName = filename; + while (app.metadataCache.getFirstLinkpathDest(newName, "")) { if (i === 1) - newBasename += ` ${i}`; + newName += ` ${i}`; else - newBasename = newBasename.slice(0, -2) + ` ${i}`; + newName = newName.slice(0, -2) + ` ${i}`; i++; } - const crumb = writeBCsInline - ? `${oppField}:: [[${currFile.basename}]]` - : `---\n${oppField}: ['${currFile.basename}']\n---`; - const templatePath = threadingDirTemplates[dir]; + return newName; +} +async function resolveThreadingContentTemplate(app, writeBCsInline, templatePath, oppField, currFile, crumb) { let newContent = crumb; if (templatePath) { const templateFile = app.metadataCache.getFirstLinkpathDest(templatePath, ""); @@ -24927,6 +24918,25 @@ async function thread(plugin, field) { ? `${oppField}:: [[${currFile.basename}]]` : `${oppField}: ['${currFile.basename}']`); } + return newContent; +} +async function thread(plugin, field) { + var _a; + const { app, settings } = plugin; + const { userHiers, threadingTemplate, dateFormat, threadIntoNewPane, threadingDirTemplates, threadUnderCursor, writeBCsInline, } = settings; + const currFile = app.workspace.getActiveFile(); + if (!currFile) + return; + const newFileParent = app.fileManager.getNewFileParent(currFile.path); + const dir = getFieldInfo(userHiers, field).fieldDir; + const oppField = getOppFields(userHiers, field, dir)[0]; + let newBasename = resolveThreadingNameTemplate(threadingTemplate, currFile, field, dir, dateFormat); + newBasename = makeFilenameUnique(app, newBasename); + const oppCrumb = writeBCsInline + ? `${oppField}:: [[${currFile.basename}]]` + : `---\n${oppField}: ['${currFile.basename}']\n---`; + const templatePath = threadingDirTemplates[dir]; + const newContent = await resolveThreadingContentTemplate(app, writeBCsInline, templatePath, oppField, currFile, oppCrumb); const newFile = await app.vault.create(obsidian.normalizePath(`${newFileParent.path}/${newBasename}.md`), newContent); if (!writeBCsInline) { const { api } = (_a = app.plugins.plugins.metaedit) !== null && _a !== void 0 ? _a : {}; @@ -24937,16 +24947,23 @@ async function thread(plugin, field) { await createOrUpdateYaml(field, newFile.basename, currFile, app.metadataCache.getFileCache(currFile).frontmatter, api); } else { - // TODO Check if this note already has this field - let content = await app.vault.read(currFile); - const splits = splitAtYaml(content); - content = - splits[0] + - (splits[0].length ? "\n" : "") + - `${field}:: [[${newFile.basename}]]` + - (splits[1].length ? "\n" : "") + - splits[1]; - await app.vault.modify(currFile, content); + const crumb = `${field}:: [[${newFile.basename}]]`; + const { editor } = app.workspace.activeLeaf.view; + if (threadUnderCursor || !editor) { + editor.replaceRange(crumb, editor.getCursor()); + } + else { + // TODO Check if this note already has this field + let content = await app.vault.read(currFile); + const splits = splitAtYaml(content); + content = + splits[0] + + (splits[0].length ? "\n" : "") + + crumb + + (splits[1].length ? "\n" : "") + + splits[1]; + await app.vault.modify(currFile, content); + } } const leaf = threadIntoNewPane ? app.workspace.splitActiveLeaf() @@ -39452,10 +39469,23 @@ function addThreadingSettings(plugin, cmdsDetails) { }); new obsidian.Setting(threadingDetails) .setName("Open new threads in new pane or current pane") - .addToggle((tog) => tog.onChange(async (value) => { - settings.threadIntoNewPane = value; - await plugin.saveSettings(); - })); + .addToggle((tog) => { + tog.setValue(settings.threadIntoNewPane); + tog.onChange(async (value) => { + settings.threadIntoNewPane = value; + await plugin.saveSettings(); + }); + }); + new obsidian.Setting(threadingDetails) + .setName("Thread under Cursor") + .setDesc(fragWithHTML("If the setting Write Breadcrumbs Inline is enabled, where should the new Breadcrumb be added to the current note? ✅ = Under the cursor, ❌ = At the top of the note (under the yaml, if applicable)")) + .addToggle((tog) => { + tog.setValue(settings.threadUnderCursor); + tog.onChange(async (value) => { + settings.threadUnderCursor = value; + await plugin.saveSettings(); + }); + }); new obsidian.Setting(threadingDetails) .setName("New Note Name Template") .setDesc(fragWithHTML(`When threading into a new note, choose the template for the new note name.
diff --git a/src/Commands/threading.ts b/src/Commands/threading.ts index c3012a84..246b58d4 100644 --- a/src/Commands/threading.ts +++ b/src/Commands/threading.ts @@ -1,29 +1,18 @@ -import { normalizePath, Notice } from "obsidian"; +import { App, normalizePath, Notice, TFile } from "obsidian"; +import type { Directions } from "../interfaces"; import type BCPlugin from "../main"; import { getFieldInfo, getOppFields } from "../Utils/HierUtils"; import { createOrUpdateYaml, splitAtYaml } from "../Utils/ObsidianUtils"; -export async function thread(plugin: BCPlugin, field: string) { - const { app, settings } = plugin; - const { - userHiers, - writeBCsInline, - threadingTemplate, - dateFormat, - threadingDirTemplates, - threadIntoNewPane, - } = settings; - - const currFile = app.workspace.getActiveFile(); - if (!currFile) return; - - const newFileParent = app.fileManager.getNewFileParent(currFile.path); - - const dir = getFieldInfo(userHiers, field).fieldDir; - const oppField = getOppFields(userHiers, field, dir)[0]; - - let newBasename = threadingTemplate - ? threadingTemplate +const resolveThreadingNameTemplate = ( + template: string, + currFile: TFile, + field: string, + dir: Directions, + dateFormat: string +) => + template + ? template .replace("{{current}}", currFile.basename) .replace("{{field}}", field) .replace("{{dir}}", dir) @@ -31,19 +20,27 @@ export async function thread(plugin: BCPlugin, field: string) { .replace("{{date}}", moment().format(dateFormat)) : "Untitled"; - let i = 1; - while (app.metadataCache.getFirstLinkpathDest(newBasename, "")) { - if (i === 1) newBasename += ` ${i}`; - else newBasename = newBasename.slice(0, -2) + ` ${i}`; +function makeFilenameUnique(app: App, filename: string) { + let i = 1, + newName = filename; + while (app.metadataCache.getFirstLinkpathDest(newName, "")) { + if (i === 1) newName += ` ${i}`; + else newName = newName.slice(0, -2) + ` ${i}`; i++; } + return newName; +} - const crumb = writeBCsInline - ? `${oppField}:: [[${currFile.basename}]]` - : `---\n${oppField}: ['${currFile.basename}']\n---`; - - const templatePath = threadingDirTemplates[dir]; +async function resolveThreadingContentTemplate( + app: App, + writeBCsInline: boolean, + templatePath: string, + oppField: string, + currFile: TFile, + crumb: string +) { let newContent = crumb; + if (templatePath) { const templateFile = app.metadataCache.getFirstLinkpathDest( templatePath, @@ -58,6 +55,51 @@ export async function thread(plugin: BCPlugin, field: string) { : `${oppField}: ['${currFile.basename}']` ); } + return newContent; +} + +export async function thread(plugin: BCPlugin, field: string) { + const { app, settings } = plugin; + const { + userHiers, + threadingTemplate, + dateFormat, + threadIntoNewPane, + threadingDirTemplates, + threadUnderCursor, + writeBCsInline, + } = settings; + + const currFile = app.workspace.getActiveFile(); + if (!currFile) return; + + const newFileParent = app.fileManager.getNewFileParent(currFile.path); + + const dir = getFieldInfo(userHiers, field).fieldDir; + const oppField = getOppFields(userHiers, field, dir)[0]; + + let newBasename = resolveThreadingNameTemplate( + threadingTemplate, + currFile, + field, + dir, + dateFormat + ); + newBasename = makeFilenameUnique(app, newBasename); + + const oppCrumb = writeBCsInline + ? `${oppField}:: [[${currFile.basename}]]` + : `---\n${oppField}: ['${currFile.basename}']\n---`; + + const templatePath = threadingDirTemplates[dir]; + const newContent = await resolveThreadingContentTemplate( + app, + writeBCsInline, + templatePath, + oppField, + currFile, + oppCrumb + ); const newFile = await app.vault.create( normalizePath(`${newFileParent.path}/${newBasename}.md`), @@ -80,17 +122,23 @@ export async function thread(plugin: BCPlugin, field: string) { api ); } else { - // TODO Check if this note already has this field - let content = await app.vault.read(currFile); - const splits = splitAtYaml(content); - content = - splits[0] + - (splits[0].length ? "\n" : "") + - `${field}:: [[${newFile.basename}]]` + - (splits[1].length ? "\n" : "") + - splits[1]; - - await app.vault.modify(currFile, content); + const crumb = `${field}:: [[${newFile.basename}]]`; + const { editor } = app.workspace.activeLeaf.view; + if (threadUnderCursor || !editor) { + editor.replaceRange(crumb, editor.getCursor()); + } else { + // TODO Check if this note already has this field + let content = await app.vault.read(currFile); + const splits = splitAtYaml(content); + content = + splits[0] + + (splits[0].length ? "\n" : "") + + crumb + + (splits[1].length ? "\n" : "") + + splits[1]; + + await app.vault.modify(currFile, content); + } } const leaf = threadIntoNewPane diff --git a/src/Settings/ThreadingSettings.ts b/src/Settings/ThreadingSettings.ts index 07ad48dd..2d90e0d1 100644 --- a/src/Settings/ThreadingSettings.ts +++ b/src/Settings/ThreadingSettings.ts @@ -20,12 +20,27 @@ export function addThreadingSettings( }); new Setting(threadingDetails) .setName("Open new threads in new pane or current pane") - .addToggle((tog) => + .addToggle((tog) => { + tog.setValue(settings.threadIntoNewPane); tog.onChange(async (value) => { settings.threadIntoNewPane = value; await plugin.saveSettings(); - }) - ); + }); + }); + new Setting(threadingDetails) + .setName("Thread under Cursor") + .setDesc( + fragWithHTML( + "If the setting Write Breadcrumbs Inline is enabled, where should the new Breadcrumb be added to the current note? ✅ = Under the cursor, ❌ = At the top of the note (under the yaml, if applicable)" + ) + ) + .addToggle((tog) => { + tog.setValue(settings.threadUnderCursor); + tog.onChange(async (value) => { + settings.threadUnderCursor = value; + await plugin.saveSettings(); + }); + }); new Setting(threadingDetails) .setName("New Note Name Template") diff --git a/src/constants.ts b/src/constants.ts index 05fe0a75..640af0cd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -372,6 +372,7 @@ export const DEFAULT_SETTINGS: BCSettings = { threadIntoNewPane: false, threadingTemplate: "{{field}} of {{current}}", threadingDirTemplates: { up: "", same: "", down: "", next: "", prev: "" }, + threadUnderCursor: false, trailSeperator: "→", treatCurrNodeAsImpliedSibling: false, trimDendronNotes: false, diff --git a/src/interfaces.ts b/src/interfaces.ts index 095857cf..765aa28e 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -105,6 +105,7 @@ export interface BCSettings { threadIntoNewPane: boolean; threadingTemplate: string; threadingDirTemplates: { [dir in Directions]: string }; + threadUnderCursor: boolean; trailSeperator: string; treatCurrNodeAsImpliedSibling: boolean; trimDendronNotes: boolean;