mindnet_obsidian/src/workbench/insertEdgeIntoSectionContent.ts
Lars 725adb5302
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Enhance UI and functionality for Chain Workbench and related features
- Introduced a wide two-column layout for the Chain Workbench modal, improving user experience and accessibility.
- Added new styles for workbench components, including headers, filters, and main containers, to enhance visual organization.
- Updated chain templates to allow for multiple distinct matches per template, improving flexibility in template matching.
- Enhanced documentation to clarify the new settings and commands related to the Chain Workbench and edge detection features.
- Implemented logging for better tracking of missing configurations, ensuring users are informed about any loading issues.
2026-02-05 11:41:15 +01:00

150 lines
7.3 KiB
TypeScript

/**
* Pure logic: compute section content after inserting an edge into the abstract block.
* Used by insertEdgeInSection and by tests.
*/
import { extractExistingMappings } from "../mapping/mappingExtractor";
import { buildMappingBlock, insertMappingBlock } from "../mapping/mappingBuilder";
export interface InsertEdgeOptions {
wrapperCalloutType: string;
wrapperTitle: string;
wrapperFolded: boolean;
}
/** Separator between edge blocks inside abstract: exactly one line with exactly one ">". */
const EDGE_GROUP_SEPARATOR = "\n>\n";
/**
* Compute new section content after inserting one edge (edgeType → targetLink).
* - If section has an abstract block: insert into it (append to same edge-type group, or add new group separated by ">").
* - If no abstract block: append new mapping block at end.
*/
export function computeSectionContentAfterInsertEdge(
sectionContent: string,
edgeType: string,
targetLink: string,
options: InsertEdgeOptions
): string {
const { wrapperCalloutType, wrapperTitle, wrapperFolded } = options;
const mappingState = extractExistingMappings(sectionContent, wrapperCalloutType, wrapperTitle);
const hasMappingBlock = mappingState.wrapperBlockStartLine !== null && mappingState.wrapperBlockEndLine !== null;
if (hasMappingBlock && mappingState.wrapperBlockStartLine !== null && mappingState.wrapperBlockEndLine !== null) {
const sectionLines = sectionContent.split(/\r?\n/);
const wrapperStart = mappingState.wrapperBlockStartLine;
const wrapperEnd = mappingState.wrapperBlockEndLine;
const wrapperBlockContent = sectionLines.slice(wrapperStart, wrapperEnd).join("\n");
// Same edge type already exists → append target to that group
// Match includes the separator ">\n" if present before next edge type
const edgeTypeGroupRegex = new RegExp(
`(>>\\s*\\[!edge\\]\\s+${edgeType.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?)(?=\\n\\n|\\n>>\\s*\\[!edge\\]|\\n>\\s*\\^map-|$)`
);
const edgeTypeMatch = wrapperBlockContent.match(edgeTypeGroupRegex);
if (edgeTypeMatch && edgeTypeMatch[1]) {
// Get the matched group
const matchedGroup = edgeTypeMatch[1];
// Check what comes after the matched group in the original content
const matchEndIndex = edgeTypeMatch.index! + edgeTypeMatch[0].length;
const afterMatch = wrapperBlockContent.slice(matchEndIndex);
// Check if there's a separator ">\n" immediately after the group
const hasSeparatorAfter = afterMatch.match(/^\n>\s*\n/);
// Also check if the group itself ends with a separator
const trimmedGroup = matchedGroup.trimEnd();
const endsWithSeparator = trimmedGroup.endsWith("\n>");
const endsWithTarget = trimmedGroup.match(/>>\s*\[\[[^\]]+\]\]\s*$/);
let updatedGroup: string;
let updatedAfterMatch = afterMatch;
if (endsWithSeparator || hasSeparatorAfter) {
// Group has a separator (either at end or after), insert new target before separator
if (endsWithSeparator) {
// Separator is part of the group, remove it, add target, restore separator
const separatorIndex = trimmedGroup.lastIndexOf("\n>");
let groupWithoutSeparator: string;
if (separatorIndex >= 0) {
// Remove separator and everything after it
groupWithoutSeparator = trimmedGroup.slice(0, separatorIndex);
// Remove trailing whitespace but preserve the newline structure
// We want to keep exactly one newline before adding the new target
groupWithoutSeparator = groupWithoutSeparator.replace(/[ \t]+$/, ""); // Remove trailing spaces/tabs only
} else {
groupWithoutSeparator = trimmedGroup.replace(/\n>\s*$/, "").trimEnd();
}
// Add target directly followed by separator (>\n), no blank line in between
// The separator should be "\n>\n" (one line with ">", then newline for next group)
// But we need to ensure no extra blank line after the separator
updatedGroup = groupWithoutSeparator + "\n>> [[" + targetLink + "]]\n>";
} else {
// Separator is after the group, add target before it
// Ensure no blank line between target and separator
updatedGroup = trimmedGroup.trimEnd() + "\n>> [[" + targetLink + "]]";
// Remove any blank lines before separator in afterMatch
updatedAfterMatch = afterMatch.replace(/^\s*\n+/, "\n");
}
} else if (endsWithTarget) {
// Group ends with a target, append new target
updatedGroup = trimmedGroup + "\n>> [[" + targetLink + "]]\n";
} else {
// Fallback: append with newline
updatedGroup = trimmedGroup + "\n>> [[" + targetLink + "]]\n";
}
// Ensure no blank line between separator and next edge type
// The separator ends with ">\n", and the next edge type should come immediately after (no blank line)
// Remove any blank lines (multiple newlines) between separator and next edge type
let cleanedAfterMatch = updatedAfterMatch;
// If afterMatch starts with newline(s) followed by >> [!edge], ensure only one newline
if (cleanedAfterMatch.match(/^\n+>>\s*\[!edge\]/)) {
// Replace multiple newlines with single newline
cleanedAfterMatch = cleanedAfterMatch.replace(/^(\n+)(>>\s*\[!edge\])/, "\n$2");
}
const updatedWrapperBlock = wrapperBlockContent.slice(0, edgeTypeMatch.index!) + updatedGroup + cleanedAfterMatch;
const beforeWrapper = sectionLines.slice(0, wrapperStart).join("\n");
const afterWrapper = sectionLines.slice(wrapperEnd).join("\n");
return beforeWrapper + "\n" + updatedWrapperBlock + "\n" + afterWrapper;
}
// New edge type → add new group, separated by exactly one line with exactly one ">"
const blockIdMatch = wrapperBlockContent.match(/\n>?\s*\^map-/);
const insertPos = blockIdMatch ? blockIdMatch.index ?? wrapperBlockContent.length : wrapperBlockContent.length;
const endsWithNewline = insertPos > 0 && wrapperBlockContent[insertPos - 1] === "\n";
const newEdgeGroup =
(endsWithNewline ? ">\n" : EDGE_GROUP_SEPARATOR) + `>> [!edge] ${edgeType}\n>> [[${targetLink}]]\n`;
const updatedWrapperBlock =
wrapperBlockContent.slice(0, insertPos) + newEdgeGroup + wrapperBlockContent.slice(insertPos);
const beforeWrapper = sectionLines.slice(0, wrapperStart).join("\n");
const afterWrapper = sectionLines.slice(wrapperEnd).join("\n");
return beforeWrapper + "\n" + updatedWrapperBlock + "\n" + afterWrapper;
}
// No abstract block: append new mapping block at end (use full targetLink for display)
const targetLinkNorm = targetLink.split("|")[0]?.trim() || targetLink;
const existingMappings = new Map<string, string>();
existingMappings.set(targetLinkNorm, edgeType);
const foldMarker = wrapperFolded ? "-" : "+";
const newMappingBlock = buildMappingBlock(
[targetLinkNorm],
existingMappings,
{
wrapperCalloutType,
wrapperTitle,
wrapperFolded,
defaultEdgeType: "",
assignUnmapped: "none",
}
);
if (newMappingBlock) {
return insertMappingBlock(sectionContent, newMappingBlock);
}
const mappingBlock = `\n\n> [!${wrapperCalloutType}]${foldMarker} ${wrapperTitle}\n>> [!edge] ${edgeType}\n>> [[${targetLink}]]\n`;
return sectionContent.trimEnd() + mappingBlock;
}