Add heading level configuration to interview steps and rendering logic
- Introduced a new `heading_level` property in `CaptureTextLineStep` to enable customizable heading levels for interview steps. - Updated `parseInterviewConfig` to parse and validate heading level settings. - Enhanced rendering functions to prepend heading prefixes based on the configured heading level, improving text output formatting. - Implemented a dropdown in the `InterviewWizardModal` for selecting heading levels, allowing users to easily adjust the heading for each step. - Improved handling of nested loops to support heading level selection and display.
This commit is contained in:
parent
611ad37c42
commit
070cb853a9
|
|
@ -333,6 +333,17 @@ function parseStep(raw: Record<string, unknown>): InterviewStep | null {
|
|||
step.prompt = raw.prompt.trim();
|
||||
}
|
||||
|
||||
// Parse heading_level
|
||||
if (raw.heading_level && typeof raw.heading_level === "object") {
|
||||
const headingLevel = raw.heading_level as Record<string, unknown>;
|
||||
step.heading_level = {
|
||||
enabled: headingLevel.enabled === true,
|
||||
default: typeof headingLevel.default === "number"
|
||||
? Math.max(1, Math.min(6, headingLevel.default))
|
||||
: 2,
|
||||
};
|
||||
}
|
||||
|
||||
// Parse output.template
|
||||
if (raw.output && typeof raw.output === "object") {
|
||||
const output = raw.output as Record<string, unknown>;
|
||||
|
|
|
|||
|
|
@ -93,17 +93,33 @@ function renderCaptureTextLine(step: CaptureTextLineStep, answers: RenderAnswers
|
|||
|
||||
const text = String(value);
|
||||
|
||||
// Get heading level if configured
|
||||
const headingLevelKey = `${step.key}_heading_level`;
|
||||
const headingLevel = answers.collectedData.get(headingLevelKey);
|
||||
let headingPrefix = "";
|
||||
if (step.heading_level?.enabled) {
|
||||
if (typeof headingLevel === "number") {
|
||||
const level = Math.max(1, Math.min(6, headingLevel));
|
||||
headingPrefix = "#".repeat(level) + " ";
|
||||
} else if (step.heading_level.default) {
|
||||
// Fallback to default if not set
|
||||
const level = Math.max(1, Math.min(6, step.heading_level.default));
|
||||
headingPrefix = "#".repeat(level) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Use template if provided
|
||||
if (step.output?.template) {
|
||||
return renderTemplate(step.output.template, {
|
||||
text,
|
||||
text: headingPrefix + text,
|
||||
field: step.key,
|
||||
value: text,
|
||||
value: headingPrefix + text,
|
||||
heading_level: headingLevel ? String(headingLevel) : "",
|
||||
});
|
||||
}
|
||||
|
||||
// Default: just the text
|
||||
return text;
|
||||
// Default: text with heading prefix if configured
|
||||
return headingPrefix + text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -187,16 +203,32 @@ function renderLoopRecursive(step: LoopStep, answers: RenderAnswers, depth: numb
|
|||
const text = String(fieldValue);
|
||||
const captureStep = nestedStep as CaptureTextLineStep;
|
||||
|
||||
// Get heading level if configured (for nested loops, check in item data)
|
||||
const headingLevelKey = `${nestedStep.key}_heading_level`;
|
||||
const headingLevel = (item as Record<string, unknown>)[headingLevelKey];
|
||||
let headingPrefix = "";
|
||||
if (captureStep.heading_level?.enabled) {
|
||||
if (typeof headingLevel === "number") {
|
||||
const level = Math.max(1, Math.min(6, headingLevel));
|
||||
headingPrefix = "#".repeat(level) + " ";
|
||||
} else if (captureStep.heading_level.default) {
|
||||
// Fallback to default if not set in item
|
||||
const level = Math.max(1, Math.min(6, captureStep.heading_level.default));
|
||||
headingPrefix = "#".repeat(level) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Use template if provided
|
||||
if (captureStep.output?.template) {
|
||||
itemParts.push(renderTemplate(captureStep.output.template, {
|
||||
text,
|
||||
text: headingPrefix + text,
|
||||
field: nestedStep.key,
|
||||
value: text,
|
||||
value: headingPrefix + text,
|
||||
heading_level: headingLevel ? String(headingLevel) : (captureStep.heading_level?.default ? String(captureStep.heading_level.default) : ""),
|
||||
}));
|
||||
} else {
|
||||
// Default: just the text
|
||||
itemParts.push(text);
|
||||
// Default: text with heading prefix if configured
|
||||
itemParts.push(headingPrefix + text);
|
||||
}
|
||||
}
|
||||
} else if (nestedStep.type === "capture_frontmatter") {
|
||||
|
|
@ -267,9 +299,15 @@ function renderLoopRecursive(step: LoopStep, answers: RenderAnswers, depth: numb
|
|||
* Render template string with token replacement.
|
||||
* Tokens: {text}, {field}, {value}
|
||||
*/
|
||||
function renderTemplate(template: string, tokens: { text: string; field: string; value: string }): string {
|
||||
return template
|
||||
function renderTemplate(template: string, tokens: { text: string; field: string; value: string; heading_level?: string }): string {
|
||||
let result = template
|
||||
.replace(/\{text\}/g, tokens.text)
|
||||
.replace(/\{field\}/g, tokens.field)
|
||||
.replace(/\{value\}/g, tokens.value);
|
||||
|
||||
if (tokens.heading_level !== undefined) {
|
||||
result = result.replace(/\{heading_level\}/g, tokens.heading_level);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,12 @@ export interface CaptureTextLineStep {
|
|||
label?: string;
|
||||
required?: boolean;
|
||||
prompt?: string; // Optional prompt text
|
||||
heading_level?: {
|
||||
enabled?: boolean; // Show heading level selector (default: false)
|
||||
default?: number; // Default heading level 1-6 (default: 2)
|
||||
};
|
||||
output?: {
|
||||
template?: string; // Template with tokens: {text}, {field}, {value}
|
||||
template?: string; // Template with tokens: {text}, {field}, {value}, {heading_level}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -425,13 +425,70 @@ export class InterviewWizardModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
// Input container
|
||||
// Input container with heading level selector
|
||||
const inputContainer = fieldContainer.createEl("div", {
|
||||
cls: "mindnet-field__input",
|
||||
});
|
||||
inputContainer.style.width = "100%";
|
||||
inputContainer.style.display = "flex";
|
||||
inputContainer.style.gap = "0.5em";
|
||||
inputContainer.style.alignItems = "center";
|
||||
|
||||
const fieldSetting = new Setting(inputContainer);
|
||||
// Heading level dropdown (if enabled)
|
||||
let headingLevel: number | null = null;
|
||||
const headingLevelKey = `${step.key}_heading_level`;
|
||||
const storedHeadingLevel = this.state.collectedData.get(headingLevelKey);
|
||||
if (storedHeadingLevel !== undefined && typeof storedHeadingLevel === "number") {
|
||||
headingLevel = storedHeadingLevel;
|
||||
} else if (step.heading_level?.enabled) {
|
||||
headingLevel = step.heading_level.default || 2;
|
||||
}
|
||||
|
||||
if (step.heading_level?.enabled) {
|
||||
const headingSelectorContainer = inputContainer.createEl("div");
|
||||
headingSelectorContainer.style.flexShrink = "0";
|
||||
|
||||
const headingLabel = headingSelectorContainer.createEl("label", {
|
||||
text: "H",
|
||||
attr: { for: `heading-level-${step.key}` },
|
||||
});
|
||||
headingLabel.style.marginRight = "0.25em";
|
||||
headingLabel.style.fontSize = "0.9em";
|
||||
headingLabel.style.color = "var(--text-muted)";
|
||||
|
||||
const headingSelect = headingSelectorContainer.createEl("select", {
|
||||
attr: { id: `heading-level-${step.key}` },
|
||||
});
|
||||
headingSelect.style.padding = "0.25em 0.5em";
|
||||
headingSelect.style.border = "1px solid var(--background-modifier-border)";
|
||||
headingSelect.style.borderRadius = "4px";
|
||||
headingSelect.style.background = "var(--background-primary)";
|
||||
headingSelect.style.fontSize = "0.9em";
|
||||
headingSelect.style.minWidth = "3em";
|
||||
|
||||
// Add options H1-H6
|
||||
for (let level = 1; level <= 6; level++) {
|
||||
const option = headingSelect.createEl("option", {
|
||||
text: `H${level}`,
|
||||
attr: { value: String(level) },
|
||||
});
|
||||
if (headingLevel === level) {
|
||||
option.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
headingSelect.onchange = () => {
|
||||
const selectedLevel = parseInt(headingSelect.value, 10);
|
||||
this.state.collectedData.set(headingLevelKey, selectedLevel);
|
||||
};
|
||||
}
|
||||
|
||||
// Text input (takes remaining space)
|
||||
const textInputContainer = inputContainer.createEl("div");
|
||||
textInputContainer.style.flex = "1";
|
||||
textInputContainer.style.minWidth = "0";
|
||||
|
||||
const fieldSetting = new Setting(textInputContainer);
|
||||
fieldSetting.settingEl.style.width = "100%";
|
||||
fieldSetting.controlEl.style.width = "100%";
|
||||
|
||||
|
|
@ -1146,13 +1203,82 @@ export class InterviewWizardModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
// Input container
|
||||
// Input container with heading level selector
|
||||
const inputContainer = fieldContainer.createEl("div", {
|
||||
cls: "mindnet-field__input",
|
||||
});
|
||||
inputContainer.style.width = "100%";
|
||||
inputContainer.style.display = "flex";
|
||||
inputContainer.style.gap = "0.5em";
|
||||
inputContainer.style.alignItems = "center";
|
||||
|
||||
const fieldSetting = new Setting(inputContainer);
|
||||
// Heading level dropdown (if enabled)
|
||||
const headingLevelDraftKey = `${nestedStep.key}_heading_level`;
|
||||
// Get loopState from the loopKey to access draft
|
||||
const currentLoopState = this.state.loopRuntimeStates.get(loopKey);
|
||||
let headingLevelDraftValue: number | undefined = undefined;
|
||||
if (currentLoopState) {
|
||||
headingLevelDraftValue = currentLoopState.draft[headingLevelDraftKey] as number | undefined;
|
||||
// If not in draft and we're editing, try to get from the item being edited
|
||||
if (headingLevelDraftValue === undefined && currentLoopState.editIndex !== null) {
|
||||
const itemBeingEdited = currentLoopState.items[currentLoopState.editIndex];
|
||||
if (itemBeingEdited && typeof itemBeingEdited === "object") {
|
||||
headingLevelDraftValue = (itemBeingEdited as Record<string, unknown>)[headingLevelDraftKey] as number | undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
let headingLevel: number | null = null;
|
||||
if (headingLevelDraftValue !== undefined && typeof headingLevelDraftValue === "number") {
|
||||
headingLevel = headingLevelDraftValue;
|
||||
} else if (nestedStep.heading_level?.enabled) {
|
||||
headingLevel = nestedStep.heading_level.default || 2;
|
||||
}
|
||||
|
||||
if (nestedStep.heading_level?.enabled) {
|
||||
const headingSelectorContainer = inputContainer.createEl("div");
|
||||
headingSelectorContainer.style.flexShrink = "0";
|
||||
|
||||
const headingLabel = headingSelectorContainer.createEl("label", {
|
||||
text: "H",
|
||||
attr: { for: `heading-level-${loopKey}-${nestedStep.key}` },
|
||||
});
|
||||
headingLabel.style.marginRight = "0.25em";
|
||||
headingLabel.style.fontSize = "0.9em";
|
||||
headingLabel.style.color = "var(--text-muted)";
|
||||
|
||||
const headingSelect = headingSelectorContainer.createEl("select", {
|
||||
attr: { id: `heading-level-${loopKey}-${nestedStep.key}` },
|
||||
});
|
||||
headingSelect.style.padding = "0.25em 0.5em";
|
||||
headingSelect.style.border = "1px solid var(--background-modifier-border)";
|
||||
headingSelect.style.borderRadius = "4px";
|
||||
headingSelect.style.background = "var(--background-primary)";
|
||||
headingSelect.style.fontSize = "0.9em";
|
||||
headingSelect.style.minWidth = "3em";
|
||||
|
||||
// Add options H1-H6
|
||||
for (let level = 1; level <= 6; level++) {
|
||||
const option = headingSelect.createEl("option", {
|
||||
text: `H${level}`,
|
||||
attr: { value: String(level) },
|
||||
});
|
||||
if (headingLevel === level) {
|
||||
option.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
headingSelect.onchange = () => {
|
||||
const selectedLevel = parseInt(headingSelect.value, 10);
|
||||
onFieldChange(headingLevelDraftKey, selectedLevel);
|
||||
};
|
||||
}
|
||||
|
||||
// Text input (takes remaining space)
|
||||
const textInputContainer = inputContainer.createEl("div");
|
||||
textInputContainer.style.flex = "1";
|
||||
textInputContainer.style.minWidth = "0";
|
||||
|
||||
const fieldSetting = new Setting(textInputContainer);
|
||||
fieldSetting.settingEl.style.width = "100%";
|
||||
fieldSetting.controlEl.style.width = "100%";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user