diff --git a/main.js b/main.js index 4b84957a..bf99312e 100644 --- a/main.js +++ b/main.js @@ -20801,6 +20801,26 @@ function iterateAllGs(currGraphs, cb) { } } } +/** + * Get all the fields in `dir`. + * Returns all fields if `dir === 'all'` + * @param {userHierarchy[]} userHierarchies + * @param {Directions|"all"} dir + */ +function getFields(userHierarchies, dir = "all") { + const fields = []; + userHierarchies.forEach((hier) => { + if (dir === "all") { + DIRECTIONS.forEach((eachDir) => { + fields.push(...hier[eachDir]); + }); + } + else { + fields.push(...hier[dir]); + } + }); + return fields; +} function getAllFieldGs(fields, currGraphs) { const fieldGs = []; iterateAllGs(currGraphs, (g, dir, fieldName) => { @@ -20810,9 +20830,6 @@ function getAllFieldGs(fields, currGraphs) { return fieldGs; } const hierToStr = (hier) => DIRECTIONS.map((dir) => `${ARROW_DIRECTIONS[dir]}: ${hier[dir].join(", ")}`).join("\n"); -function removeDuplicates(arr) { - return [...new Set(arr)]; -} const getOppDir = (dir) => { switch (dir) { case "up": @@ -35249,78 +35266,6 @@ class BCPlugin extends obsidian.Plugin { this.registerInterval(this.refreshIntervalID); } }; - this.hierarchyNoteAdjList = (str) => { - let noteContent = str; - const { settings } = this; - const yamlRegex = new RegExp(/^---.*/); - const hasYaml = yamlRegex.test(noteContent); - if (hasYaml) { - noteContent = noteContent.split("---").slice(2).join("---"); - } - const layers = noteContent.split("\n").filter((line) => line); - const getDepth = (line) => line.split(/[-*+]/)[0].length; - const depths = layers.map(getDepth); - const differences = []; - depths.forEach((dep, i) => { - if (i >= 1) { - differences.push(dep - depths[i - 1]); - } - }); - debug(settings, { differences }); - const posDifferences = differences - .filter((diff) => diff !== 0) - .map(Math.abs); - const lcm = Math.min(...posDifferences); - if (!posDifferences.every((diff) => diff % lcm === 0)) { - new obsidian.Notice("Please make sure the indentation is consistent in your hierarchy note."); - return []; - } - const difference = lcm; - const adjItems = []; - // TODO Allow user to pick the field name they want - const lineRegex = new RegExp(/\s*[-*+] \[\[(.*)\]\]/); - const pushNoteUp = (hier, currNote, currDepth) => { - const copy = [...hier]; - const noteUp = copy - .reverse() - .findIndex((adjItem) => adjItem.depth === currDepth - difference); - if (noteUp > -1) - hier[noteUp].children.push(currNote); - }; - let lineNo = 0; - while (lineNo < layers.length) { - const currLine = layers[lineNo]; - const currNote = currLine.match(lineRegex)[1]; - const currDepth = getDepth(currLine); - adjItems[lineNo] = { note: currNote, depth: currDepth, children: [] }; - if (lineNo !== layers.length - 1) { - const nextLine = layers[lineNo + 1]; - const nextNote = nextLine.match(lineRegex)[1]; - const nextDepth = getDepth(nextLine); - if (nextDepth > currDepth) { - adjItems[lineNo].children.push(nextNote); - pushNoteUp(adjItems, currNote, currDepth); - } - else if (currDepth === 0) - return; - else { - pushNoteUp(adjItems, currNote, currDepth); - } - } - else { - const prevLine = layers[lineNo - 1]; - const prevDepth = getDepth(prevLine); - if (prevDepth >= currDepth) { - pushNoteUp(adjItems, currNote, currDepth); - } - } - lineNo++; - } - adjItems.forEach((item) => { - item.children = removeDuplicates(item.children); - }); - return adjItems; - }; } async refreshIndex() { if (!this.activeLeafChange) @@ -35395,7 +35340,7 @@ class BCPlugin extends obsidian.Plugin { this.addCommand({ id: "open-vis-modal", name: "Open Visualisation Modal", - callback: async () => { + callback: () => { new VisModal(this.app, this).open(); }, }); @@ -35453,6 +35398,54 @@ class BCPlugin extends obsidian.Plugin { } return null; } + async getHierarchyNoteItems(file) { + const { listItems } = this.app.metadataCache.getFileCache(file); + if (!listItems) + return []; + const content = await this.app.vault.cachedRead(file); + const lines = content.split("\n"); + const hierarchyNoteItems = []; + const afterBulletReg = new RegExp(/\s*[+*-]\s(.*$)/); + const dropWikiLinksReg = new RegExp(/\[\[(.*?)\]\]/); + const fieldNameReg = new RegExp(/(.*?)\[\[.*?\]\]/); + const problemFields = []; + const upFields = getFields(this.settings.userHierarchies, "up"); + for (const item of listItems) { + const currItem = lines[item.position.start.line]; + const afterBulletCurr = afterBulletReg.exec(currItem)[1]; + const dropWikiCurr = dropWikiLinksReg.exec(afterBulletCurr)[1]; + let fieldNameCurr = fieldNameReg.exec(afterBulletCurr)[1].trim() || null; + // Ensure fieldName is one of the existing up fields. `null` if not + if (fieldNameCurr !== null && !upFields.includes(fieldNameCurr)) { + problemFields.push(fieldNameCurr); + fieldNameCurr = null; + } + const { parent } = item; + if (parent >= 0) { + const parentNote = lines[parent]; + const afterBulletParent = afterBulletReg.exec(parentNote)[1]; + const dropWikiParent = dropWikiLinksReg.exec(afterBulletParent)[1]; + hierarchyNoteItems.push({ + currNote: dropWikiCurr, + parentNote: dropWikiParent, + fieldName: fieldNameCurr, + }); + } + else { + hierarchyNoteItems.push({ + currNote: dropWikiCurr, + parentNote: null, + fieldName: fieldNameCurr, + }); + } + } + if (problemFields.length > 0) { + const msg = `'${problemFields.join(", ")}' is/are not in any of your hierarchies, but are being used in: '${file.basename}'`; + new obsidian.Notice(msg); + console.log(msg, { problemFields }); + } + return hierarchyNoteItems; + } // SECTION OneSource populateGraph(g, currFileName, fieldValues, dir, fieldName) { //@ts-ignore @@ -35466,6 +35459,25 @@ class BCPlugin extends obsidian.Plugin { addEdgeIfNot(g, currFileName, value, { dir, fieldName }); }); } + populateMain(main, currFileName, dir, fieldName, targets, neighbours, neighbourObjArr) { + addNodeIfNot(main, currFileName, { + dir, + fieldName, + order: neighbours.order, + }); + targets.forEach((target) => { + var _a, _b; + addNodeIfNot(main, target, { + dir, + fieldName, + order: (_b = (_a = neighbourObjArr.find((neighbour) => (neighbour.current.basename || neighbour.current.name) === target)) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : 9999, + }); + addEdgeIfNot(main, currFileName, target, { + dir, + fieldName, + }); + }); + } async getCSVRows() { const { CSVPaths } = this.settings; const CSVRows = []; @@ -35514,21 +35526,17 @@ class BCPlugin extends obsidian.Plugin { } const neighbourObjArr = await getNeighbourObjArr(this, fileFrontmatterArr); debugGroupStart(settings, "debugMode", "Hierarchy Note Adjacency List"); - let hierarchyNotesArr = []; + const hierarchyNotesArr = []; if (settings.hierarchyNotes[0] !== "") { - const contentArr = []; - settings.hierarchyNotes.forEach(async (note) => { + for (const note of settings.hierarchyNotes) { const file = this.app.metadataCache.getFirstLinkpathDest(note, ""); if (file) { - const content = await this.app.vault.cachedRead(file); - contentArr.push(content); + hierarchyNotesArr.push(...(await this.getHierarchyNoteItems(file))); } else { - new obsidian.Notice(`${note} is no longer in your vault. The Hierarchy note should still work, but it is best to remove ${note} from your list of hierarchy notes in Breadcrumbs settings.`); + new obsidian.Notice(`${note} is no longer in your vault. It is best to remove it in Breadcrumbs settings.`); } - }); - await Promise.all(contentArr); - hierarchyNotesArr = contentArr.map(this.hierarchyNoteAdjList).flat(); + } debug(settings, { hierarchyNotesArr }); } debugGroupEnd(settings, "debugMode"); @@ -35562,24 +35570,7 @@ class BCPlugin extends obsidian.Plugin { const g = graphs.hierGs[i][dir][fieldName]; const targets = hier[dir][fieldName]; this.populateGraph(g, currFileName, targets, dir, fieldName); - addNodeIfNot(graphs.main, currFileName, { - dir, - fieldName, - order: neighbours.order, - }); - targets.forEach((target) => { - var _a, _b; - addNodeIfNot(graphs.main, target, { - dir, - fieldName, - order: (_b = (_a = neighbourObjArr.find((neighbour) => (neighbour.current.basename || neighbour.current.name) === - target)) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : 9999, - }); - addEdgeIfNot(graphs.main, currFileName, target, { - dir, - fieldName, - }); - }); + this.populateMain(graphs.main, currFileName, dir, fieldName, targets, neighbours, neighbourObjArr); if (useCSV) { this.addCSVCrumbs(g, CSVRows, dir, fieldName); this.addCSVCrumbs(graphs.main, CSVRows, dir, fieldName); @@ -35589,33 +35580,34 @@ class BCPlugin extends obsidian.Plugin { }); }); if (hierarchyNotesArr.length) { - const { hierarchyNoteUpFieldName, hierarchyNoteDownFieldName } = settings; - if (hierarchyNoteUpFieldName !== "") { - const gUp = graphs.hierGs.find((hierG) => hierG.up[hierarchyNoteUpFieldName]).up[hierarchyNoteUpFieldName]; - hierarchyNotesArr.forEach((adjListItem) => { - adjListItem.children.forEach((child) => { - //@ts-ignore - addNodeIfNot(gUp, adjListItem.note, { dir: "up" }); - gUp.addEdge(child, adjListItem.note, { - dir: "up", - fieldName: hierarchyNoteUpFieldName, - }); + const { hierarchyNoteUpFieldName } = settings; + const { main } = graphs; + const upFields = getFields(userHierarchies, "up"); + hierarchyNotesArr.forEach((hnItem) => { + var _a, _b; + if (hnItem.parentNote === null) + return; + const upField = (_a = hnItem.fieldName) !== null && _a !== void 0 ? _a : (hierarchyNoteUpFieldName || upFields[0]); + const downField = (_b = getOppFields(userHierarchies, upField)[0]) !== null && _b !== void 0 ? _b : `${upField}`; + const gUp = graphs.hierGs.find((hierG) => hierG.up[upField]).up[upField]; + const gDown = graphs.hierGs.find((hierG) => hierG.down[downField]).down[downField]; + [gUp, main].forEach((g) => { + addNodeIfNot(g, hnItem.currNote); + addNodeIfNot(g, hnItem.parentNote); + addEdgeIfNot(g, hnItem.currNote, hnItem.parentNote, { + dir: "up", + fieldName: upField, }); }); - } - if (hierarchyNoteDownFieldName !== "") { - const gDown = graphs.hierGs.find((hierG) => hierG.down[hierarchyNoteDownFieldName]).down[hierarchyNoteDownFieldName]; - hierarchyNotesArr.forEach((adjListItem) => { - adjListItem.children.forEach((child) => { - //@ts-ignore - addNodeIfNot(gDown, adjListItem.note, { dir: "down" }); - gDown.addEdge(adjListItem.note, child, { - dir: "down", - fieldName: hierarchyNoteDownFieldName, - }); + [gDown, main].forEach((g) => { + addNodeIfNot(g, hnItem.parentNote); + addNodeIfNot(g, hnItem.currNote); + addEdgeIfNot(g, hnItem.parentNote, hnItem.currNote, { + dir: "down", + fieldName: downField, }); }); - } + }); } DIRECTIONS.forEach((dir) => { const allXGs = getAllGsInDir(graphs.hierGs, dir); diff --git a/src/interfaces.ts b/src/interfaces.ts index 3ba14c57..05bd1879 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -94,6 +94,11 @@ export interface ParentObj { current: TFile; parents: string[]; } +export interface HierarchyNoteItem { + parentNote: string; + fieldName: string; + currNote: string; +} export interface fileFrontmatter { file: TFile; diff --git a/src/main.ts b/src/main.ts index 21d741e8..5c93bc58 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,6 +33,8 @@ import type { Directions, dvFrontmatterCache, HierarchyGraphs, + HierarchyNoteItem, + neighbourObj, } from "src/interfaces"; import { addEdgeIfNot, @@ -44,14 +46,15 @@ import { getAllFieldGs, getAllGsInDir, getDVMetadataCache, + getFields, getNeighbourObjArr, getObsMetadataCache, getOppDir, + getOppFields, getOutNeighbours, getPrevNext, mergeGs, oppFields, - removeDuplicates, writeBCToFile, } from "src/sharedFunctions"; import { VisModal } from "src/VisModal"; @@ -175,7 +178,7 @@ export default class BCPlugin extends Plugin { this.addCommand({ id: "open-vis-modal", name: "Open Visualisation Modal", - callback: async () => { + callback: () => { new VisModal(this.app, this).open(); }, }); @@ -265,100 +268,65 @@ export default class BCPlugin extends Plugin { return null; } - hierarchyNoteAdjList = (str: string) => { - let noteContent = str; - const { settings } = this; - - const yamlRegex = new RegExp(/^---.*/); - const hasYaml = yamlRegex.test(noteContent); - if (hasYaml) { - noteContent = noteContent.split("---").slice(2).join("---"); - } - - const layers = noteContent.split("\n").filter((line) => line); - - const getDepth = (line: string) => line.split(/[-*+]/)[0].length; - - const depths = layers.map(getDepth); - const differences = []; - - depths.forEach((dep, i) => { - if (i >= 1) { - differences.push(dep - depths[i - 1]); - } - }); + async getHierarchyNoteItems(file: TFile) { + const { listItems } = this.app.metadataCache.getFileCache(file); + if (!listItems) return []; - debug(settings, { differences }); + const content = await this.app.vault.cachedRead(file); + const lines = content.split("\n"); - const posDifferences = differences - .filter((diff) => diff !== 0) - .map(Math.abs); + const hierarchyNoteItems: HierarchyNoteItem[] = []; - const lcm = Math.min(...posDifferences); + const afterBulletReg = new RegExp(/\s*[+*-]\s(.*$)/); + const dropWikiLinksReg = new RegExp(/\[\[(.*?)\]\]/); + const fieldNameReg = new RegExp(/(.*?)\[\[.*?\]\]/); - if (!posDifferences.every((diff) => diff % lcm === 0)) { - new Notice( - "Please make sure the indentation is consistent in your hierarchy note." - ); - return []; - } - const difference = lcm; - - type adjItem = { note: string; depth: number; children: string[] }; - const adjItems: adjItem[] = []; - - // TODO Allow user to pick the field name they want - const lineRegex = new RegExp(/\s*[-*+] \[\[(.*)\]\]/); - - const pushNoteUp = ( - hier: adjItem[], - currNote: string, - currDepth: number - ) => { - const copy = [...hier]; - const noteUp = copy - .reverse() - .findIndex((adjItem) => adjItem.depth === currDepth - difference); - if (noteUp > -1) hier[noteUp].children.push(currNote); - }; + const problemFields: string[] = []; - let lineNo = 0; - while (lineNo < layers.length) { - const currLine = layers[lineNo]; + const upFields = getFields(this.settings.userHierarchies, "up"); + for (const item of listItems) { + const currItem = lines[item.position.start.line]; - const currNote = currLine.match(lineRegex)[1]; - const currDepth = getDepth(currLine); + const afterBulletCurr = afterBulletReg.exec(currItem)[1]; + const dropWikiCurr = dropWikiLinksReg.exec(afterBulletCurr)[1]; + let fieldNameCurr = fieldNameReg.exec(afterBulletCurr)[1].trim() || null; - adjItems[lineNo] = { note: currNote, depth: currDepth, children: [] }; + // Ensure fieldName is one of the existing up fields. `null` if not + if (fieldNameCurr !== null && !upFields.includes(fieldNameCurr)) { + problemFields.push(fieldNameCurr); + fieldNameCurr = null; + } - if (lineNo !== layers.length - 1) { - const nextLine = layers[lineNo + 1]; - const nextNote = nextLine.match(lineRegex)[1]; - const nextDepth = getDepth(nextLine); + const { parent } = item; + if (parent >= 0) { + const parentNote = lines[parent]; + const afterBulletParent = afterBulletReg.exec(parentNote)[1]; + const dropWikiParent = dropWikiLinksReg.exec(afterBulletParent)[1]; - if (nextDepth > currDepth) { - adjItems[lineNo].children.push(nextNote); - pushNoteUp(adjItems, currNote, currDepth); - } else if (currDepth === 0) return; - else { - pushNoteUp(adjItems, currNote, currDepth); - } + hierarchyNoteItems.push({ + currNote: dropWikiCurr, + parentNote: dropWikiParent, + fieldName: fieldNameCurr, + }); } else { - const prevLine = layers[lineNo - 1]; - const prevDepth = getDepth(prevLine); - - if (prevDepth >= currDepth) { - pushNoteUp(adjItems, currNote, currDepth); - } + hierarchyNoteItems.push({ + currNote: dropWikiCurr, + parentNote: null, + fieldName: fieldNameCurr, + }); } - - lineNo++; } - adjItems.forEach((item) => { - item.children = removeDuplicates(item.children); - }); - return adjItems; - }; + if (problemFields.length > 0) { + const msg = `'${problemFields.join( + ", " + )}' is/are not in any of your hierarchies, but are being used in: '${ + file.basename + }'`; + new Notice(msg); + console.log(msg, { problemFields }); + } + return hierarchyNoteItems; + } // SECTION OneSource @@ -381,6 +349,37 @@ export default class BCPlugin extends Plugin { }); } + populateMain( + main: MultiGraph, + currFileName: string, + dir: Directions, + fieldName: string, + targets: string[], + neighbours: neighbourObj, + neighbourObjArr: neighbourObj[] + ): void { + addNodeIfNot(main, currFileName, { + dir, + fieldName, + order: neighbours.order, + }); + targets.forEach((target) => { + addNodeIfNot(main, target, { + dir, + fieldName, + order: + neighbourObjArr.find( + (neighbour) => + (neighbour.current.basename || neighbour.current.name) === target + )?.order ?? 9999, + }); + addEdgeIfNot(main, currFileName, target, { + dir, + fieldName, + }); + }); + } + async getCSVRows() { const { CSVPaths } = this.settings; const CSVRows: { [key: string]: string }[] = []; @@ -444,29 +443,19 @@ export default class BCPlugin extends Plugin { const neighbourObjArr = await getNeighbourObjArr(this, fileFrontmatterArr); debugGroupStart(settings, "debugMode", "Hierarchy Note Adjacency List"); - let hierarchyNotesArr: { - note: string; - depth: number; - children: string[]; - }[] = []; + const hierarchyNotesArr: HierarchyNoteItem[] = []; if (settings.hierarchyNotes[0] !== "") { - const contentArr: string[] = []; - - settings.hierarchyNotes.forEach(async (note) => { + for (const note of settings.hierarchyNotes) { const file = this.app.metadataCache.getFirstLinkpathDest(note, ""); if (file) { - const content = await this.app.vault.cachedRead(file); - contentArr.push(content); + hierarchyNotesArr.push(...(await this.getHierarchyNoteItems(file))); } else { new Notice( - `${note} is no longer in your vault. The Hierarchy note should still work, but it is best to remove ${note} from your list of hierarchy notes in Breadcrumbs settings.` + `${note} is no longer in your vault. It is best to remove it in Breadcrumbs settings.` ); } - }); - - await Promise.all(contentArr); + } - hierarchyNotesArr = contentArr.map(this.hierarchyNoteAdjList).flat(); debug(settings, { hierarchyNotesArr }); } debugGroupEnd(settings, "debugMode"); @@ -508,27 +497,15 @@ export default class BCPlugin extends Plugin { const targets = hier[dir][fieldName]; this.populateGraph(g, currFileName, targets, dir, fieldName); - addNodeIfNot(graphs.main, currFileName, { + this.populateMain( + graphs.main, + currFileName, dir, fieldName, - order: neighbours.order, - }); - targets.forEach((target) => { - addNodeIfNot(graphs.main, target, { - dir, - fieldName, - order: - neighbourObjArr.find( - (neighbour) => - (neighbour.current.basename || neighbour.current.name) === - target - )?.order ?? 9999, - }); - addEdgeIfNot(graphs.main, currFileName, target, { - dir, - fieldName, - }); - }); + targets, + neighbours, + neighbourObjArr + ); if (useCSV) { this.addCSVCrumbs(g, CSVRows, dir, fieldName); @@ -540,40 +517,43 @@ export default class BCPlugin extends Plugin { }); if (hierarchyNotesArr.length) { - const { hierarchyNoteUpFieldName, hierarchyNoteDownFieldName } = settings; - - if (hierarchyNoteUpFieldName !== "") { - const gUp = graphs.hierGs.find( - (hierG) => hierG.up[hierarchyNoteUpFieldName] - ).up[hierarchyNoteUpFieldName]; - - hierarchyNotesArr.forEach((adjListItem) => { - adjListItem.children.forEach((child) => { - //@ts-ignore - addNodeIfNot(gUp, adjListItem.note, { dir: "up" }); - gUp.addEdge(child, adjListItem.note, { - dir: "up", - fieldName: hierarchyNoteUpFieldName, - }); + const { hierarchyNoteUpFieldName } = settings; + const { main } = graphs; + const upFields = getFields(userHierarchies, "up"); + + hierarchyNotesArr.forEach((hnItem) => { + if (hnItem.parentNote === null) return; + + const upField = + hnItem.fieldName ?? (hierarchyNoteUpFieldName || upFields[0]); + const downField = + getOppFields(userHierarchies, upField)[0] ?? `${upField}`; + + const gUp = graphs.hierGs.find((hierG) => hierG.up[upField]).up[ + upField + ]; + const gDown = graphs.hierGs.find((hierG) => hierG.down[downField]).down[ + downField + ]; + + [gUp, main].forEach((g) => { + addNodeIfNot(g, hnItem.currNote); + addNodeIfNot(g, hnItem.parentNote); + addEdgeIfNot(g, hnItem.currNote, hnItem.parentNote, { + dir: "up", + fieldName: upField, }); }); - } - if (hierarchyNoteDownFieldName !== "") { - const gDown = graphs.hierGs.find( - (hierG) => hierG.down[hierarchyNoteDownFieldName] - ).down[hierarchyNoteDownFieldName]; - - hierarchyNotesArr.forEach((adjListItem) => { - adjListItem.children.forEach((child) => { - //@ts-ignore - addNodeIfNot(gDown, adjListItem.note, { dir: "down" }); - gDown.addEdge(adjListItem.note, child, { - dir: "down", - fieldName: hierarchyNoteDownFieldName, - }); + + [gDown, main].forEach((g) => { + addNodeIfNot(g, hnItem.parentNote); + addNodeIfNot(g, hnItem.currNote); + addEdgeIfNot(g, hnItem.parentNote, hnItem.currNote, { + dir: "down", + fieldName: downField, }); }); - } + }); } DIRECTIONS.forEach((dir) => { diff --git a/src/sharedFunctions.ts b/src/sharedFunctions.ts index dc1babe5..31575d17 100644 --- a/src/sharedFunctions.ts +++ b/src/sharedFunctions.ts @@ -626,6 +626,28 @@ export function iterateAllGs( } } } +/** + * Get all the fields in `dir`. + * Returns all fields if `dir === 'all'` + * @param {userHierarchy[]} userHierarchies + * @param {Directions|"all"} dir + */ +export function getFields( + userHierarchies: userHierarchy[], + dir: Directions | "all" = "all" +) { + const fields: string[] = []; + userHierarchies.forEach((hier) => { + if (dir === "all") { + DIRECTIONS.forEach((eachDir) => { + fields.push(...hier[eachDir]); + }); + } else { + fields.push(...hier[dir]); + } + }); + return fields; +} export function getAllFieldGs(fields: string[], currGraphs: HierarchyGraphs[]) { const fieldGs: Graph[] = [];