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