Implement Interview Wizard Modal enhancements with new CSS styles and markdown support
- Added comprehensive CSS styles for the Interview Wizard Modal, improving layout and responsiveness. - Introduced markdown editing capabilities with a toolbar for text areas, allowing for rich text input. - Enhanced step rendering logic to support optional prompt text for each step. - Updated the modal structure to include sticky navigation and improved content organization. - Refactored existing rendering methods to utilize new CSS classes for better styling consistency.
This commit is contained in:
parent
bab84549e2
commit
d7aa9bd964
|
|
@ -49,6 +49,7 @@ export interface CaptureFrontmatterStep {
|
||||||
label?: string;
|
label?: string;
|
||||||
field: string;
|
field: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
prompt?: string; // Optional prompt text
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CaptureTextStep {
|
export interface CaptureTextStep {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
|
Component,
|
||||||
|
MarkdownRenderer,
|
||||||
Modal,
|
Modal,
|
||||||
Notice,
|
Notice,
|
||||||
Setting,
|
Setting,
|
||||||
|
|
@ -20,6 +22,11 @@ import {
|
||||||
flattenSteps,
|
flattenSteps,
|
||||||
} from "../interview/wizardState";
|
} from "../interview/wizardState";
|
||||||
import { extractFrontmatterId } from "../parser/parseFrontmatter";
|
import { extractFrontmatterId } from "../parser/parseFrontmatter";
|
||||||
|
import {
|
||||||
|
createMarkdownToolbar,
|
||||||
|
applyMarkdownWrap,
|
||||||
|
applyLinePrefix,
|
||||||
|
} from "./markdownToolbar";
|
||||||
|
|
||||||
export interface WizardResult {
|
export interface WizardResult {
|
||||||
applied: boolean;
|
applied: boolean;
|
||||||
|
|
@ -35,6 +42,8 @@ export class InterviewWizardModal extends Modal {
|
||||||
profileKey: string;
|
profileKey: string;
|
||||||
// Store current input values to save on navigation
|
// Store current input values to save on navigation
|
||||||
private currentInputValues: Map<string, string> = new Map();
|
private currentInputValues: Map<string, string> = new Map();
|
||||||
|
// Store preview mode state per step key
|
||||||
|
private previewMode: Map<string, boolean> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
app: App,
|
app: App,
|
||||||
|
|
@ -94,6 +103,9 @@ export class InterviewWizardModal extends Modal {
|
||||||
onOpen(): void {
|
onOpen(): void {
|
||||||
const fileName = this.file.basename || this.file.name.replace(/\.md$/, "");
|
const fileName = this.file.basename || this.file.name.replace(/\.md$/, "");
|
||||||
|
|
||||||
|
// Add CSS class for styling
|
||||||
|
this.modalEl.addClass("mindnet-wizard-modal");
|
||||||
|
|
||||||
console.log("=== WIZARD START ===", {
|
console.log("=== WIZARD START ===", {
|
||||||
profileKey: this.profileKey,
|
profileKey: this.profileKey,
|
||||||
file: this.file.path,
|
file: this.file.path,
|
||||||
|
|
@ -108,6 +120,9 @@ export class InterviewWizardModal extends Modal {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
|
|
||||||
|
// Apply flex layout structure
|
||||||
|
contentEl.addClass("modal-content");
|
||||||
|
|
||||||
const step = getCurrentStep(this.state);
|
const step = getCurrentStep(this.state);
|
||||||
|
|
||||||
console.log("Render step", {
|
console.log("Render step", {
|
||||||
|
|
@ -141,11 +156,16 @@ export class InterviewWizardModal extends Modal {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create body container for scrollable content
|
||||||
|
const bodyEl = contentEl.createEl("div", {
|
||||||
|
cls: "modal-content-body",
|
||||||
|
});
|
||||||
|
|
||||||
// Check if ID exists
|
// Check if ID exists
|
||||||
const hasId = this.checkIdExists();
|
const hasId = this.checkIdExists();
|
||||||
|
|
||||||
if (!hasId) {
|
if (!hasId) {
|
||||||
const warningEl = contentEl.createEl("div", {
|
const warningEl = bodyEl.createEl("div", {
|
||||||
cls: "interview-warning",
|
cls: "interview-warning",
|
||||||
});
|
});
|
||||||
warningEl.createEl("p", {
|
warningEl.createEl("p", {
|
||||||
|
|
@ -158,30 +178,38 @@ export class InterviewWizardModal extends Modal {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create step content container
|
||||||
|
const stepContentEl = bodyEl.createEl("div", {
|
||||||
|
cls: "step-content",
|
||||||
|
});
|
||||||
|
|
||||||
// Render step based on type
|
// Render step based on type
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case "instruction":
|
case "instruction":
|
||||||
this.renderInstructionStep(step, contentEl);
|
this.renderInstructionStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
case "capture_text":
|
case "capture_text":
|
||||||
this.renderCaptureTextStep(step, contentEl);
|
this.renderCaptureTextStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
case "capture_frontmatter":
|
case "capture_frontmatter":
|
||||||
this.renderCaptureFrontmatterStep(step, contentEl);
|
this.renderCaptureFrontmatterStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
case "loop":
|
case "loop":
|
||||||
this.renderLoopStep(step, contentEl);
|
this.renderLoopStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
case "llm_dialog":
|
case "llm_dialog":
|
||||||
this.renderLLMDialogStep(step, contentEl);
|
this.renderLLMDialogStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
case "review":
|
case "review":
|
||||||
this.renderReviewStep(step, contentEl);
|
this.renderReviewStep(step, stepContentEl);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation buttons
|
// Navigation buttons in sticky footer
|
||||||
this.renderNavigation(contentEl);
|
const footerEl = contentEl.createEl("div", {
|
||||||
|
cls: "modal-content-footer",
|
||||||
|
});
|
||||||
|
this.renderNavigation(footerEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIdExists(): boolean {
|
checkIdExists(): boolean {
|
||||||
|
|
@ -229,19 +257,73 @@ export class InterviewWizardModal extends Modal {
|
||||||
|
|
||||||
const existingValue =
|
const existingValue =
|
||||||
(this.state.collectedData.get(step.key) as string) || "";
|
(this.state.collectedData.get(step.key) as string) || "";
|
||||||
|
const isPreviewMode = this.previewMode.get(step.key) || false;
|
||||||
|
|
||||||
console.log("Render capture_text step", {
|
console.log("Render capture_text step", {
|
||||||
stepKey: step.key,
|
stepKey: step.key,
|
||||||
stepLabel: step.label,
|
stepLabel: step.label,
|
||||||
existingValue: existingValue,
|
existingValue: existingValue,
|
||||||
valueLength: existingValue.length,
|
valueLength: existingValue.length,
|
||||||
|
isPreviewMode: isPreviewMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
containerEl.createEl("h2", {
|
// Field container with vertical layout
|
||||||
text: step.label || "Enter Text",
|
const fieldContainer = containerEl.createEl("div", {
|
||||||
|
cls: "mindnet-field",
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(containerEl).addTextArea((text) => {
|
// Label
|
||||||
|
if (step.label) {
|
||||||
|
const labelEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__label",
|
||||||
|
text: step.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description/Prompt
|
||||||
|
if (step.prompt) {
|
||||||
|
const descEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__desc",
|
||||||
|
text: step.prompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container for editor/preview
|
||||||
|
const editorContainer = fieldContainer.createEl("div", {
|
||||||
|
cls: "markdown-editor-container",
|
||||||
|
});
|
||||||
|
editorContainer.style.width = "100%";
|
||||||
|
editorContainer.style.position = "relative";
|
||||||
|
|
||||||
|
// Preview container (hidden by default)
|
||||||
|
const previewContainer = editorContainer.createEl("div", {
|
||||||
|
cls: "markdown-preview-container",
|
||||||
|
});
|
||||||
|
previewContainer.style.display = isPreviewMode ? "block" : "none";
|
||||||
|
previewContainer.style.width = "100%";
|
||||||
|
previewContainer.style.minHeight = "240px";
|
||||||
|
previewContainer.style.padding = "1em";
|
||||||
|
previewContainer.style.border = "1px solid var(--background-modifier-border)";
|
||||||
|
previewContainer.style.borderRadius = "4px";
|
||||||
|
previewContainer.style.background = "var(--background-primary)";
|
||||||
|
previewContainer.style.overflowY = "auto";
|
||||||
|
|
||||||
|
// Editor container
|
||||||
|
const textEditorContainer = editorContainer.createEl("div", {
|
||||||
|
cls: "markdown-editor-wrapper",
|
||||||
|
});
|
||||||
|
textEditorContainer.style.display = isPreviewMode ? "none" : "block";
|
||||||
|
textEditorContainer.style.width = "100%";
|
||||||
|
|
||||||
|
// Create textarea first
|
||||||
|
const textSetting = new Setting(textEditorContainer);
|
||||||
|
textSetting.settingEl.style.width = "100%";
|
||||||
|
textSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
let textareaRef: HTMLTextAreaElement | null = null;
|
||||||
|
|
||||||
|
textSetting.addTextArea((text) => {
|
||||||
|
textareaRef = text.inputEl;
|
||||||
text.setValue(existingValue);
|
text.setValue(existingValue);
|
||||||
// Store initial value
|
// Store initial value
|
||||||
this.currentInputValues.set(step.key, existingValue);
|
this.currentInputValues.set(step.key, existingValue);
|
||||||
|
|
@ -256,10 +338,71 @@ export class InterviewWizardModal extends Modal {
|
||||||
// Update stored value
|
// Update stored value
|
||||||
this.currentInputValues.set(step.key, value);
|
this.currentInputValues.set(step.key, value);
|
||||||
this.state.collectedData.set(step.key, value);
|
this.state.collectedData.set(step.key, value);
|
||||||
|
|
||||||
|
// Update preview if in preview mode
|
||||||
|
if (isPreviewMode) {
|
||||||
|
this.updatePreview(previewContainer, value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
text.inputEl.rows = 10;
|
text.inputEl.rows = 10;
|
||||||
|
text.inputEl.style.width = "100%";
|
||||||
|
text.inputEl.style.minHeight = "240px";
|
||||||
|
text.inputEl.style.boxSizing = "border-box";
|
||||||
text.inputEl.focus();
|
text.inputEl.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create toolbar after textarea is created
|
||||||
|
// Use setTimeout to ensure textarea is in DOM
|
||||||
|
setTimeout(() => {
|
||||||
|
const textarea = textEditorContainer.querySelector("textarea");
|
||||||
|
if (textarea) {
|
||||||
|
const toolbar = createMarkdownToolbar(
|
||||||
|
textarea,
|
||||||
|
() => {
|
||||||
|
const newPreviewMode = !this.previewMode.get(step.key);
|
||||||
|
this.previewMode.set(step.key, newPreviewMode);
|
||||||
|
this.renderStep();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
textEditorContainer.insertBefore(toolbar, textEditorContainer.firstChild);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// Render preview if in preview mode
|
||||||
|
if (isPreviewMode && existingValue) {
|
||||||
|
this.updatePreview(previewContainer, existingValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update preview container with rendered markdown.
|
||||||
|
*/
|
||||||
|
private async updatePreview(
|
||||||
|
container: HTMLElement,
|
||||||
|
markdown: string
|
||||||
|
): Promise<void> {
|
||||||
|
container.empty();
|
||||||
|
if (!markdown.trim()) {
|
||||||
|
container.createEl("p", {
|
||||||
|
text: "(empty)",
|
||||||
|
cls: "text-muted",
|
||||||
|
});
|
||||||
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCaptureFrontmatterStep(
|
renderCaptureFrontmatterStep(
|
||||||
|
|
@ -287,13 +430,45 @@ export class InterviewWizardModal extends Modal {
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
containerEl.createEl("h2", {
|
// Field container with vertical layout
|
||||||
text: step.label || `Enter ${step.field}`,
|
const fieldContainer = containerEl.createEl("div", {
|
||||||
|
cls: "mindnet-field",
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
// Label
|
||||||
.setName(step.field)
|
const labelText = step.label || step.field;
|
||||||
.addText((text) => {
|
if (labelText) {
|
||||||
|
const labelEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__label",
|
||||||
|
text: labelText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description/Prompt
|
||||||
|
if (step.prompt) {
|
||||||
|
const descEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__desc",
|
||||||
|
text: step.prompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input container
|
||||||
|
const inputContainer = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__input",
|
||||||
|
});
|
||||||
|
inputContainer.style.width = "100%";
|
||||||
|
|
||||||
|
const fieldSetting = new Setting(inputContainer);
|
||||||
|
fieldSetting.settingEl.style.width = "100%";
|
||||||
|
fieldSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
// Hide the default label from Setting component
|
||||||
|
const settingNameEl2 = fieldSetting.settingEl.querySelector(".setting-item-name") as HTMLElement | null;
|
||||||
|
if (settingNameEl2) {
|
||||||
|
settingNameEl2.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSetting.addText((text) => {
|
||||||
text.setValue(defaultValue);
|
text.setValue(defaultValue);
|
||||||
// Store initial value
|
// Store initial value
|
||||||
this.currentInputValues.set(step.key, defaultValue);
|
this.currentInputValues.set(step.key, defaultValue);
|
||||||
|
|
@ -324,6 +499,8 @@ export class InterviewWizardModal extends Modal {
|
||||||
this.state.patches.push(patch);
|
this.state.patches.push(patch);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
text.inputEl.style.width = "100%";
|
||||||
|
text.inputEl.style.boxSizing = "border-box";
|
||||||
text.inputEl.focus();
|
text.inputEl.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +527,7 @@ export class InterviewWizardModal extends Modal {
|
||||||
// Show existing items
|
// Show existing items
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
const itemsList = containerEl.createEl("div", { cls: "loop-items-list" });
|
const itemsList = containerEl.createEl("div", { cls: "loop-items-list" });
|
||||||
|
itemsList.style.width = "100%";
|
||||||
itemsList.createEl("h3", { text: `Gesammelte Items (${items.length}):` });
|
itemsList.createEl("h3", { text: `Gesammelte Items (${items.length}):` });
|
||||||
const ul = itemsList.createEl("ul");
|
const ul = itemsList.createEl("ul");
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
|
@ -383,7 +561,12 @@ export class InterviewWizardModal extends Modal {
|
||||||
|
|
||||||
// Render nested steps for current item
|
// Render nested steps for current item
|
||||||
if (step.items.length > 0) {
|
if (step.items.length > 0) {
|
||||||
containerEl.createEl("h3", {
|
const itemFormContainer = containerEl.createEl("div", {
|
||||||
|
cls: "loop-item-form",
|
||||||
|
});
|
||||||
|
itemFormContainer.style.width = "100%";
|
||||||
|
|
||||||
|
itemFormContainer.createEl("h3", {
|
||||||
text: items.length === 0 ? "First Item" : `Item ${items.length + 1}`,
|
text: items.length === 0 ? "First Item" : `Item ${items.length + 1}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -397,19 +580,50 @@ export class InterviewWizardModal extends Modal {
|
||||||
const existingValue = (itemData.get(nestedStep.key) as string) || "";
|
const existingValue = (itemData.get(nestedStep.key) as string) || "";
|
||||||
const inputKey = `${itemDataKey}_${nestedStep.key}`;
|
const inputKey = `${itemDataKey}_${nestedStep.key}`;
|
||||||
|
|
||||||
containerEl.createEl("h4", {
|
// Field container with vertical layout
|
||||||
text: nestedStep.label || nestedStep.key,
|
const fieldContainer = itemFormContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show prompt if available
|
// Label
|
||||||
if (nestedStep.prompt) {
|
if (nestedStep.label) {
|
||||||
containerEl.createEl("p", {
|
const labelEl = fieldContainer.createEl("div", {
|
||||||
text: nestedStep.prompt,
|
cls: "mindnet-field__label",
|
||||||
cls: "interview-prompt",
|
text: nestedStep.label,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
new Setting(containerEl).addTextArea((text) => {
|
// Description/Prompt
|
||||||
|
if (nestedStep.prompt) {
|
||||||
|
const descEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__desc",
|
||||||
|
text: nestedStep.prompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor container
|
||||||
|
const editorContainer = fieldContainer.createEl("div", {
|
||||||
|
cls: "markdown-editor-container",
|
||||||
|
});
|
||||||
|
editorContainer.style.width = "100%";
|
||||||
|
editorContainer.style.position = "relative";
|
||||||
|
|
||||||
|
const textEditorContainer = editorContainer.createEl("div", {
|
||||||
|
cls: "markdown-editor-wrapper",
|
||||||
|
});
|
||||||
|
textEditorContainer.style.width = "100%";
|
||||||
|
|
||||||
|
const textSetting = new Setting(textEditorContainer);
|
||||||
|
textSetting.settingEl.style.width = "100%";
|
||||||
|
textSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
// Hide the default label from Setting component
|
||||||
|
const settingNameEl = textSetting.settingEl.querySelector(".setting-item-name") as HTMLElement;
|
||||||
|
if (settingNameEl) {
|
||||||
|
settingNameEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
textSetting.addTextArea((text) => {
|
||||||
text.setValue(existingValue);
|
text.setValue(existingValue);
|
||||||
this.currentInputValues.set(inputKey, existingValue);
|
this.currentInputValues.set(inputKey, existingValue);
|
||||||
|
|
||||||
|
|
@ -419,19 +633,61 @@ export class InterviewWizardModal extends Modal {
|
||||||
});
|
});
|
||||||
text.inputEl.rows = 10;
|
text.inputEl.rows = 10;
|
||||||
text.inputEl.style.width = "100%";
|
text.inputEl.style.width = "100%";
|
||||||
text.inputEl.style.minHeight = "150px";
|
text.inputEl.style.minHeight = "200px";
|
||||||
|
text.inputEl.style.boxSizing = "border-box";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add toolbar for loop item textarea
|
||||||
|
setTimeout(() => {
|
||||||
|
const textarea = textEditorContainer.querySelector("textarea");
|
||||||
|
if (textarea) {
|
||||||
|
const itemToolbar = createMarkdownToolbar(textarea);
|
||||||
|
textEditorContainer.insertBefore(itemToolbar, textEditorContainer.firstChild);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
} else if (nestedStep.type === "capture_frontmatter") {
|
} else if (nestedStep.type === "capture_frontmatter") {
|
||||||
const existingValue = (itemData.get(nestedStep.key) as string) || "";
|
const existingValue = (itemData.get(nestedStep.key) as string) || "";
|
||||||
const inputKey = `${itemDataKey}_${nestedStep.key}`;
|
const inputKey = `${itemDataKey}_${nestedStep.key}`;
|
||||||
|
|
||||||
containerEl.createEl("h4", {
|
// Field container with vertical layout
|
||||||
text: nestedStep.label || nestedStep.field || nestedStep.key,
|
const fieldContainer = itemFormContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field",
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
// Label
|
||||||
.setName(nestedStep.field || nestedStep.key)
|
const labelText = nestedStep.label || nestedStep.field || nestedStep.key;
|
||||||
.addText((text) => {
|
if (labelText) {
|
||||||
|
const labelEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__label",
|
||||||
|
text: labelText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description/Prompt
|
||||||
|
if (nestedStep.prompt) {
|
||||||
|
const descEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__desc",
|
||||||
|
text: nestedStep.prompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input container
|
||||||
|
const inputContainer = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__input",
|
||||||
|
});
|
||||||
|
inputContainer.style.width = "100%";
|
||||||
|
|
||||||
|
const fieldSetting = new Setting(inputContainer);
|
||||||
|
fieldSetting.settingEl.style.width = "100%";
|
||||||
|
fieldSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
// Hide the default label from Setting component
|
||||||
|
const settingNameEl = fieldSetting.settingEl.querySelector(".setting-item-name") as HTMLElement | null;
|
||||||
|
if (settingNameEl) {
|
||||||
|
settingNameEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSetting.addText((text) => {
|
||||||
text.setValue(existingValue);
|
text.setValue(existingValue);
|
||||||
this.currentInputValues.set(inputKey, existingValue);
|
this.currentInputValues.set(inputKey, existingValue);
|
||||||
|
|
||||||
|
|
@ -439,12 +695,16 @@ export class InterviewWizardModal extends Modal {
|
||||||
this.currentInputValues.set(inputKey, value);
|
this.currentInputValues.set(inputKey, value);
|
||||||
itemData.set(nestedStep.key, value);
|
itemData.set(nestedStep.key, value);
|
||||||
});
|
});
|
||||||
|
text.inputEl.style.width = "100%";
|
||||||
|
text.inputEl.style.boxSizing = "border-box";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add item button
|
// Add item button
|
||||||
new Setting(containerEl).addButton((button) => {
|
const addButtonSetting = new Setting(itemFormContainer);
|
||||||
|
addButtonSetting.settingEl.style.width = "100%";
|
||||||
|
addButtonSetting.addButton((button) => {
|
||||||
button
|
button
|
||||||
.setButtonText("Add Item")
|
.setButtonText("Add Item")
|
||||||
.setCta()
|
.setCta()
|
||||||
|
|
@ -493,7 +753,7 @@ export class InterviewWizardModal extends Modal {
|
||||||
|
|
||||||
// Show hint if no items yet
|
// Show hint if no items yet
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
containerEl.createEl("p", {
|
itemFormContainer.createEl("p", {
|
||||||
text: "⚠️ Please add at least one item before continuing",
|
text: "⚠️ Please add at least one item before continuing",
|
||||||
cls: "interview-warning",
|
cls: "interview-warning",
|
||||||
});
|
});
|
||||||
|
|
@ -504,26 +764,72 @@ export class InterviewWizardModal extends Modal {
|
||||||
renderLLMDialogStep(step: InterviewStep, containerEl: HTMLElement): void {
|
renderLLMDialogStep(step: InterviewStep, containerEl: HTMLElement): void {
|
||||||
if (step.type !== "llm_dialog") return;
|
if (step.type !== "llm_dialog") return;
|
||||||
|
|
||||||
containerEl.createEl("h2", {
|
// Field container with vertical layout
|
||||||
text: step.label || "LLM Dialog",
|
const fieldContainer = containerEl.createEl("div", {
|
||||||
|
cls: "mindnet-field",
|
||||||
});
|
});
|
||||||
|
|
||||||
containerEl.createEl("p", {
|
// Label
|
||||||
|
if (step.label) {
|
||||||
|
const labelEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__label",
|
||||||
|
text: step.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description/Prompt
|
||||||
|
if (step.prompt) {
|
||||||
|
const descEl = fieldContainer.createEl("div", {
|
||||||
|
cls: "mindnet-field__desc",
|
||||||
text: `Prompt: ${step.prompt}`,
|
text: `Prompt: ${step.prompt}`,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const existingValue =
|
const existingValue =
|
||||||
(this.state.collectedData.get(step.key) as string) || "";
|
(this.state.collectedData.get(step.key) as string) || "";
|
||||||
|
|
||||||
new Setting(containerEl)
|
// Editor container for LLM response
|
||||||
.setName("Response")
|
const llmEditorContainer = fieldContainer.createEl("div", {
|
||||||
.addTextArea((text) => {
|
cls: "markdown-editor-wrapper",
|
||||||
text.setValue(existingValue).onChange((value) => {
|
});
|
||||||
|
llmEditorContainer.style.width = "100%";
|
||||||
|
|
||||||
|
const llmSetting = new Setting(llmEditorContainer);
|
||||||
|
llmSetting.settingEl.style.width = "100%";
|
||||||
|
llmSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
// Hide the default label from Setting component
|
||||||
|
const settingNameEl = llmSetting.settingEl.querySelector(".setting-item-name") as HTMLElement;
|
||||||
|
if (settingNameEl) {
|
||||||
|
settingNameEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
let llmTextareaRef: HTMLTextAreaElement | null = null;
|
||||||
|
|
||||||
|
llmSetting.addTextArea((text) => {
|
||||||
|
llmTextareaRef = text.inputEl;
|
||||||
|
text.setValue(existingValue);
|
||||||
|
this.currentInputValues.set(step.key, existingValue);
|
||||||
|
|
||||||
|
text.onChange((value) => {
|
||||||
|
this.currentInputValues.set(step.key, value);
|
||||||
this.state.collectedData.set(step.key, value);
|
this.state.collectedData.set(step.key, value);
|
||||||
});
|
});
|
||||||
text.inputEl.rows = 10;
|
text.inputEl.rows = 10;
|
||||||
|
text.inputEl.style.width = "100%";
|
||||||
|
text.inputEl.style.minHeight = "240px";
|
||||||
|
text.inputEl.style.boxSizing = "border-box";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add toolbar for LLM response
|
||||||
|
setTimeout(() => {
|
||||||
|
const textarea = llmEditorContainer.querySelector("textarea");
|
||||||
|
if (textarea) {
|
||||||
|
const llmToolbar = createMarkdownToolbar(textarea);
|
||||||
|
llmEditorContainer.insertBefore(llmToolbar, llmEditorContainer.firstChild);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
|
||||||
containerEl.createEl("p", {
|
containerEl.createEl("p", {
|
||||||
text: "Note: LLM dialog requires manual input in this version",
|
text: "Note: LLM dialog requires manual input in this version",
|
||||||
cls: "interview-note",
|
cls: "interview-note",
|
||||||
|
|
@ -564,9 +870,14 @@ export class InterviewWizardModal extends Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNavigation(containerEl: HTMLElement): void {
|
renderNavigation(containerEl: HTMLElement): void {
|
||||||
|
// Navigation buttons in a flex row
|
||||||
const navContainer = containerEl.createEl("div", {
|
const navContainer = containerEl.createEl("div", {
|
||||||
cls: "interview-navigation",
|
cls: "interview-navigation",
|
||||||
});
|
});
|
||||||
|
navContainer.style.display = "flex";
|
||||||
|
navContainer.style.gap = "0.5em";
|
||||||
|
navContainer.style.justifyContent = "flex-end";
|
||||||
|
navContainer.style.flexWrap = "wrap";
|
||||||
|
|
||||||
// Back button
|
// Back button
|
||||||
new Setting(navContainer)
|
new Setting(navContainer)
|
||||||
|
|
|
||||||
317
src/ui/markdownToolbar.ts
Normal file
317
src/ui/markdownToolbar.ts
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
/**
|
||||||
|
* Markdown toolbar helpers for text editors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply markdown formatting to selected text in textarea.
|
||||||
|
* If no selection, inserts before/after at cursor position.
|
||||||
|
*/
|
||||||
|
export function applyMarkdownWrap(
|
||||||
|
textarea: HTMLTextAreaElement,
|
||||||
|
before: string,
|
||||||
|
after: string = ""
|
||||||
|
): void {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const selectedText = text.substring(start, end);
|
||||||
|
|
||||||
|
let newText: string;
|
||||||
|
let newCursorPos: number;
|
||||||
|
|
||||||
|
if (selectedText) {
|
||||||
|
// Wrap selected text
|
||||||
|
newText = text.substring(0, start) + before + selectedText + after + text.substring(end);
|
||||||
|
newCursorPos = start + before.length + selectedText.length + after.length;
|
||||||
|
} else {
|
||||||
|
// Insert at cursor position
|
||||||
|
newText = text.substring(0, start) + before + after + text.substring(end);
|
||||||
|
newCursorPos = start + before.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.value = newText;
|
||||||
|
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
// Trigger change event
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply prefix to each selected line (for lists).
|
||||||
|
*/
|
||||||
|
export function applyLinePrefix(
|
||||||
|
textarea: HTMLTextAreaElement,
|
||||||
|
prefix: string
|
||||||
|
): void {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
|
||||||
|
// Find line boundaries
|
||||||
|
const beforeSelection = text.substring(0, start);
|
||||||
|
const selection = text.substring(start, end);
|
||||||
|
const afterSelection = text.substring(end);
|
||||||
|
|
||||||
|
const lineStart = beforeSelection.lastIndexOf("\n") + 1;
|
||||||
|
const lineEnd = afterSelection.indexOf("\n");
|
||||||
|
const fullLineEnd = end + (lineEnd >= 0 ? lineEnd : afterSelection.length);
|
||||||
|
|
||||||
|
// Get all lines in selection
|
||||||
|
const lines = selection.split("\n");
|
||||||
|
const firstLineStart = beforeSelection.lastIndexOf("\n") + 1;
|
||||||
|
const firstLinePrefix = text.substring(firstLineStart, start);
|
||||||
|
|
||||||
|
// Apply prefix to each line
|
||||||
|
const prefixedLines = lines.map((line) => {
|
||||||
|
if (line.trim()) {
|
||||||
|
return prefix + line;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSelection = prefixedLines.join("\n");
|
||||||
|
const newText =
|
||||||
|
text.substring(0, lineStart) + newSelection + text.substring(fullLineEnd);
|
||||||
|
|
||||||
|
textarea.value = newText;
|
||||||
|
|
||||||
|
// Restore selection
|
||||||
|
const newStart = lineStart;
|
||||||
|
const newEnd = lineStart + newSelection.length;
|
||||||
|
textarea.setSelectionRange(newStart, newEnd);
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
// Trigger change event
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove prefix from each selected line (for lists).
|
||||||
|
*/
|
||||||
|
export function removeLinePrefix(
|
||||||
|
textarea: HTMLTextAreaElement,
|
||||||
|
prefix: string
|
||||||
|
): void {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
|
||||||
|
// Find line boundaries
|
||||||
|
const beforeSelection = text.substring(0, start);
|
||||||
|
const selection = text.substring(start, end);
|
||||||
|
const afterSelection = text.substring(end);
|
||||||
|
|
||||||
|
const lineStart = beforeSelection.lastIndexOf("\n") + 1;
|
||||||
|
const lineEnd = afterSelection.indexOf("\n");
|
||||||
|
const fullLineEnd = end + (lineEnd >= 0 ? lineEnd : afterSelection.length);
|
||||||
|
|
||||||
|
// Get all lines in selection
|
||||||
|
const lines = selection.split("\n");
|
||||||
|
|
||||||
|
// Remove prefix from each line
|
||||||
|
const unprefixedLines = lines.map((line) => {
|
||||||
|
if (line.startsWith(prefix)) {
|
||||||
|
return line.substring(prefix.length);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSelection = unprefixedLines.join("\n");
|
||||||
|
const newText =
|
||||||
|
text.substring(0, lineStart) + newSelection + text.substring(fullLineEnd);
|
||||||
|
|
||||||
|
textarea.value = newText;
|
||||||
|
|
||||||
|
// Restore selection
|
||||||
|
const newStart = lineStart;
|
||||||
|
const newEnd = lineStart + newSelection.length;
|
||||||
|
textarea.setSelectionRange(newStart, newEnd);
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
// Trigger change event
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create markdown toolbar with buttons.
|
||||||
|
*/
|
||||||
|
export function createMarkdownToolbar(
|
||||||
|
textarea: HTMLTextAreaElement,
|
||||||
|
onTogglePreview?: () => void
|
||||||
|
): HTMLElement {
|
||||||
|
const toolbar = document.createElement("div");
|
||||||
|
toolbar.className = "markdown-toolbar";
|
||||||
|
toolbar.style.display = "flex";
|
||||||
|
toolbar.style.gap = "0.25em";
|
||||||
|
toolbar.style.padding = "0.5em";
|
||||||
|
toolbar.style.borderBottom = "1px solid var(--background-modifier-border)";
|
||||||
|
toolbar.style.flexWrap = "wrap";
|
||||||
|
toolbar.style.alignItems = "center";
|
||||||
|
|
||||||
|
// Bold
|
||||||
|
const boldBtn = createToolbarButton("B", "Bold (Ctrl+B)", () => {
|
||||||
|
applyMarkdownWrap(textarea, "**", "**");
|
||||||
|
});
|
||||||
|
toolbar.appendChild(boldBtn);
|
||||||
|
|
||||||
|
// Italic
|
||||||
|
const italicBtn = createToolbarButton("I", "Italic (Ctrl+I)", () => {
|
||||||
|
applyMarkdownWrap(textarea, "*", "*");
|
||||||
|
});
|
||||||
|
toolbar.appendChild(italicBtn);
|
||||||
|
|
||||||
|
// H2
|
||||||
|
const h2Btn = createToolbarButton("H2", "Heading 2", () => {
|
||||||
|
applyLinePrefix(textarea, "## ");
|
||||||
|
});
|
||||||
|
toolbar.appendChild(h2Btn);
|
||||||
|
|
||||||
|
// H3
|
||||||
|
const h3Btn = createToolbarButton("H3", "Heading 3", () => {
|
||||||
|
applyLinePrefix(textarea, "### ");
|
||||||
|
});
|
||||||
|
toolbar.appendChild(h3Btn);
|
||||||
|
|
||||||
|
// Bullet List
|
||||||
|
const bulletBtn = createToolbarButton("•", "Bullet List", () => {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const selectedText = text.substring(start, end);
|
||||||
|
|
||||||
|
// Check if lines already have bullet prefix
|
||||||
|
const lines = selectedText.split("\n");
|
||||||
|
const hasBullets = lines.some((line) => line.trim().startsWith("- "));
|
||||||
|
|
||||||
|
if (hasBullets) {
|
||||||
|
removeLinePrefix(textarea, "- ");
|
||||||
|
} else {
|
||||||
|
applyLinePrefix(textarea, "- ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toolbar.appendChild(bulletBtn);
|
||||||
|
|
||||||
|
// Numbered List
|
||||||
|
const numberedBtn = createToolbarButton("1.", "Numbered List", () => {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const selectedText = text.substring(start, end);
|
||||||
|
|
||||||
|
// Check if lines already have numbered prefix
|
||||||
|
const lines = selectedText.split("\n");
|
||||||
|
const hasNumbers = lines.some((line) => /^\d+\.\s/.test(line.trim()));
|
||||||
|
|
||||||
|
if (hasNumbers) {
|
||||||
|
// Remove numbered prefix (simple version - removes "1. " pattern)
|
||||||
|
const unprefixedLines = lines.map((line) => {
|
||||||
|
const match = line.match(/^(\d+\.\s)(.*)$/);
|
||||||
|
return match ? match[2] : line;
|
||||||
|
});
|
||||||
|
const newSelection = unprefixedLines.join("\n");
|
||||||
|
textarea.value =
|
||||||
|
text.substring(0, start) + newSelection + text.substring(end);
|
||||||
|
textarea.setSelectionRange(start, start + newSelection.length);
|
||||||
|
textarea.focus();
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
} else {
|
||||||
|
// Add numbered prefix
|
||||||
|
let counter = 1;
|
||||||
|
const numberedLines = lines.map((line) => {
|
||||||
|
if (line.trim()) {
|
||||||
|
return `${counter++}. ${line}`;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
const newSelection = numberedLines.join("\n");
|
||||||
|
textarea.value =
|
||||||
|
text.substring(0, start) + newSelection + text.substring(end);
|
||||||
|
textarea.setSelectionRange(start, start + newSelection.length);
|
||||||
|
textarea.focus();
|
||||||
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toolbar.appendChild(numberedBtn);
|
||||||
|
|
||||||
|
// Code
|
||||||
|
const codeBtn = createToolbarButton("</>", "Code (Ctrl+`)", () => {
|
||||||
|
applyMarkdownWrap(textarea, "`", "`");
|
||||||
|
});
|
||||||
|
toolbar.appendChild(codeBtn);
|
||||||
|
|
||||||
|
// Link
|
||||||
|
const linkBtn = createToolbarButton("🔗", "Link", () => {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const selectedText = text.substring(start, end);
|
||||||
|
|
||||||
|
if (selectedText) {
|
||||||
|
// Wrap selected text as link
|
||||||
|
applyMarkdownWrap(textarea, "[", "](url)");
|
||||||
|
// Select "url" part
|
||||||
|
setTimeout(() => {
|
||||||
|
const newPos = textarea.selectionStart - 5; // "url)".length
|
||||||
|
textarea.setSelectionRange(newPos, newPos + 3);
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
// Insert link template
|
||||||
|
applyMarkdownWrap(textarea, "[text](url)", "");
|
||||||
|
// Select "text" part
|
||||||
|
setTimeout(() => {
|
||||||
|
const newPos = textarea.selectionStart - 9; // "](url)".length
|
||||||
|
textarea.setSelectionRange(newPos, newPos + 4);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toolbar.appendChild(linkBtn);
|
||||||
|
|
||||||
|
// Preview toggle
|
||||||
|
if (onTogglePreview) {
|
||||||
|
const previewBtn = createToolbarButton("👁️", "Toggle Preview", () => {
|
||||||
|
onTogglePreview();
|
||||||
|
});
|
||||||
|
previewBtn.style.marginLeft = "auto";
|
||||||
|
toolbar.appendChild(previewBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a toolbar button.
|
||||||
|
*/
|
||||||
|
function createToolbarButton(
|
||||||
|
label: string,
|
||||||
|
title: string,
|
||||||
|
onClick: () => void
|
||||||
|
): HTMLElement {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.textContent = label;
|
||||||
|
button.title = title;
|
||||||
|
button.className = "markdown-toolbar-button";
|
||||||
|
button.style.padding = "0.25em 0.5em";
|
||||||
|
button.style.border = "1px solid var(--background-modifier-border)";
|
||||||
|
button.style.borderRadius = "4px";
|
||||||
|
button.style.background = "var(--background-primary)";
|
||||||
|
button.style.cursor = "pointer";
|
||||||
|
button.style.fontSize = "0.9em";
|
||||||
|
button.style.minWidth = "2em";
|
||||||
|
|
||||||
|
button.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("mouseenter", () => {
|
||||||
|
button.style.background = "var(--interactive-hover)";
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("mouseleave", () => {
|
||||||
|
button.style.background = "var(--background-primary)";
|
||||||
|
});
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
226
styles.css
226
styles.css
|
|
@ -61,3 +61,229 @@ If your plugin does not need CSS, delete this file.
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Interview Wizard Modal Layout */
|
||||||
|
.mindnet-wizard-modal.modal {
|
||||||
|
width: clamp(720px, 88vw, 1100px) !important;
|
||||||
|
height: clamp(640px, 86vh, 920px) !important;
|
||||||
|
max-width: 90vw !important;
|
||||||
|
max-height: 90vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .modal-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .modal-content-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 1em;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .modal-content-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 1.5em;
|
||||||
|
min-height: 0; /* Important for flex scrolling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .modal-content-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 1em;
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-width inputs in wizard */
|
||||||
|
.mindnet-wizard-modal .setting-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .setting-item-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .setting-item-control textarea {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 240px;
|
||||||
|
flex-grow: 1;
|
||||||
|
resize: vertical;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .setting-item-control input[type="text"] {
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single column layout for step content */
|
||||||
|
.mindnet-wizard-modal .step-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .step-content .setting-item {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation buttons layout */
|
||||||
|
.mindnet-wizard-modal .interview-navigation {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-wizard-modal .interview-navigation .setting-item {
|
||||||
|
margin: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown toolbar styles */
|
||||||
|
.markdown-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-toolbar-button {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--background-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
min-width: 2em;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-toolbar-button:hover {
|
||||||
|
background: var(--interactive-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-toolbar-button:active {
|
||||||
|
background: var(--interactive-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown editor container */
|
||||||
|
.markdown-editor-container {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-preview-container {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 240px;
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--background-primary);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-preview-container .markdown-preview-view {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure textarea in editor wrapper takes full width */
|
||||||
|
.markdown-editor-wrapper .setting-item {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor-wrapper .setting-item-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor-wrapper textarea {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
padding: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Field container with vertical layout */
|
||||||
|
.mindnet-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 14px 0 18px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-field__label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-field__desc {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.85;
|
||||||
|
line-height: 1.35;
|
||||||
|
white-space: normal;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-field__input {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mindnet-field textarea,
|
||||||
|
.mindnet-field input[type="text"] {
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure no truncation in field descriptions */
|
||||||
|
.mindnet-field__desc,
|
||||||
|
.mindnet-field__label {
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: clip !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove any truncation from Setting component labels when inside mindnet-field */
|
||||||
|
.mindnet-field .setting-item-name {
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: clip !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user