Add a "Make substep of…" option to the step context menu
Lets a step be re-parented under another step by entering that step's displayed number, moving it (with its own substeps) to the end of the target's substeps and updating the numbering accordingly.
This commit is contained in:
@@ -731,6 +731,7 @@ class GuideEditor {
|
|||||||
this.selectStep(step.stepId);
|
this.selectStep(step.stepId);
|
||||||
contextMenu(e.clientX, e.clientY, [
|
contextMenu(e.clientX, e.clientY, [
|
||||||
{ label: 'Add substep', action: () => this.addSubstep(step.stepId) },
|
{ label: 'Add substep', action: () => this.addSubstep(step.stepId) },
|
||||||
|
{ label: 'Make substep of…', action: () => this.makeSubstepOf(step.stepId) },
|
||||||
{ label: 'Duplicate step', action: () => this.duplicateSelectedStep() },
|
{ label: 'Duplicate step', action: () => this.duplicateSelectedStep() },
|
||||||
'sep',
|
'sep',
|
||||||
{ label: 'Move up', action: () => this.moveSelectedStep(-1) },
|
{ label: 'Move up', action: () => this.moveSelectedStep(-1) },
|
||||||
@@ -1289,6 +1290,66 @@ class GuideEditor {
|
|||||||
this.onToast(parent ? 'Substep added.' : 'Step added.');
|
this.onToast(parent ? 'Substep added.' : 'Step added.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** All step ids whose ancestor chain leads back to `stepId`. */
|
||||||
|
getStepDescendantIds(stepId) {
|
||||||
|
const result = [];
|
||||||
|
const queue = [stepId];
|
||||||
|
while (queue.length) {
|
||||||
|
const current = queue.shift();
|
||||||
|
for (const s of this.steps) {
|
||||||
|
if (s.parentStepId === current) {
|
||||||
|
result.push(s.stepId);
|
||||||
|
queue.push(s.stepId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeSubstepOf(stepId) {
|
||||||
|
const step = this.stepMap.get(stepId);
|
||||||
|
if (!step) return;
|
||||||
|
const numbers = stepNumberMap(this.steps);
|
||||||
|
const input = await dialogs.promptText({
|
||||||
|
title: 'Make substep',
|
||||||
|
label: 'Make substep of step #',
|
||||||
|
placeholder: 'e.g. 2',
|
||||||
|
});
|
||||||
|
if (input == null) return;
|
||||||
|
const target = input.trim().replace(/^#/, '');
|
||||||
|
const targetId = this.steps.find((s) => numbers.get(s.stepId) === target)?.stepId;
|
||||||
|
if (!targetId) {
|
||||||
|
this.onToast(`No step numbered "${target}".`, { error: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subtreeIds = new Set([stepId, ...this.getStepDescendantIds(stepId)]);
|
||||||
|
if (subtreeIds.has(targetId)) {
|
||||||
|
this.onToast('A step cannot be made a substep of itself or one of its own substeps.', { error: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (step.parentStepId === targetId) {
|
||||||
|
this.onToast(`“${step.title || 'Untitled step'}” is already a substep of step ${target}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
step.parentStepId = targetId;
|
||||||
|
await api.step.save({ guideId: this.guideId, step });
|
||||||
|
|
||||||
|
// Move the step (with its own substeps) to sit right after the target
|
||||||
|
// step's existing substeps, so it becomes the target's last substep.
|
||||||
|
const order = this.steps.map((s) => s.stepId);
|
||||||
|
const remaining = order.filter((id) => !subtreeIds.has(id));
|
||||||
|
const targetSubtree = new Set([targetId, ...this.getStepDescendantIds(targetId)]);
|
||||||
|
let insertAt = remaining.indexOf(targetId) + 1;
|
||||||
|
while (insertAt < remaining.length && targetSubtree.has(remaining[insertAt])) insertAt++;
|
||||||
|
const movedBlock = order.filter((id) => subtreeIds.has(id));
|
||||||
|
remaining.splice(insertAt, 0, ...movedBlock);
|
||||||
|
await api.step.reorder({ guideId: this.guideId, order: remaining });
|
||||||
|
|
||||||
|
await this.reload(stepId);
|
||||||
|
this.onToast(`“${step.title || 'Untitled step'}” is now a substep of step ${target}.`);
|
||||||
|
}
|
||||||
|
|
||||||
async duplicateSelectedStep() {
|
async duplicateSelectedStep() {
|
||||||
const step = this.currentStep;
|
const step = this.currentStep;
|
||||||
if (!step) return;
|
if (!step) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user