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();
|
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
|
// Parse output.template
|
||||||
if (raw.output && typeof raw.output === "object") {
|
if (raw.output && typeof raw.output === "object") {
|
||||||
const output = raw.output as Record<string, unknown>;
|
const output = raw.output as Record<string, unknown>;
|
||||||
|
|
|
||||||
|
|
@ -93,17 +93,33 @@ function renderCaptureTextLine(step: CaptureTextLineStep, answers: RenderAnswers
|
||||||
|
|
||||||
const text = String(value);
|
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
|
// Use template if provided
|
||||||
if (step.output?.template) {
|
if (step.output?.template) {
|
||||||
return renderTemplate(step.output.template, {
|
return renderTemplate(step.output.template, {
|
||||||
text,
|
text: headingPrefix + text,
|
||||||
field: step.key,
|
field: step.key,
|
||||||
value: text,
|
value: headingPrefix + text,
|
||||||
|
heading_level: headingLevel ? String(headingLevel) : "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: just the text
|
// Default: text with heading prefix if configured
|
||||||
return text;
|
return headingPrefix + text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -187,16 +203,32 @@ function renderLoopRecursive(step: LoopStep, answers: RenderAnswers, depth: numb
|
||||||
const text = String(fieldValue);
|
const text = String(fieldValue);
|
||||||
const captureStep = nestedStep as CaptureTextLineStep;
|
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
|
// Use template if provided
|
||||||
if (captureStep.output?.template) {
|
if (captureStep.output?.template) {
|
||||||
itemParts.push(renderTemplate(captureStep.output.template, {
|
itemParts.push(renderTemplate(captureStep.output.template, {
|
||||||
text,
|
text: headingPrefix + text,
|
||||||
field: nestedStep.key,
|
field: nestedStep.key,
|
||||||
value: text,
|
value: headingPrefix + text,
|
||||||
|
heading_level: headingLevel ? String(headingLevel) : (captureStep.heading_level?.default ? String(captureStep.heading_level.default) : ""),
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// Default: just the text
|
// Default: text with heading prefix if configured
|
||||||
itemParts.push(text);
|
itemParts.push(headingPrefix + text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (nestedStep.type === "capture_frontmatter") {
|
} else if (nestedStep.type === "capture_frontmatter") {
|
||||||
|
|
@ -267,9 +299,15 @@ function renderLoopRecursive(step: LoopStep, answers: RenderAnswers, depth: numb
|
||||||
* Render template string with token replacement.
|
* Render template string with token replacement.
|
||||||
* Tokens: {text}, {field}, {value}
|
* Tokens: {text}, {field}, {value}
|
||||||
*/
|
*/
|
||||||
function renderTemplate(template: string, tokens: { text: string; field: string; value: string }): string {
|
function renderTemplate(template: string, tokens: { text: string; field: string; value: string; heading_level?: string }): string {
|
||||||
return template
|
let result = template
|
||||||
.replace(/\{text\}/g, tokens.text)
|
.replace(/\{text\}/g, tokens.text)
|
||||||
.replace(/\{field\}/g, tokens.field)
|
.replace(/\{field\}/g, tokens.field)
|
||||||
.replace(/\{value\}/g, tokens.value);
|
.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;
|
label?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
prompt?: string; // Optional prompt text
|
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?: {
|
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", {
|
const inputContainer = fieldContainer.createEl("div", {
|
||||||
cls: "mindnet-field__input",
|
cls: "mindnet-field__input",
|
||||||
});
|
});
|
||||||
inputContainer.style.width = "100%";
|
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.settingEl.style.width = "100%";
|
||||||
fieldSetting.controlEl.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", {
|
const inputContainer = fieldContainer.createEl("div", {
|
||||||
cls: "mindnet-field__input",
|
cls: "mindnet-field__input",
|
||||||
});
|
});
|
||||||
inputContainer.style.width = "100%";
|
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.settingEl.style.width = "100%";
|
||||||
fieldSetting.controlEl.style.width = "100%";
|
fieldSetting.controlEl.style.width = "100%";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user