From b5f2c8fc6764f49ea81ae9f992318b37eb3b2790 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 16 Jan 2026 22:32:36 +0100 Subject: [PATCH] Enhance Interview Wizard Modal with improved preview and editing functionality - Added a "Back to Edit" button wrapper in preview mode for easier navigation back to editing. - Updated preview rendering logic to ensure the back button visibility aligns with the current mode. - Enhanced textarea handling to retrieve and update draft values more effectively when toggling between preview and edit modes. - Improved error handling and fallback rendering for the Markdown preview, ensuring a better user experience during content display. - Refactored existing rendering methods to maintain structure while clearing content, enhancing performance and reliability. --- src/ui/InterviewWizardModal.ts | 262 +++++++++++++++++++++++++++------ 1 file changed, 219 insertions(+), 43 deletions(-) diff --git a/src/ui/InterviewWizardModal.ts b/src/ui/InterviewWizardModal.ts index dab5503..59b9188 100644 --- a/src/ui/InterviewWizardModal.ts +++ b/src/ui/InterviewWizardModal.ts @@ -325,6 +325,35 @@ export class InterviewWizardModal extends Modal { previewContainer.style.borderRadius = "4px"; previewContainer.style.background = "var(--background-primary)"; previewContainer.style.overflowY = "auto"; + previewContainer.style.position = "relative"; + + // Add "Back to Edit" button wrapper (outside preview content, so it doesn't get cleared) + const backToEditWrapper = editorContainer.createEl("div", { + cls: "preview-back-button-wrapper", + }); + backToEditWrapper.style.display = isPreviewMode ? "block" : "none"; + backToEditWrapper.style.position = "absolute"; + backToEditWrapper.style.top = "0.5em"; + backToEditWrapper.style.right = "0.5em"; + backToEditWrapper.style.zIndex = "20"; + + const backToEditBtn = backToEditWrapper.createEl("button", { + text: "✏️ Zurück zum Bearbeiten", + cls: "mod-cta", + }); + backToEditBtn.onclick = () => { + // Get current value from stored input values + const currentValue = this.currentInputValues.get(step.key) || existingValue; + // Update stored value + this.currentInputValues.set(step.key, String(currentValue)); + this.state.collectedData.set(step.key, currentValue); + + // Toggle preview mode off + this.previewMode.set(step.key, false); + + // Re-render to show editor + this.renderStep(); + }; // Editor container const textEditorContainer = editorContainer.createEl("div", { @@ -376,10 +405,36 @@ export class InterviewWizardModal extends Modal { if (textarea) { const toolbar = createMarkdownToolbar( textarea, - () => { + async () => { + // Get current value from textarea before toggling + let currentValue = textarea.value; + // If textarea is empty, try to get from stored values + if (!currentValue || currentValue.trim() === "") { + currentValue = this.currentInputValues.get(step.key) || existingValue || ""; + } + // Update stored value + this.currentInputValues.set(step.key, currentValue); + this.state.collectedData.set(step.key, currentValue); + + // Toggle preview mode const newPreviewMode = !this.previewMode.get(step.key); this.previewMode.set(step.key, newPreviewMode); - this.renderStep(); + + // If switching to preview mode, render preview immediately + if (newPreviewMode) { + // Update preview container visibility + previewContainer.style.display = "block"; + textEditorContainer.style.display = "none"; + backToEditWrapper.style.display = "block"; + // Render preview content (use existingValue as fallback) + const valueToRender = currentValue || existingValue || ""; + await this.updatePreview(previewContainer, valueToRender); + } else { + // Switching back to edit mode + previewContainer.style.display = "none"; + textEditorContainer.style.display = "block"; + backToEditWrapper.style.display = "none"; + } } ); textEditorContainer.insertBefore(toolbar, textEditorContainer.firstChild); @@ -388,7 +443,10 @@ export class InterviewWizardModal extends Modal { // Render preview if in preview mode if (isPreviewMode && existingValue) { - this.updatePreview(previewContainer, existingValue); + this.updatePreview(previewContainer, existingValue).then(() => { + // After preview is rendered, ensure back button is visible + backToEditWrapper.style.display = "block"; + }); } } @@ -526,28 +584,91 @@ export class InterviewWizardModal extends Modal { container: HTMLElement, markdown: string ): Promise { - container.empty(); - if (!markdown.trim()) { - container.createEl("p", { + console.log("updatePreview called", { + markdownLength: markdown?.length || 0, + markdownPreview: markdown?.substring(0, 100) || "(empty)", + containerExists: !!container, + containerId: container.id || "no-id", + containerClasses: container.className, + }); + + // Clear container but keep structure + const existingChildren = Array.from(container.children); + for (const child of existingChildren) { + child.remove(); + } + + if (!markdown || !markdown.trim()) { + const emptyEl = container.createEl("p", { text: "(empty)", cls: "text-muted", }); + console.log("Preview: showing empty message"); return; } // Use Obsidian's MarkdownRenderer // Create a component for the renderer const component = new Component(); - // Register it with the modal (Modal extends Component) - (this as any).addChild(component); - await MarkdownRenderer.render( - this.app, - markdown, - container, - this.file.path, - component - ); + try { + console.log("Calling MarkdownRenderer.render", { + markdownLength: markdown.length, + filePath: this.file.path, + containerTag: container.tagName, + }); + + // IMPORTANT: Component must be loaded before rendering + // This ensures proper lifecycle management + component.load(); + + await MarkdownRenderer.render( + this.app, + markdown, + container, + this.file.path, + component + ); + + console.log("Preview rendered successfully", { + containerChildren: container.children.length, + containerHTML: container.innerHTML.substring(0, 200), + }); + + // If container is still empty after rendering, try alternative approach + if (container.children.length === 0) { + console.warn("Container is empty after MarkdownRenderer.render, trying alternative"); + // Fallback: create a simple div with the markdown as HTML (basic rendering) + const fallbackDiv = container.createEl("div", { + cls: "markdown-preview-fallback", + }); + // Simple markdown to HTML conversion (very basic) + const html = markdown + .replace(/\n\n/g, "

") + .replace(/\n/g, "
") + .replace(/\*\*(.+?)\*\*/g, "$1") + .replace(/\*(.+?)\*/g, "$1") + .replace(/`(.+?)`/g, "$1"); + fallbackDiv.innerHTML = `

${html}

`; + console.log("Fallback preview rendered"); + } + + // Clean up component when done (optional, but good practice) + // Component will be cleaned up when modal closes + } catch (error) { + console.error("Error rendering preview", error); + const errorEl = container.createEl("p", { + text: `Error rendering preview: ${String(error)}`, + cls: "text-error", + }); + // Also show the raw markdown for debugging + const rawEl = container.createEl("pre", { + text: markdown, + cls: "text-muted", + }); + rawEl.style.fontSize = "0.8em"; + rawEl.style.overflow = "auto"; + } } renderCaptureFrontmatterStep( @@ -1091,29 +1212,33 @@ export class InterviewWizardModal extends Modal { previewContainer.style.overflowY = "auto"; previewContainer.style.position = "relative"; - // Add "Back to Edit" button in preview container - if (isPreviewMode) { - const backToEditBtn = previewContainer.createEl("button", { - text: "✏️ Zurück zum Bearbeiten", - cls: "mod-cta", - }); - backToEditBtn.style.position = "absolute"; - backToEditBtn.style.top = "0.5em"; - backToEditBtn.style.right = "0.5em"; - backToEditBtn.style.zIndex = "10"; - backToEditBtn.onclick = () => { - // Get current value from preview (it's already in draft) - const currentValue = existingValue; - // Update draft with current value - onFieldChange(nestedStep.key, currentValue); - - // Toggle preview mode off - this.previewMode.set(previewKey, false); - - // Re-render to show editor - this.renderStep(); - }; - } + // Add "Back to Edit" button wrapper (outside preview content, so it doesn't get cleared) + const backToEditWrapper = editorContainer.createEl("div", { + cls: "preview-back-button-wrapper", + }); + backToEditWrapper.style.display = isPreviewMode ? "block" : "none"; + backToEditWrapper.style.position = "absolute"; + backToEditWrapper.style.top = "0.5em"; + backToEditWrapper.style.right = "0.5em"; + backToEditWrapper.style.zIndex = "20"; + + const backToEditBtn = backToEditWrapper.createEl("button", { + text: "✏️ Zurück zum Bearbeiten", + cls: "mod-cta", + }); + backToEditBtn.onclick = () => { + // Get current value from draft (it's already saved) + const currentLoopState = this.state.loopRuntimeStates.get(loopKey); + const currentValue = currentLoopState?.draft[nestedStep.key] || existingValue; + // Update draft with current value (ensure it's saved) + onFieldChange(nestedStep.key, String(currentValue)); + + // Toggle preview mode off + this.previewMode.set(previewKey, false); + + // Re-render to show editor + this.renderStep(); + }; // Editor container const textEditorContainer = editorContainer.createEl("div", { @@ -1158,18 +1283,66 @@ export class InterviewWizardModal extends Modal { if (textarea) { const itemToolbar = createMarkdownToolbar( textarea, - () => { + async () => { // Get current value from textarea before toggling - const currentValue = textarea.value; + let currentValue = textarea.value; + console.log("Preview toggle clicked (loop)", { + textareaValue: currentValue, + textareaValueLength: currentValue?.length || 0, + existingValue: existingValue, + existingValueLength: existingValue?.length || 0, + previewKey: previewKey, + loopKey: loopKey, + nestedStepKey: nestedStep.key, + }); + + // If textarea is empty, try to get from draft + if (!currentValue || currentValue.trim() === "") { + const currentLoopState = this.state.loopRuntimeStates.get(loopKey); + console.log("Textarea empty, checking draft", { + loopStateExists: !!currentLoopState, + draftValue: currentLoopState?.draft[nestedStep.key], + }); + if (currentLoopState) { + const draftValue = currentLoopState.draft[nestedStep.key]; + if (draftValue) { + currentValue = String(draftValue); + console.log("Using draft value", { draftValue: currentValue }); + } + } + } + // Update draft with current value onFieldChange(nestedStep.key, currentValue); // Toggle preview mode const newPreviewMode = !this.previewMode.get(previewKey); this.previewMode.set(previewKey, newPreviewMode); + console.log("Preview mode toggled", { + newPreviewMode: newPreviewMode, + previewKey: previewKey, + }); - // Re-render to show/hide preview - this.renderStep(); + // If switching to preview mode, render preview immediately + if (newPreviewMode) { + // Update preview container visibility + previewContainer.style.display = "block"; + textEditorContainer.style.display = "none"; + backToEditWrapper.style.display = "block"; + // Render preview content (use existingValue as fallback) + const valueToRender = currentValue || existingValue || ""; + console.log("Rendering preview", { + valueToRender: valueToRender, + valueLength: valueToRender.length, + valuePreview: valueToRender.substring(0, 100), + }); + await this.updatePreview(previewContainer, valueToRender); + } else { + // Switching back to edit mode + previewContainer.style.display = "none"; + textEditorContainer.style.display = "block"; + backToEditWrapper.style.display = "none"; + } } ); textEditorContainer.insertBefore(itemToolbar, textEditorContainer.firstChild); @@ -1179,7 +1352,10 @@ export class InterviewWizardModal extends Modal { // Render preview if in preview mode if (isPreviewMode && existingValue) { - this.updatePreview(previewContainer, existingValue); + this.updatePreview(previewContainer, existingValue).then(() => { + // After preview is rendered, ensure back button is visible + backToEditWrapper.style.display = "block"; + }); } } else if (nestedStep.type === "capture_text_line") { // Field container