292 lines
6.5 KiB
Dart
292 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();
|
||
|
|
}
|