setPlainText method
Replaces content with plain paragraphs, preserving the story identity.
Formatting and list structure are reset. CRLF and CR become line breaks.
Supply storyId for connectors with multiple labels. Admission enforces
the slot's text and line limit, locks and host policies; success is one
undo entry.
Implementation
EditResult setPlainText({
required String elementId,
required String text,
String? storyId,
String? slotId,
}) {
final target = _resolveTarget(elementId, storyId);
if (target == null) return _noTarget;
if (_unavailable(target) case final denial?) return denial;
final before = target.canonical.story;
final slots = before.blocks.map((block) => block.effectiveSlotId).toSet();
if ((slotId == null && slots.length > 1) ||
(slotId != null && !slots.contains(slotId))) {
return const EditResult.denied(
code: DiagramDenialCode.unavailableTarget,
reason: 'Select an existing text slot explicitly',
);
}
final selectedSlot = slotId ?? slots.firstOrNull;
final blocks = before.blocks
.where((block) => block.effectiveSlotId == selectedSlot)
.toList();
final lines = text.split(RegExp(r'\r\n|\r|\n'));
final replacements = [
for (var i = 0; i < lines.length; i++)
TextParagraph(
id: i < blocks.length ? blocks[i].id : store.allocateId('paragraph'),
role: blocks.firstOrNull?.role ?? TextRole.body,
slotId: blocks.firstOrNull?.slotId,
alignment: blocks.firstOrNull?.alignment ?? ParagraphAlignment.start,
runs: [TextRun(lines[i])],
),
];
var inserted = false;
final updated = <TextParagraph>[];
for (final block in before.blocks) {
if (block.effectiveSlotId != selectedSlot) {
updated.add(block);
} else if (!inserted) {
updated.addAll(replacements);
inserted = true;
}
}
if (!inserted) updated.addAll(replacements);
final after = before.copyWith(blocks: updated);
return replace(elementId: elementId, before: before, after: after);
}