the-mews/app/lib/mews/document.dart

291 lines
6.5 KiB
Dart

/// Document model for Mews pages (mews-spec/SPEC.md sections 3 to 6).
/// Pure data: no Flutter imports, so it can be built inside an isolate.
library;
/// The `body` variant class a page carries (spec 5.3), or [plain] when absent.
enum MewsVariant { plain, warm, cool, green, mono }
/// A parsed page. [specVersion] holds the conformance marker's value and is
/// null on a page without the marker, which must be shown plainly (spec 8.1).
class MewsPage {
const MewsPage({
required this.url,
required this.body,
required this.lang,
required this.direction,
required this.variant,
required this.entries,
this.specVersion,
this.title,
this.description,
this.author,
this.atomFeedHref,
});
final String url;
final List<MewsBlock> body;
final String lang;
final TextDirection direction;
final MewsVariant variant;
final List<MewsEntry> entries;
final String? specVersion;
final String? title;
final String? description;
final String? author;
final String? atomFeedHref;
bool get isMews => specVersion != null;
}
/// TextDirection without importing Flutter: pages with dir="rtl" render
/// right-to-left (spec 3).
enum TextDirection { ltr, rtl }
/// A dated link that starts a list item (spec 6.1).
class MewsEntry {
const MewsEntry({required this.url, required this.date, required this.title});
final String url;
final DateTime date;
final String title;
}
sealed class MewsBlock {
const MewsBlock();
}
class HeadingBlock extends MewsBlock {
const HeadingBlock(this.level, this.inline);
final int level; // 1 to 6
final List<MewsInline> inline;
}
class ParagraphBlock extends MewsBlock {
const ParagraphBlock(this.inline);
final List<MewsInline> inline;
}
class QuoteBlock extends MewsBlock {
const QuoteBlock(this.children);
final List<MewsBlock> children;
}
class PreBlock extends MewsBlock {
const PreBlock(this.text);
final String text;
}
class AddressBlock extends MewsBlock {
const AddressBlock(this.inline);
final List<MewsInline> inline;
}
class RuleBlock extends MewsBlock {
const RuleBlock();
}
class ListBlock extends MewsBlock {
const ListBlock({required this.ordered, required this.start, required this.items});
final bool ordered;
final int start;
final List<MewsListItem> items;
}
class MewsListItem {
const MewsListItem(this.blocks);
final List<MewsBlock> blocks;
}
class DefinitionItem {
const DefinitionItem(this.term, this.definition);
final List<MewsInline> term;
final List<MewsInline> definition;
}
class DefinitionListBlock extends MewsBlock {
const DefinitionListBlock(this.items);
final List<DefinitionItem> items;
}
class TableBlock extends MewsBlock {
const TableBlock({required this.rows, this.caption, this.summary});
final List<MewsTableRow> rows;
final String? caption;
final String? summary;
}
class MewsTableRow {
const MewsTableRow(this.cells);
final List<MewsTableCell> cells;
}
class MewsTableCell {
const MewsTableCell({
required this.header,
required this.inline,
this.colspan = 1,
this.rowspan = 1,
});
final bool header;
final List<MewsInline> inline;
final int colspan;
final int rowspan;
}
/// Same-site images load normally; off-site or data: sources are refused by
/// the client without reader action (specs 4.2 and 8.1), and the alt text is
/// shown in their place.
class ImageBlock extends MewsBlock {
const ImageBlock({required this.src, required this.alt, required this.allowed});
final String src;
final String alt;
final bool allowed;
}
class FormBlock extends MewsBlock {
const FormBlock({
required this.action,
required this.method,
required this.fields,
required this.submitLabel,
});
final String action;
final String method;
final List<MewsFormField> fields;
final String? submitLabel;
}
sealed class MewsFormField {
const MewsFormField({required this.name, this.label});
final String name;
final String? label;
}
class MewsTextField extends MewsFormField {
const MewsTextField({
required super.name,
required this.password,
this.value = '',
this.maxLength,
this.inputMode,
super.label,
});
final bool password;
final String value;
final int? maxLength;
final String? inputMode;
}
class MewsCheckboxField extends MewsFormField {
const MewsCheckboxField({
required super.name,
required this.checked,
this.value = 'on',
super.label,
});
final bool checked;
final String value;
}
class MewsRadioField extends MewsFormField {
const MewsRadioField({
required super.name,
required this.checked,
required this.value,
super.label,
});
final bool checked;
final String value;
}
class MewsSelectField extends MewsFormField {
const MewsSelectField({required super.name, required this.options, super.label});
final List<MewsSelectOption> options;
}
class MewsSelectOption {
const MewsSelectOption({required this.value, required this.selected});
final String value;
final bool selected;
}
class MewsTextAreaField extends MewsFormField {
const MewsTextAreaField({
required super.name,
required this.value,
this.rows = 3,
super.label,
});
final String value;
final int rows;
}
/// A hidden input; kept in the model so it round-trips on submit.
class MewsHiddenField extends MewsFormField {
const MewsHiddenField({required super.name, required this.value});
final String value;
}
/// One or more inputs of kind submit/reset collapse into this control.
class MewsSubmitField extends MewsFormField {
const MewsSubmitField({required this.reset, super.label, super.name = ''});
final bool reset;
}
sealed class MewsInline {
const MewsInline();
}
class MewsText extends MewsInline {
const MewsText(this.text);
final String text;
}
enum EmphasisStyle { emphasis, strong, code, sample, variable, term, quote }
/// em, i, cite and abbr map to [EmphasisStyle.emphasis]; strong and b to
/// [EmphasisStyle.strong]; kbd and samp to [EmphasisStyle.sample]; var to
/// [EmphasisStyle.variable]; dfn to [EmphasisStyle.term]; q to
/// [EmphasisStyle.quote]. The distinction keeps the renderer honest about what
/// authors meant, per the reader-controls-presentation principle.
class MewsEmphasis extends MewsInline {
const MewsEmphasis(this.style, this.children);
final EmphasisStyle style;
final List<MewsInline> children;
}
class MewsLink extends MewsInline {
const MewsLink(this.href, this.children);
final String href;
final List<MewsInline> children;
}
class MewsLineBreak extends MewsInline {
const MewsLineBreak();
}