/** * Modal for prompting edge type assignment for a single link. */ import { Modal } from "obsidian"; import type { LinkWorkItem } from "../mapping/worklistBuilder"; import type { EdgeVocabulary } from "../vocab/types"; import type { GraphSchema } from "../mapping/graphSchema"; import { EdgeTypeChooserModal, type EdgeTypeChoice } from "./EdgeTypeChooserModal"; import { computeEdgeSuggestions } from "../mapping/schemaHelper"; export type LinkPromptDecision = | { action: "keep"; edgeType: string } | { action: "change"; edgeType: string; alias?: string } | { action: "skip" }; export class LinkPromptModal extends Modal { private item: LinkWorkItem; private vocabulary: EdgeVocabulary; private sourceType: string | null; private graphSchema: GraphSchema | null; private result: LinkPromptDecision | null = null; private resolve: ((result: LinkPromptDecision) => void) | null = null; constructor( app: any, item: LinkWorkItem, vocabulary: EdgeVocabulary, sourceType: string | null, graphSchema: GraphSchema | null = null ) { super(app); this.item = item; this.vocabulary = vocabulary; this.sourceType = sourceType; this.graphSchema = graphSchema; } onOpen(): void { const { contentEl } = this; contentEl.empty(); contentEl.addClass("link-prompt-modal"); // Link info const linkInfo = contentEl.createEl("div", { cls: "link-info" }); linkInfo.createEl("h2", { text: `Link: [[${this.item.link}]]` }); if (this.item.targetType) { linkInfo.createEl("p", { text: `Target type: ${this.item.targetType}` }); } // Current mapping (if exists) if (this.item.currentType) { const currentInfo = linkInfo.createEl("p", { cls: "current-mapping" }); currentInfo.textContent = `Current edge type: ${this.item.currentType}`; // Check if current type is prohibited if (this.graphSchema) { const suggestions = computeEdgeSuggestions( this.vocabulary, this.sourceType, this.item.targetType, this.graphSchema ); if (suggestions.prohibited.includes(this.item.currentType)) { const warning = linkInfo.createEl("span", { text: " ⚠️ Prohibited", cls: "prohibited-warning", }); warning.style.color = "var(--text-error)"; warning.style.fontWeight = "bold"; } } } // Action buttons const buttonContainer = contentEl.createEl("div", { cls: "action-buttons" }); buttonContainer.style.display = "flex"; buttonContainer.style.flexDirection = "column"; buttonContainer.style.gap = "0.5em"; buttonContainer.style.marginTop = "1em"; if (this.item.currentType) { // Keep current const keepBtn = buttonContainer.createEl("button", { text: `✅ Keep (${this.item.currentType})`, cls: "mod-cta", }); keepBtn.onclick = () => { this.result = { action: "keep", edgeType: this.item.currentType! }; this.close(); }; // Change type const changeBtn = buttonContainer.createEl("button", { text: "Change type...", }); changeBtn.onclick = async () => { const chooser = new EdgeTypeChooserModal( this.app, this.vocabulary, this.sourceType, this.item.targetType, this.graphSchema ); const choice = await chooser.show(); if (choice) { this.result = { action: "change", edgeType: choice.edgeType, alias: choice.alias, }; this.close(); } }; // Skip const skipBtn = buttonContainer.createEl("button", { text: "Skip link", }); skipBtn.onclick = () => { this.result = { action: "skip" }; this.close(); }; } else { // No current mapping // Skip const skipBtn = buttonContainer.createEl("button", { text: "⏩ Skip link", }); skipBtn.onclick = () => { this.result = { action: "skip" }; this.close(); }; // Choose type const chooseBtn = buttonContainer.createEl("button", { text: "Choose type...", cls: "mod-cta", }); chooseBtn.onclick = async () => { const chooser = new EdgeTypeChooserModal( this.app, this.vocabulary, this.sourceType, this.item.targetType, this.graphSchema ); const choice = await chooser.show(); if (choice) { this.result = { action: "change", edgeType: choice.edgeType, alias: choice.alias, }; this.close(); } }; } } onClose(): void { const { contentEl } = this; contentEl.empty(); if (this.resolve && this.result) { this.resolve(this.result); } else if (this.resolve) { // User closed without decision, treat as skip this.resolve({ action: "skip" }); } } /** * Show modal and return promise that resolves with user's decision. */ async show(): Promise { return new Promise((resolve) => { this.resolve = resolve; this.open(); }); } }