Enhance edge type handling and categorization
- Added optional description and category fields to edge type entries, improving metadata for edge types. - Updated the `getAllEdgeTypes` and `groupEdgeTypesByCategory` functions to utilize new fields for better organization and display. - Enhanced UI components to show descriptions as tooltips and categorize edge types in the EdgeTypeChooserModal and InlineEdgeTypeModal. - Improved parsing logic in `parseEdgeVocabulary` to extract descriptions and categories from the vocabulary table, ensuring richer edge type data. - Adjusted the LinkPromptModal to clarify edge type actions and maintain alias information during selection.
This commit is contained in:
parent
7ea36fbed4
commit
2fcf333e56
|
|
@ -39,11 +39,15 @@ export function getAllEdgeTypes(vocabulary: EdgeVocabulary): Array<{
|
|||
canonical: string;
|
||||
aliases: string[];
|
||||
displayName: string; // First alias or canonical
|
||||
description?: string;
|
||||
category?: string;
|
||||
}> {
|
||||
const result: Array<{
|
||||
canonical: string;
|
||||
aliases: string[];
|
||||
displayName: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
}> = [];
|
||||
|
||||
for (const [canonical, entry] of vocabulary.byCanonical.entries()) {
|
||||
|
|
@ -53,6 +57,8 @@ export function getAllEdgeTypes(vocabulary: EdgeVocabulary): Array<{
|
|||
canonical,
|
||||
aliases: entry.aliases,
|
||||
displayName,
|
||||
description: entry.description,
|
||||
category: entry.category,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -64,14 +70,42 @@ export function getAllEdgeTypes(vocabulary: EdgeVocabulary): Array<{
|
|||
|
||||
/**
|
||||
* Group edge types by category (if available).
|
||||
* MVP: Returns single "All" category.
|
||||
* Uses category from EdgeTypeEntry if available.
|
||||
* If categories are found, groups by category. Otherwise returns single "All" group.
|
||||
*/
|
||||
export function groupEdgeTypesByCategory(
|
||||
edgeTypes: Array<{ canonical: string; aliases: string[]; displayName: string }>
|
||||
): Map<string, Array<{ canonical: string; aliases: string[]; displayName: string }>> {
|
||||
// TODO: Implement category grouping from schema
|
||||
// For MVP, return single "All" category
|
||||
const grouped = new Map<string, Array<{ canonical: string; aliases: string[]; displayName: string }>>();
|
||||
grouped.set("All", edgeTypes);
|
||||
return grouped;
|
||||
edgeTypes: Array<{ canonical: string; aliases: string[]; displayName: string; description?: string; category?: string }>
|
||||
): Map<string, Array<{ canonical: string; aliases: string[]; displayName: string; description?: string; category?: string }>> {
|
||||
const grouped = new Map<string, Array<{ canonical: string; aliases: string[]; displayName: string; description?: string; category?: string }>>();
|
||||
|
||||
// Check if any edge types have categories
|
||||
const hasCategories = edgeTypes.some(e => e.category && e.category.trim() !== "");
|
||||
|
||||
if (hasCategories) {
|
||||
// Group by category
|
||||
for (const edgeType of edgeTypes) {
|
||||
const category = edgeType.category && edgeType.category.trim() !== ""
|
||||
? edgeType.category.trim()
|
||||
: "Allgemein"; // Default category if not specified
|
||||
if (!grouped.has(category)) {
|
||||
grouped.set(category, []);
|
||||
}
|
||||
grouped.get(category)!.push(edgeType);
|
||||
}
|
||||
|
||||
// Sort categories alphabetically
|
||||
const sortedCategories = Array.from(grouped.keys()).sort();
|
||||
const sortedGrouped = new Map<string, Array<{ canonical: string; aliases: string[]; displayName: string; description?: string; category?: string }>>();
|
||||
for (const category of sortedCategories) {
|
||||
const types = grouped.get(category);
|
||||
if (types) {
|
||||
sortedGrouped.set(category, types);
|
||||
}
|
||||
}
|
||||
return sortedGrouped;
|
||||
} else {
|
||||
// No categories found, use "All"
|
||||
grouped.set("All", edgeTypes);
|
||||
return grouped;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,13 +227,18 @@ export async function buildSemanticMappings(
|
|||
|
||||
// Apply decision
|
||||
if (decision.action === "keep") {
|
||||
// Use the current type as-is (preserves alias if it was an alias)
|
||||
// item.currentType is already the actual type from existingMappings (could be alias or canonical)
|
||||
mappingsToUse.set(item.link, decision.edgeType);
|
||||
if (mappingState.existingMappings.has(item.link)) {
|
||||
result.existingMappingsKept++;
|
||||
}
|
||||
} else if (decision.action === "change") {
|
||||
// Use canonical type (alias is for display only)
|
||||
mappingsToUse.set(item.link, decision.edgeType);
|
||||
// Use alias if provided, otherwise use canonical type
|
||||
// This ensures selected aliases are written to the file, not just canonical types
|
||||
const finalEdgeType = decision.alias || decision.edgeType;
|
||||
mappingsToUse.set(item.link, finalEdgeType);
|
||||
console.log(`[Mindnet] Setting edge type for ${item.link}: ${finalEdgeType} (canonical: ${decision.edgeType}, alias: ${decision.alias || "none"})`);
|
||||
if (mappingState.existingMappings.has(item.link)) {
|
||||
// Changed existing
|
||||
} else {
|
||||
|
|
@ -248,9 +253,10 @@ export async function buildSemanticMappings(
|
|||
}
|
||||
} else {
|
||||
// Silent modes: none/defaultType/advisor
|
||||
// Always include existing mappings
|
||||
// Always include existing mappings (preserve aliases as they are)
|
||||
for (const [link, edgeType] of mappingState.existingMappings.entries()) {
|
||||
if (section.links.includes(link)) {
|
||||
// Use existing type as-is (could be alias or canonical)
|
||||
mappingsToUse.set(link, edgeType);
|
||||
result.existingMappingsKept++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,13 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
const allEdgeTypes = getAllEdgeTypes(this.vocabulary);
|
||||
const grouped = groupEdgeTypesByCategory(allEdgeTypes);
|
||||
|
||||
// Debug: Log grouping info
|
||||
console.log("[Mindnet] EdgeTypeChooserModal:", {
|
||||
totalEdgeTypes: allEdgeTypes.length,
|
||||
categories: Array.from(grouped.keys()),
|
||||
categorySizes: Array.from(grouped.entries()).map(([cat, types]) => ({ category: cat, count: types.length })),
|
||||
});
|
||||
|
||||
// Recommended section (if any)
|
||||
if (suggestions.typical.length > 0) {
|
||||
contentEl.createEl("h3", { text: "⭐ Recommended (schema)" });
|
||||
|
|
@ -61,11 +68,18 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
const entry = this.vocabulary.byCanonical.get(canonical);
|
||||
if (!entry) continue;
|
||||
|
||||
const displayName = entry.aliases.length > 0 ? entry.aliases[0] : canonical;
|
||||
// Display: canonical type (primary), aliases shown after selection
|
||||
const btn = recommendedContainer.createEl("button", {
|
||||
text: `⭐ ${displayName}`,
|
||||
text: `⭐ ${canonical}`,
|
||||
cls: "mod-cta",
|
||||
});
|
||||
|
||||
// Add description as tooltip/hover text
|
||||
if (entry.description) {
|
||||
btn.title = entry.description;
|
||||
btn.setAttribute("data-description", entry.description);
|
||||
}
|
||||
|
||||
btn.onclick = () => {
|
||||
this.selectEdgeType(canonical, entry.aliases);
|
||||
};
|
||||
|
|
@ -73,16 +87,24 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
}
|
||||
|
||||
// All categories
|
||||
contentEl.createEl("h3", { text: "📂 All categories" });
|
||||
if (grouped.size > 1 || (grouped.size === 1 && !grouped.has("All"))) {
|
||||
contentEl.createEl("h3", { text: "📂 All categories" });
|
||||
}
|
||||
|
||||
for (const [category, types] of grouped.entries()) {
|
||||
if (category !== "All") {
|
||||
contentEl.createEl("h4", { text: category });
|
||||
// Show category heading if not "All" or if there are multiple categories
|
||||
if (category !== "All" || grouped.size > 1) {
|
||||
const categoryHeading = contentEl.createEl("h4", { text: category });
|
||||
// Add description if available (from first entry in category)
|
||||
const firstType = types[0];
|
||||
if (firstType && firstType.description) {
|
||||
categoryHeading.title = firstType.description;
|
||||
}
|
||||
}
|
||||
|
||||
const container = contentEl.createEl("div", { cls: "edge-type-list" });
|
||||
|
||||
for (const { canonical, aliases, displayName } of types) {
|
||||
for (const { canonical, aliases, displayName, description } of types) {
|
||||
const isProhibited = suggestions.prohibited.includes(canonical);
|
||||
const isTypical = suggestions.typical.includes(canonical);
|
||||
|
||||
|
|
@ -90,14 +112,21 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
if (isTypical) prefix = "⭐";
|
||||
if (isProhibited) prefix = "🚫";
|
||||
|
||||
// Display: canonical type (primary), aliases shown after selection
|
||||
const btn = container.createEl("button", {
|
||||
text: `${prefix} ${displayName}`,
|
||||
text: `${prefix} ${canonical}`,
|
||||
});
|
||||
|
||||
if (isProhibited) {
|
||||
btn.addClass("prohibited");
|
||||
}
|
||||
|
||||
// Add description as tooltip/hover text
|
||||
if (description) {
|
||||
btn.title = description;
|
||||
btn.setAttribute("data-description", description);
|
||||
}
|
||||
|
||||
btn.onclick = () => {
|
||||
this.selectEdgeType(canonical, aliases);
|
||||
};
|
||||
|
|
@ -122,12 +151,8 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
// No aliases, use canonical directly
|
||||
this.result = { edgeType: canonical };
|
||||
this.close();
|
||||
} else if (aliases.length === 1) {
|
||||
// Single alias, use it
|
||||
this.result = { edgeType: canonical, alias: aliases[0] };
|
||||
this.close();
|
||||
} else {
|
||||
// Multiple aliases, show alias chooser
|
||||
// Always show alias chooser if aliases exist (even for single alias)
|
||||
this.selectedCanonical = canonical;
|
||||
this.showAliasChooser(aliases);
|
||||
}
|
||||
|
|
@ -138,14 +163,26 @@ export class EdgeTypeChooserModal extends Modal {
|
|||
contentEl.empty();
|
||||
|
||||
contentEl.createEl("h2", { text: "Choose alias" });
|
||||
contentEl.createEl("p", { text: `Multiple aliases available for ${this.selectedCanonical}` });
|
||||
contentEl.createEl("p", { text: `Aliases available for ${this.selectedCanonical}` });
|
||||
|
||||
const container = contentEl.createEl("div", { cls: "alias-list" });
|
||||
|
||||
// Option: Use canonical (no alias)
|
||||
const canonicalBtn = container.createEl("button", {
|
||||
text: `${this.selectedCanonical} (canonical)`,
|
||||
cls: "mod-cta",
|
||||
});
|
||||
canonicalBtn.onclick = () => {
|
||||
if (this.selectedCanonical) {
|
||||
this.result = { edgeType: this.selectedCanonical };
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
// All aliases
|
||||
for (const alias of aliases) {
|
||||
const btn = container.createEl("button", {
|
||||
text: alias,
|
||||
cls: "mod-cta",
|
||||
});
|
||||
btn.onclick = () => {
|
||||
if (this.selectedCanonical) {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,20 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
});
|
||||
linkInfo.textContent = `Link: [[${this.linkBasename}]]`;
|
||||
|
||||
// Show selected type if one was chosen (e.g., from chooser)
|
||||
if (this.selectedEdgeType) {
|
||||
const selectedInfo = contentEl.createEl("div", {
|
||||
cls: "inline-edge-type-modal__selected-info",
|
||||
});
|
||||
const displayType = this.selectedAlias || this.selectedEdgeType;
|
||||
selectedInfo.textContent = `Ausgewählt: ${displayType}`;
|
||||
selectedInfo.style.marginTop = "0.5em";
|
||||
selectedInfo.style.padding = "0.5em";
|
||||
selectedInfo.style.backgroundColor = "var(--background-modifier-hover)";
|
||||
selectedInfo.style.borderRadius = "4px";
|
||||
selectedInfo.style.fontWeight = "bold";
|
||||
}
|
||||
|
||||
// Get recommendations
|
||||
const recommendations = this.getRecommendations();
|
||||
const typical = recommendations.typical;
|
||||
|
|
@ -80,10 +94,19 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
// Auto-select first typical if available
|
||||
// Auto-select first typical if available (use canonical, alias selection comes later)
|
||||
// Only auto-select if no type has been explicitly selected yet
|
||||
if (typical.length > 0 && typical[0] && !this.selectedEdgeType) {
|
||||
this.selectedEdgeType = typical[0];
|
||||
this.result = { chosenRawType: typical[0], cancelled: false };
|
||||
const firstTypical = typical[0];
|
||||
this.selectedEdgeType = firstTypical;
|
||||
// Use canonical as default, alias will be selected in alias chooser if needed
|
||||
this.result = { chosenRawType: firstTypical, cancelled: false };
|
||||
}
|
||||
|
||||
// If a type was already selected (e.g., from chooser), ensure it's reflected in result
|
||||
if (this.selectedEdgeType && !this.result) {
|
||||
const rawType = this.selectedAlias || this.selectedEdgeType;
|
||||
this.result = { chosenRawType: rawType, cancelled: false };
|
||||
}
|
||||
|
||||
// Recommended chips (typical) - all are directly selectable, first one is preselected
|
||||
|
|
@ -100,21 +123,45 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
});
|
||||
|
||||
for (let i = 0; i < typical.length; i++) {
|
||||
const edgeType = typical[i];
|
||||
if (!edgeType) continue;
|
||||
const canonical = typical[i];
|
||||
if (!canonical) continue;
|
||||
|
||||
// Get entry for description
|
||||
let description: string | undefined = undefined;
|
||||
let aliases: string[] = [];
|
||||
if (this.vocabulary) {
|
||||
const entry = this.vocabulary.byCanonical.get(canonical);
|
||||
if (entry) {
|
||||
description = entry.description;
|
||||
aliases = entry.aliases;
|
||||
}
|
||||
}
|
||||
|
||||
// Display: canonical type (primary)
|
||||
const chip = chipsContainer.createEl("button", {
|
||||
cls: "inline-edge-type-modal__chip",
|
||||
text: edgeType,
|
||||
text: canonical,
|
||||
});
|
||||
|
||||
// First typical is preselected
|
||||
if (i === 0) {
|
||||
// Add description as tooltip/hover text
|
||||
if (description) {
|
||||
chip.title = description;
|
||||
chip.setAttribute("data-description", description);
|
||||
}
|
||||
|
||||
// Mark as selected if this is the currently selected type
|
||||
const isSelected = this.selectedEdgeType === canonical;
|
||||
if (isSelected || (i === 0 && !this.selectedEdgeType)) {
|
||||
chip.addClass("mod-cta");
|
||||
}
|
||||
|
||||
chip.onclick = () => {
|
||||
this.selectEdgeType(edgeType);
|
||||
// If aliases exist, show alias chooser, otherwise use canonical
|
||||
if (aliases.length > 0) {
|
||||
this.showAliasChooser(canonical, aliases);
|
||||
} else {
|
||||
this.selectEdgeType(canonical);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -133,12 +180,38 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
cls: "inline-edge-type-modal__chips",
|
||||
});
|
||||
|
||||
for (const edgeType of alternatives) {
|
||||
for (const canonical of alternatives) {
|
||||
// Get entry for description
|
||||
let description: string | undefined = undefined;
|
||||
let aliases: string[] = [];
|
||||
if (this.vocabulary) {
|
||||
const entry = this.vocabulary.byCanonical.get(canonical);
|
||||
if (entry) {
|
||||
description = entry.description;
|
||||
aliases = entry.aliases;
|
||||
}
|
||||
}
|
||||
|
||||
// Display: canonical type (primary)
|
||||
const chip = chipsContainer.createEl("button", {
|
||||
cls: "inline-edge-type-modal__chip",
|
||||
text: edgeType,
|
||||
text: canonical,
|
||||
});
|
||||
chip.onclick = () => this.selectEdgeType(edgeType);
|
||||
|
||||
// Add description as tooltip/hover text
|
||||
if (description) {
|
||||
chip.title = description;
|
||||
chip.setAttribute("data-description", description);
|
||||
}
|
||||
|
||||
chip.onclick = () => {
|
||||
// If aliases exist, show alias chooser, otherwise use canonical
|
||||
if (aliases.length > 0) {
|
||||
this.showAliasChooser(canonical, aliases);
|
||||
} else {
|
||||
this.selectEdgeType(canonical);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,11 +228,28 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
cls: "inline-edge-type-modal__chips",
|
||||
});
|
||||
|
||||
for (const edgeType of prohibited) {
|
||||
for (const canonical of prohibited) {
|
||||
// Get entry for description
|
||||
let description: string | undefined = undefined;
|
||||
if (this.vocabulary) {
|
||||
const entry = this.vocabulary.byCanonical.get(canonical);
|
||||
if (entry) {
|
||||
description = entry.description;
|
||||
}
|
||||
}
|
||||
|
||||
// Display: canonical type (primary)
|
||||
const chip = chipsContainer.createEl("button", {
|
||||
cls: "inline-edge-type-modal__chip",
|
||||
text: edgeType,
|
||||
text: canonical,
|
||||
});
|
||||
|
||||
// Add description as tooltip/hover text (even for disabled chips)
|
||||
if (description) {
|
||||
chip.title = description;
|
||||
chip.setAttribute("data-description", description);
|
||||
}
|
||||
|
||||
chip.disabled = true;
|
||||
chip.addClass("mod-muted");
|
||||
}
|
||||
|
|
@ -183,10 +273,10 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
);
|
||||
const choice: EdgeTypeChoice | null = await chooser.show();
|
||||
if (choice) {
|
||||
// Store the choice and close modal
|
||||
// Store the choice and re-render to show selection
|
||||
this.selectEdgeType(choice.edgeType, choice.alias);
|
||||
// Close this modal immediately after selection
|
||||
this.close();
|
||||
// Re-render the modal to show the selected type
|
||||
this.onOpen();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -201,8 +291,18 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
cls: "mod-cta",
|
||||
});
|
||||
okBtn.onclick = () => {
|
||||
// If no type selected yet, use preselected
|
||||
// If no type selected yet, use preselected canonical
|
||||
if (!this.result && this.selectedEdgeType) {
|
||||
// Check if aliases exist - if so, show alias chooser first
|
||||
if (this.vocabulary) {
|
||||
const entry = this.vocabulary.byCanonical.get(this.selectedEdgeType);
|
||||
if (entry && entry.aliases.length > 0) {
|
||||
// Show alias chooser
|
||||
this.showAliasChooser(this.selectedEdgeType, entry.aliases);
|
||||
return; // Don't close yet, wait for alias selection
|
||||
}
|
||||
}
|
||||
// No aliases, use canonical directly
|
||||
this.result = { chosenRawType: this.selectedEdgeType, cancelled: false };
|
||||
} else if (!this.result) {
|
||||
// No selection possible, treat as skip
|
||||
|
|
@ -249,27 +349,67 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
}
|
||||
|
||||
private selectEdgeType(edgeType: string, alias?: string | null): void {
|
||||
// Remove previous selection
|
||||
const chips = this.contentEl.querySelectorAll(".inline-edge-type-modal__chip");
|
||||
const chipsArray = Array.from(chips);
|
||||
for (const chip of chipsArray) {
|
||||
if (chip instanceof HTMLElement) {
|
||||
chip.removeClass("mod-cta");
|
||||
}
|
||||
}
|
||||
|
||||
// Mark selected
|
||||
const selectedChip = chipsArray.find(
|
||||
(chip) => chip.textContent === edgeType || chip.textContent?.includes(edgeType)
|
||||
);
|
||||
if (selectedChip && selectedChip instanceof HTMLElement) {
|
||||
selectedChip.addClass("mod-cta");
|
||||
}
|
||||
|
||||
// Store result
|
||||
// Store result - use alias if provided, otherwise use edgeType (canonical)
|
||||
this.selectedEdgeType = edgeType;
|
||||
this.selectedAlias = alias || null;
|
||||
this.result = { chosenRawType: edgeType, cancelled: false };
|
||||
// Use alias if available, otherwise use canonical
|
||||
const rawType = alias || edgeType;
|
||||
this.result = { chosenRawType: rawType, cancelled: false };
|
||||
|
||||
// Note: Visual highlighting will be done in onOpen() when re-rendering
|
||||
}
|
||||
|
||||
private showAliasChooser(canonical: string, aliases: string[]): void {
|
||||
const { contentEl } = this;
|
||||
|
||||
// Store original content structure for restoration
|
||||
const originalSections = Array.from(contentEl.children);
|
||||
|
||||
// Clear and show alias chooser
|
||||
contentEl.empty();
|
||||
contentEl.createEl("h2", { text: "Choose alias" });
|
||||
contentEl.createEl("p", { text: `Aliases available for ${canonical}` });
|
||||
|
||||
const container = contentEl.createEl("div", { cls: "alias-list" });
|
||||
container.style.display = "flex";
|
||||
container.style.flexDirection = "column";
|
||||
container.style.gap = "0.5em";
|
||||
container.style.marginTop = "1em";
|
||||
|
||||
// Option: Use canonical (no alias)
|
||||
const canonicalBtn = container.createEl("button", {
|
||||
text: `${canonical} (canonical)`,
|
||||
cls: "mod-cta",
|
||||
});
|
||||
canonicalBtn.style.width = "100%";
|
||||
canonicalBtn.style.padding = "0.75em";
|
||||
canonicalBtn.onclick = () => {
|
||||
this.selectEdgeType(canonical);
|
||||
// Restore original content by re-rendering
|
||||
this.onOpen();
|
||||
};
|
||||
|
||||
// All aliases
|
||||
for (const alias of aliases) {
|
||||
const btn = container.createEl("button", {
|
||||
text: alias,
|
||||
});
|
||||
btn.style.width = "100%";
|
||||
btn.style.padding = "0.75em";
|
||||
btn.onclick = () => {
|
||||
this.selectEdgeType(canonical, alias);
|
||||
// Restore original content by re-rendering
|
||||
this.onOpen();
|
||||
};
|
||||
}
|
||||
|
||||
const backBtn = container.createEl("button", { text: "← Back" });
|
||||
backBtn.style.width = "100%";
|
||||
backBtn.style.marginTop = "1em";
|
||||
backBtn.onclick = () => {
|
||||
// Restore original content by re-rendering
|
||||
this.onOpen();
|
||||
};
|
||||
}
|
||||
|
||||
private async openFullChooser(): Promise<void> {
|
||||
|
|
@ -290,8 +430,10 @@ export class InlineEdgeTypeModal extends Modal {
|
|||
const choice: EdgeTypeChoice | null = await chooser.show();
|
||||
|
||||
if (choice) {
|
||||
// Store the choice and re-render to show selection
|
||||
this.selectEdgeType(choice.edgeType, choice.alias);
|
||||
this.close();
|
||||
// Re-render the modal to show the selected type
|
||||
this.onOpen();
|
||||
} else {
|
||||
// User cancelled chooser, treat as skip
|
||||
this.result = { chosenRawType: null, cancelled: false };
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { EdgeTypeChooserModal, type EdgeTypeChoice } from "./EdgeTypeChooserModa
|
|||
import { computeEdgeSuggestions } from "../mapping/schemaHelper";
|
||||
|
||||
export type LinkPromptDecision =
|
||||
| { action: "keep"; edgeType: string }
|
||||
| { action: "change"; edgeType: string; alias?: string }
|
||||
| { action: "keep"; edgeType: string } // edgeType is the actual type (alias or canonical) to keep
|
||||
| { action: "change"; edgeType: string; alias?: string } // edgeType is canonical, alias is the selected alias (if any)
|
||||
| { action: "skip" };
|
||||
|
||||
export class LinkPromptModal extends Modal {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,20 @@ const BACKTICK_RE = /`([^`]+)`/g;
|
|||
*/
|
||||
export function parseEdgeVocabulary(md: string): EdgeVocabulary {
|
||||
const lines = md.split(/\r?\n/);
|
||||
const byCanonical = new Map<string, { canonical: string; inverse?: string; aliases: string[] }>();
|
||||
const byCanonical = new Map<string, { canonical: string; inverse?: string; aliases: string[]; description?: string; category?: string }>();
|
||||
const aliasToCanonical = new Map<string, string>();
|
||||
|
||||
let skippedRows = 0;
|
||||
let currentCategory: string | null = null; // Track current H3 category
|
||||
|
||||
for (const line of lines) {
|
||||
// Detect H3 headings (###) as category separators
|
||||
const h3Match = line.match(/^###\s+(.+)$/);
|
||||
if (h3Match && h3Match[1]) {
|
||||
currentCategory = h3Match[1].trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip header separator rows (e.g., "| :--- | :--- |")
|
||||
if (/^\s*\|[\s:|-]+\|\s*$/.test(line)) {
|
||||
continue;
|
||||
|
|
@ -35,6 +43,12 @@ export function parseEdgeVocabulary(md: string): EdgeVocabulary {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Skip header rows (contains "Canonical", "System-Typ", "Beschreibung", "Kategorie", etc.)
|
||||
// Check for common header keywords
|
||||
if (/canonical|system-typ|beschreibung|kategorie|category|description|inverser|aliasse/i.test(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract all backticked tokens
|
||||
const tokens: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
|
@ -54,6 +68,49 @@ export function parseEdgeVocabulary(md: string): EdgeVocabulary {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Parse table cells (split by |, skip first and last empty cells)
|
||||
const cells = line.split("|").map(c => c.trim()).filter(c => c);
|
||||
|
||||
// Extract description and category from cells
|
||||
// Expected order: Canonical | Inverse | Aliases | Description | Category (optional)
|
||||
let description: string | undefined = undefined;
|
||||
let category: string | undefined = undefined;
|
||||
|
||||
// Try to extract from cells after aliases (index 3+)
|
||||
// Description is usually the first text cell after aliases
|
||||
// Category might be in brackets, short, or in a separate column
|
||||
for (let i = 3; i < cells.length; i++) {
|
||||
const cell = cells[i];
|
||||
if (!cell || !cell.trim()) continue;
|
||||
|
||||
const trimmed = cell.trim();
|
||||
|
||||
// Check if this looks like a category:
|
||||
// - Short text (< 40 chars)
|
||||
// - Might be in brackets [Category]
|
||||
// - Might be all caps
|
||||
// - Might match category pattern
|
||||
const looksLikeCategory =
|
||||
trimmed.length < 40 && (
|
||||
/^\[.+\]$/.test(trimmed) || // [Category]
|
||||
trimmed === trimmed.toUpperCase() || // ALL CAPS
|
||||
/^[A-ZÄÖÜ][a-zäöüß]+(\s+[A-ZÄÖÜ][a-zäöüß]+)*$/.test(trimmed) // Title Case
|
||||
);
|
||||
|
||||
if (looksLikeCategory && !category) {
|
||||
// Remove brackets if present
|
||||
category = trimmed.replace(/^\[|\]$/g, "");
|
||||
} else if (!description && trimmed.length > 0) {
|
||||
// First substantial cell is likely description
|
||||
// Remove markdown formatting but keep content
|
||||
description = trimmed
|
||||
.replace(/\*\*/g, "") // Remove bold
|
||||
.replace(/\*/g, "") // Remove italic
|
||||
.replace(/`/g, "") // Remove code
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if aliases cell contains "(Kein Alias)"
|
||||
const hasNoAliases = /\(Kein Alias\)/i.test(line);
|
||||
|
||||
|
|
@ -76,11 +133,16 @@ export function parseEdgeVocabulary(md: string): EdgeVocabulary {
|
|||
}
|
||||
}
|
||||
|
||||
// Store canonical entry
|
||||
// Store canonical entry with description and category
|
||||
// Use currentCategory from H3 heading if available, otherwise use extracted category
|
||||
const finalCategory = currentCategory || category;
|
||||
|
||||
byCanonical.set(canonical, {
|
||||
canonical,
|
||||
inverse,
|
||||
aliases,
|
||||
description,
|
||||
category: finalCategory,
|
||||
});
|
||||
|
||||
// Build alias-to-canonical mapping (case-insensitive keys)
|
||||
|
|
@ -95,7 +157,9 @@ export function parseEdgeVocabulary(md: string): EdgeVocabulary {
|
|||
}
|
||||
|
||||
if (skippedRows > 0) {
|
||||
console.warn(`parseEdgeVocabulary: Skipped ${skippedRows} rows with insufficient tokens`);
|
||||
// Only warn if there are actually problematic rows (not just header/separator rows)
|
||||
// Header and separator rows are expected and should not trigger warnings
|
||||
console.debug(`parseEdgeVocabulary: Skipped ${skippedRows} data rows with insufficient tokens (this is normal if the file contains empty or malformed table rows)`);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export interface EdgeTypeEntry {
|
|||
canonical: CanonicalEdgeType;
|
||||
inverse?: CanonicalEdgeType;
|
||||
aliases: string[];
|
||||
description?: string; // Description from vocabulary table
|
||||
category?: string; // Category/group from vocabulary table
|
||||
}
|
||||
|
||||
export interface EdgeVocabulary {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user