feat: build v1 Mews client: reader UI, gestures, Drift store, fetch actor
This commit is contained in:
parent
d5a0fcbf48
commit
01a36e8aee
56 changed files with 7287 additions and 2 deletions
291
app/lib/mews/document.dart
Normal file
291
app/lib/mews/document.dart
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/// 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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue