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;