enum AttributeScope { INLINE, // refer to https://quilljs.com/docs/formats/#inline BLOCK, // refer to https://quilljs.com/docs/formats/#block EMBEDS, // refer to https://quilljs.com/docs/formats/#embeds } class Attribute { final String key; final AttributeScope scope; T value; Attribute(this.key, this.scope, this.value); static final Map _registry = { Attribute.bold.key: Attribute.bold, Attribute.italic.key: Attribute.italic, Attribute.underline.key: Attribute.underline, Attribute.strikeThrough.key: Attribute.strikeThrough, Attribute.link.key: Attribute.link, Attribute.header.key: Attribute.header, Attribute.list.key: Attribute.list, Attribute.codeBlock.key: Attribute.codeBlock, Attribute.blockQuote.key: Attribute.blockQuote, }; static BoldAttribute bold = BoldAttribute(); static ItalicAttribute italic = ItalicAttribute(); static UnderlineAttribute underline = UnderlineAttribute(); static StrikeThroughAttribute strikeThrough = StrikeThroughAttribute(); static LinkAttribute link = LinkAttribute(''); static HeaderAttribute header = HeaderAttribute(1); static ListAttribute list = ListAttribute('ordered'); static CodeBlockAttribute codeBlock = CodeBlockAttribute(); static BlockQuoteAttribute blockQuote = BlockQuoteAttribute(); static Attribute get h1 => header.level1; static Attribute get h2 => header.level2; static Attribute get h3 => header.level3; static Attribute get ul => list.bullet; static Attribute get ol => list.ordered; bool get isInline => scope == AttributeScope.INLINE; bool get isBlockExceptHeader => scope == AttributeScope.BLOCK && key != Attribute.header.key; Map toJson() => {key: value}; static Attribute fromKeyValue(String key, dynamic value) { if (!_registry.containsKey(key)) { throw ArgumentError.value(key, 'key "$key" not found.'); } Attribute attribute = _registry[key]; if (value != null) { attribute.value = value; } return attribute; } @override bool operator ==(Object other) => identical(this, other) || other is Attribute && runtimeType == other.runtimeType && key == other.key && scope == other.scope && value == other.value; @override int get hashCode => key.hashCode ^ scope.hashCode; @override String toString() { return 'Attribute{key: $key, scope: $scope, value: $value}'; } } class BoldAttribute extends Attribute { BoldAttribute() : super('bold', AttributeScope.INLINE, true); } class ItalicAttribute extends Attribute { ItalicAttribute() : super('italic', AttributeScope.INLINE, true); } class UnderlineAttribute extends Attribute { UnderlineAttribute() : super('underline', AttributeScope.INLINE, true); } class StrikeThroughAttribute extends Attribute { StrikeThroughAttribute() : super('strike', AttributeScope.INLINE, true); } class LinkAttribute extends Attribute { LinkAttribute(String val) : super('link', AttributeScope.INLINE, val); } class HeaderAttribute extends Attribute { HeaderAttribute(int level) : super('header', AttributeScope.BLOCK, level); // H1 in HTML Attribute get level1 => Attribute(key, scope, 1); // H2 in HTML Attribute get level2 => Attribute(key, scope, 2); // H3 in HTML Attribute get level3 => Attribute(key, scope, 3); } class ListAttribute extends Attribute { ListAttribute(String val) : super('list', AttributeScope.BLOCK, val); // "attributes":{"list":"ordered"} Attribute get ordered => Attribute(key, scope, 'ordered'); // "attributes":{"list":"bullet"} Attribute get bullet => Attribute(key, scope, 'bullet'); } class CodeBlockAttribute extends Attribute { CodeBlockAttribute() : super('code-block', AttributeScope.BLOCK, true); } class BlockQuoteAttribute extends Attribute { BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true); }