From 652da4ff89050d35bbbd5b6a306a31ef4d3e0a24 Mon Sep 17 00:00:00 2001 From: Ross Keenan Date: Wed, 24 Nov 2021 10:31:06 +0200 Subject: [PATCH] feat: :sparkles: Field Suggestor! Type "BC-" at the start of a line to trigger hideTrailField settings has been removed in favour of a standard 'BC-hide-trail' --- main.js | 111 +++++++++++++++++++++++++++++++---- src/BreadcrumbsSettingTab.ts | 36 ++++++++---- src/FieldSuggestor.ts | 73 +++++++++++++++++++++++ src/constants.ts | 32 +++++++++- src/interfaces.ts | 9 +-- src/main.ts | 6 +- 6 files changed, 234 insertions(+), 33 deletions(-) create mode 100644 src/FieldSuggestor.ts diff --git a/main.js b/main.js index 5ca6968c..f2087446 100644 --- a/main.js +++ b/main.js @@ -24798,6 +24798,33 @@ const blankRealNImplied = () => { prev: { reals: [], implieds: [] }, }; }; +const BC_FIELDS = [ + { + field: "BC-folder-note", + desc: "Set this note as a Breadcrumbs folder-note. All other notes in this folder will point up to this note", + after: ": true", + }, + { + field: "BC-folder-note-up", + desc: "Manually choose the up field for this folder-note to use", + after: ": ", + }, + { + field: "BC-tag-note", + desc: "Set this note as a Breadcrumbs tag-note. All other notes with this tag will point up to this note", + after: ": true", + }, + { + field: "BC-tag-note-up", + desc: "Manually choose the up field for this tag-note to use", + after: ": ", + }, + { + field: "BC-hide-trail", + desc: "Don't show the trail in this note", + after: ": true", + }, +]; const DEFAULT_SETTINGS = { aliasesInIndex: false, alphaSortAsc: true, @@ -24807,6 +24834,7 @@ const DEFAULT_SETTINGS = { defaultView: true, dvWaitTime: 5000, dotsColour: "#000000", + fieldSuggestor: true, filterImpliedSiblingsOfDifferentTypes: false, limitWriteBCCheckboxStates: {}, indexNotes: [""], @@ -24827,7 +24855,6 @@ const DEFAULT_SETTINGS = { showGrid: true, showPrevNext: true, limitTrailCheckboxStates: {}, - hideTrailField: "hide-trail", gridDots: false, gridHeatmap: false, heatmapColour: getComputedStyle(document.body).getPropertyValue("--text-accent"), @@ -25731,6 +25758,13 @@ class BCSettingTab extends obsidian.PluginSettingTab { await plugin.saveSettings(); }; }); + new obsidian.Setting(generalDetails) + .setName("Enable Field Suggestor") + .setDesc('Alot of Breadcrumbs features require a metadata (or inline Dataview) field to work. For example, `BC-folder-note`. The Field Suggestor will show an autocomplete menu with all available Breadcrumbs field options when the content you type matches the regex /^BC-.*$/. Basically, just type "BC-" at the start of a line to trigger it.') + .addToggle((toggle) => toggle.setValue(settings.fieldSuggestor).onChange(async (value) => { + settings.fieldSuggestor = value; + await plugin.saveSettings(); + })); new obsidian.Setting(generalDetails) .setName("Refresh Index on Note Change") .setDesc("Refresh the Breadcrumbs index data everytime you change notes. This is how Breadcrumbs used to work, making it responsive to changes immediately after changing notes. However, this can be very slow on large vaults, so it is off by default.") @@ -25941,16 +25975,18 @@ class BCSettingTab extends obsidian.PluginSettingTab { }); } drawLimitTrailCheckboxes(checkboxDiv); - new obsidian.Setting(trailDetails) - .setName("Field name to hide trail") - .setDesc("A note-specific toggle to hide the Trail View. By default, it is `hide-trail`. So, to hide the trail on a specific note, add the field to that note's yaml, like so: `hide-trail: {{anything}}`.") - .addText((text) => { - text.setValue(settings.hideTrailField); - text.inputEl.onblur = async () => { - settings.hideTrailField = text.getValue(); - await plugin.saveSettings(); - }; - }); + // new Setting(trailDetails) + // .setName("Field name to hide trail") + // .setDesc( + // "A note-specific toggle to hide the Trail View. By default, it is `hide-trail`. So, to hide the trail on a specific note, add the field to that note's yaml, like so: `hide-trail: {{anything}}`." + // ) + // .addText((text) => { + // text.setValue(settings.hideTrailField); + // text.inputEl.onblur = async () => { + // settings.hideTrailField = text.getValue(); + // await plugin.saveSettings(); + // }; + // }); new obsidian.Setting(trailDetails) .setName("Views to show") .setDesc("Choose which of the views to show at the top of the note.\nTrail, Grid, and/or the Next-Previous view.") @@ -26251,6 +26287,54 @@ class BCSettingTab extends obsidian.PluginSettingTab { } } +class FieldSuggestor extends obsidian.EditorSuggest { + constructor(plugin) { + super(plugin.app); + this.getSuggestions = (context) => { + const { query } = context; + return BC_FIELDS.map((sug) => sug.field).filter((sug) => sug.includes(query)); + }; + this.plugin = plugin; + } + onTrigger(cursor, editor, _) { + var _a; + if (this.plugin.settings.fieldSuggestor) { + const sub = editor.getLine(cursor.line).substring(0, cursor.ch); + const match = (_a = sub.match(/^BC-(.*)$/)) === null || _a === void 0 ? void 0 : _a[1]; + if (match !== undefined) { + return { + end: cursor, + start: { + ch: sub.lastIndexOf(match), + line: cursor.line, + }, + query: match, + }; + } + } + return null; + } + renderSuggestion(suggestion, el) { + var _a; + el.createDiv({ + text: suggestion.replace("BC-", ""), + cls: "BC-suggester-container", + attr: { + "aria-label": (_a = BC_FIELDS.find((f) => f.field === suggestion)) === null || _a === void 0 ? void 0 : _a.desc, + "aria-label-position": "right", + }, + }); + } + selectSuggestion(suggestion) { + var _a; + const { context } = this; + if (context) { + const replacement = `${suggestion}${(_a = BC_FIELDS.find((f) => f.field === suggestion)) === null || _a === void 0 ? void 0 : _a.after}`; + context.editor.replaceRange(replacement, { ch: 0, line: context.start.line }, context.end); + } + } +} + var d3Array = createCommonjsModule(function (module, exports) { // https://d3js.org/d3-array/ v2.12.1 Copyright 2021 Mike Bostock (function (global, factory) { @@ -49435,6 +49519,7 @@ class BCPlugin extends obsidian.Plugin { for (const view of VIEWS) { this.registerView(view.type, (leaf) => new view.constructor(leaf, this)); } + this.registerEditorSuggest(new FieldSuggestor(this)); this.app.workspace.onLayoutReady(async () => { var _a; if (this.app.plugins.enabledPlugins.has("dataview")) { @@ -50171,7 +50256,7 @@ class BCPlugin extends obsidian.Plugin { async drawTrail() { var _a, _b, _c; const { settings } = this; - const { showBCs, hideTrailField, noPathMessage, respectReadableLineLength, showTrail, showGrid, showPrevNext, } = settings; + const { showBCs, noPathMessage, respectReadableLineLength, showTrail, showGrid, showPrevNext, } = settings; debugGroupStart(settings, "debugMode", "Draw Trail"); const activeMDView = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView); if (!showBCs || !activeMDView) { @@ -50186,7 +50271,7 @@ class BCPlugin extends obsidian.Plugin { } const { file } = activeMDView; const { frontmatter } = (_a = this.app.metadataCache.getFileCache(file)) !== null && _a !== void 0 ? _a : {}; - if ((frontmatter === null || frontmatter === void 0 ? void 0 : frontmatter[hideTrailField]) || (frontmatter === null || frontmatter === void 0 ? void 0 : frontmatter["kanban-plugin"])) { + if ((frontmatter === null || frontmatter === void 0 ? void 0 : frontmatter["BC-hide-trail"]) || (frontmatter === null || frontmatter === void 0 ? void 0 : frontmatter["kanban-plugin"])) { debugGroupEnd(settings, "debugMode"); return; } diff --git a/src/BreadcrumbsSettingTab.ts b/src/BreadcrumbsSettingTab.ts index 0dc0dc8f..f286c846 100644 --- a/src/BreadcrumbsSettingTab.ts +++ b/src/BreadcrumbsSettingTab.ts @@ -123,6 +123,18 @@ export class BCSettingTab extends PluginSettingTab { }; }); + new Setting(generalDetails) + .setName("Enable Field Suggestor") + .setDesc( + 'Alot of Breadcrumbs features require a metadata (or inline Dataview) field to work. For example, `BC-folder-note`. The Field Suggestor will show an autocomplete menu with all available Breadcrumbs field options when the content you type matches the regex /^BC-.*$/. Basically, just type "BC-" at the start of a line to trigger it.' + ) + .addToggle((toggle) => + toggle.setValue(settings.fieldSuggestor).onChange(async (value) => { + settings.fieldSuggestor = value; + await plugin.saveSettings(); + }) + ); + new Setting(generalDetails) .setName("Refresh Index on Note Change") .setDesc( @@ -413,18 +425,18 @@ export class BCSettingTab extends PluginSettingTab { drawLimitTrailCheckboxes(checkboxDiv); - new Setting(trailDetails) - .setName("Field name to hide trail") - .setDesc( - "A note-specific toggle to hide the Trail View. By default, it is `hide-trail`. So, to hide the trail on a specific note, add the field to that note's yaml, like so: `hide-trail: {{anything}}`." - ) - .addText((text) => { - text.setValue(settings.hideTrailField); - text.inputEl.onblur = async () => { - settings.hideTrailField = text.getValue(); - await plugin.saveSettings(); - }; - }); + // new Setting(trailDetails) + // .setName("Field name to hide trail") + // .setDesc( + // "A note-specific toggle to hide the Trail View. By default, it is `hide-trail`. So, to hide the trail on a specific note, add the field to that note's yaml, like so: `hide-trail: {{anything}}`." + // ) + // .addText((text) => { + // text.setValue(settings.hideTrailField); + // text.inputEl.onblur = async () => { + // settings.hideTrailField = text.getValue(); + // await plugin.saveSettings(); + // }; + // }); new Setting(trailDetails) .setName("Views to show") diff --git a/src/FieldSuggestor.ts b/src/FieldSuggestor.ts new file mode 100644 index 00000000..e989b428 --- /dev/null +++ b/src/FieldSuggestor.ts @@ -0,0 +1,73 @@ +import { + Editor, + EditorPosition, + EditorSuggest, + EditorSuggestContext, + EditorSuggestTriggerInfo, + TFile, +} from "obsidian"; +import { BC_FIELDS } from "src/constants"; +import type BCPlugin from "src/main"; + +export class FieldSuggestor extends EditorSuggest { + plugin: BCPlugin; + + constructor(plugin: BCPlugin) { + super(plugin.app); + this.plugin = plugin; + } + + onTrigger( + cursor: EditorPosition, + editor: Editor, + _: TFile + ): EditorSuggestTriggerInfo | null { + if (this.plugin.settings.fieldSuggestor) { + const sub = editor.getLine(cursor.line).substring(0, cursor.ch); + const match = sub.match(/^BC-(.*)$/)?.[1]; + if (match !== undefined) { + return { + end: cursor, + start: { + ch: sub.lastIndexOf(match), + line: cursor.line, + }, + query: match, + }; + } + } + return null; + } + + getSuggestions = (context: EditorSuggestContext) => { + const { query } = context; + return BC_FIELDS.map((sug) => sug.field).filter((sug) => + sug.includes(query) + ); + }; + + renderSuggestion(suggestion: string, el: HTMLElement): void { + el.createDiv({ + text: suggestion.replace("BC-", ""), + cls: "BC-suggester-container", + attr: { + "aria-label": BC_FIELDS.find((f) => f.field === suggestion)?.desc, + "aria-label-position": "right", + }, + }); + } + + selectSuggestion(suggestion: string): void { + const { context } = this; + if (context) { + const replacement = `${suggestion}${ + BC_FIELDS.find((f) => f.field === suggestion)?.after + }`; + context.editor.replaceRange( + replacement, + { ch: 0, line: context.start.line }, + context.end + ); + } + } +} diff --git a/src/constants.ts b/src/constants.ts index 5c29fd9e..a96208a1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -49,7 +49,7 @@ export const VISTYPES: visTypes[] = [ "Radial Tree", ]; -export const DIRECTIONS: Directions[] = ["up", "same", "down", "next", "prev"]; +export const DIRECTIONS = ["up", "same", "down", "next", "prev"] as const; export const ARROW_DIRECTIONS: { [dir in Directions]: string } = { up: "↑", same: "↔", @@ -84,6 +84,34 @@ export const blankRealNImplied = () => { }; }; +export const BC_FIELDS = [ + { + field: "BC-folder-note", + desc: "Set this note as a Breadcrumbs folder-note. All other notes in this folder will point up to this note", + after: ": true", + }, + { + field: "BC-folder-note-up", + desc: "Manually choose the up field for this folder-note to use", + after: ": ", + }, + { + field: "BC-tag-note", + desc: "Set this note as a Breadcrumbs tag-note. All other notes with this tag will point up to this note", + after: ": true", + }, + { + field: "BC-tag-note-up", + desc: "Manually choose the up field for this tag-note to use", + after: ": ", + }, + { + field: "BC-hide-trail", + desc: "Don't show the trail in this note", + after: ": true", + }, +]; + export const DEFAULT_SETTINGS: BCSettings = { aliasesInIndex: false, alphaSortAsc: true, @@ -93,6 +121,7 @@ export const DEFAULT_SETTINGS: BCSettings = { defaultView: true, dvWaitTime: 5000, dotsColour: "#000000", + fieldSuggestor: true, filterImpliedSiblingsOfDifferentTypes: false, limitWriteBCCheckboxStates: {}, indexNotes: [""], @@ -113,7 +142,6 @@ export const DEFAULT_SETTINGS: BCSettings = { showGrid: true, showPrevNext: true, limitTrailCheckboxStates: {}, - hideTrailField: "hide-trail", gridDots: false, gridHeatmap: false, heatmapColour: getComputedStyle(document.body).getPropertyValue( diff --git a/src/interfaces.ts b/src/interfaces.ts index 80f3a1e4..0686f564 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,8 +1,9 @@ import type Graph from "graphology"; import type { Constructor, FrontMatterCache, Pos, TFile } from "obsidian"; +import type { DIRECTIONS } from "src/constants"; import type DucksView from "src/DucksView"; -import type StatsView from "src/StatsView"; import type MatrixView from "src/MatrixView"; +import type StatsView from "src/StatsView"; export interface BCSettings { aliasesInIndex: boolean; @@ -13,11 +14,11 @@ export interface BCSettings { debugMode: boolean; defaultView: boolean; dotsColour: string; + fieldSuggestor: boolean; filterImpliedSiblingsOfDifferentTypes: boolean; gridDots: boolean; gridHeatmap: boolean; heatmapColour: string; - hideTrailField: string; hierarchyNotes: string[]; HNUpField: string; indexNotes: string[]; @@ -73,12 +74,12 @@ export interface dvFrontmatterCache { | TFile; } -export type Directions = "up" | "same" | "down" | "next" | "prev"; +export type Directions = typeof DIRECTIONS[number]; export type UserHier = { [dir in Directions]: string[]; }; -export type MyView = MatrixView | StatsView | DucksView; +export type MyView = MatrixView | DucksView | StatsView; export type ViewInfo = { plain: string; type: string; diff --git a/src/main.ts b/src/main.ts index 26ba9ffd..9f665844 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,6 +29,7 @@ import { TRAIL_ICON_SVG, VIEWS, } from "src/constants"; +import { FieldSuggestor } from "src/FieldSuggestor"; import type { BCSettings, Directions, @@ -156,6 +157,8 @@ export default class BCPlugin extends Plugin { ); } + this.registerEditorSuggest(new FieldSuggestor(this)); + this.app.workspace.onLayoutReady(async () => { if (this.app.plugins.enabledPlugins.has("dataview")) { const api = this.app.plugins.plugins.dataview?.api; @@ -1155,7 +1158,6 @@ export default class BCPlugin extends Plugin { const { settings } = this; const { showBCs, - hideTrailField, noPathMessage, respectReadableLineLength, showTrail, @@ -1181,7 +1183,7 @@ export default class BCPlugin extends Plugin { const { file } = activeMDView; const { frontmatter } = this.app.metadataCache.getFileCache(file) ?? {}; - if (frontmatter?.[hideTrailField] || frontmatter?.["kanban-plugin"]) { + if (frontmatter?.["BC-hide-trail"] || frontmatter?.["kanban-plugin"]) { debugGroupEnd(settings, "debugMode"); return; }