import 'dart:collection'; import 'package:quiver/core.dart'; 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 IGNORE, // attributes that can be ignored } class Attribute { const Attribute(this.key, this.scope, this.value); /// Unique key of this attribute. final String key; final AttributeScope scope; final T value; static final Map _registry = LinkedHashMap.of({ Attribute.bold.key: Attribute.bold, Attribute.italic.key: Attribute.italic, Attribute.small.key: Attribute.small, Attribute.underline.key: Attribute.underline, Attribute.strikeThrough.key: Attribute.strikeThrough, Attribute.inlineCode.key: Attribute.inlineCode, Attribute.font.key: Attribute.font, Attribute.size.key: Attribute.size, Attribute.link.key: Attribute.link, Attribute.color.key: Attribute.color, Attribute.background.key: Attribute.background, Attribute.placeholder.key: Attribute.placeholder, Attribute.header.key: Attribute.header, Attribute.align.key: Attribute.align, Attribute.direction.key: Attribute.direction, Attribute.list.key: Attribute.list, Attribute.codeBlock.key: Attribute.codeBlock, Attribute.blockQuote.key: Attribute.blockQuote, Attribute.indent.key: Attribute.indent, Attribute.width.key: Attribute.width, Attribute.height.key: Attribute.height, Attribute.style.key: Attribute.style, Attribute.token.key: Attribute.token, Attribute.script.key: Attribute.script, Attribute.image.key: Attribute.image, Attribute.video.key: Attribute.video, }); static const BoldAttribute bold = BoldAttribute(); static const ItalicAttribute italic = ItalicAttribute(); static const SmallAttribute small = SmallAttribute(); static const UnderlineAttribute underline = UnderlineAttribute(); static const StrikeThroughAttribute strikeThrough = StrikeThroughAttribute(); static const InlineCodeAttribute inlineCode = InlineCodeAttribute(); static const FontAttribute font = FontAttribute(null); static const SizeAttribute size = SizeAttribute(null); static const LinkAttribute link = LinkAttribute(null); static const ColorAttribute color = ColorAttribute(null); static const BackgroundAttribute background = BackgroundAttribute(null); static const PlaceholderAttribute placeholder = PlaceholderAttribute(); static const HeaderAttribute header = HeaderAttribute(); static const IndentAttribute indent = IndentAttribute(); static const AlignAttribute align = AlignAttribute(null); static const ListAttribute list = ListAttribute(null); static const CodeBlockAttribute codeBlock = CodeBlockAttribute(); static const BlockQuoteAttribute blockQuote = BlockQuoteAttribute(); static const DirectionAttribute direction = DirectionAttribute(null); static const WidthAttribute width = WidthAttribute(null); static const HeightAttribute height = HeightAttribute(null); static const StyleAttribute style = StyleAttribute(null); static const TokenAttribute token = TokenAttribute(''); static final ScriptAttribute script = ScriptAttribute(null); static const String mobileWidth = 'mobileWidth'; static const String mobileHeight = 'mobileHeight'; static const String mobileMargin = 'mobileMargin'; static const String mobileAlignment = 'mobileAlignment'; static const ImageAttribute image = ImageAttribute(null); static const VideoAttribute video = VideoAttribute(null); static final Set inlineKeys = { Attribute.bold.key, Attribute.italic.key, Attribute.small.key, Attribute.underline.key, Attribute.strikeThrough.key, Attribute.link.key, Attribute.color.key, Attribute.background.key, Attribute.placeholder.key, }; static final Set blockKeys = LinkedHashSet.of({ Attribute.header.key, Attribute.align.key, Attribute.list.key, Attribute.codeBlock.key, Attribute.blockQuote.key, Attribute.indent.key, Attribute.direction.key, }); static final Set blockKeysExceptHeader = LinkedHashSet.of({ Attribute.list.key, Attribute.align.key, Attribute.codeBlock.key, Attribute.blockQuote.key, Attribute.indent.key, Attribute.direction.key, }); static final Set exclusiveBlockKeys = LinkedHashSet.of({ Attribute.header.key, Attribute.list.key, Attribute.codeBlock.key, Attribute.blockQuote.key, }); static final Set embedKeys = { Attribute.image.key, Attribute.video.key, }; static const Attribute h1 = HeaderAttribute(level: 1); static const Attribute h2 = HeaderAttribute(level: 2); static const Attribute h3 = HeaderAttribute(level: 3); // "attributes":{"align":"left"} static const Attribute leftAlignment = AlignAttribute('left'); // "attributes":{"align":"center"} static const Attribute centerAlignment = AlignAttribute('center'); // "attributes":{"align":"right"} static const Attribute rightAlignment = AlignAttribute('right'); // "attributes":{"align":"justify"} static const Attribute justifyAlignment = AlignAttribute('justify'); // "attributes":{"list":"bullet"} static const Attribute ul = ListAttribute('bullet'); // "attributes":{"list":"ordered"} static const Attribute ol = ListAttribute('ordered'); // "attributes":{"list":"checked"} static const Attribute checked = ListAttribute('checked'); // "attributes":{"list":"unchecked"} static const Attribute unchecked = ListAttribute('unchecked'); // "attributes":{"direction":"rtl"} static const Attribute rtl = DirectionAttribute('rtl'); // "attributes":{"indent":1"} static const Attribute indentL1 = IndentAttribute(level: 1); // "attributes":{"indent":2"} static const Attribute indentL2 = IndentAttribute(level: 2); // "attributes":{"indent":3"} static const Attribute indentL3 = IndentAttribute(level: 3); static Attribute getIndentLevel(int? level) { if (level == 1) { return indentL1; } if (level == 2) { return indentL2; } if (level == 3) { return indentL3; } return IndentAttribute(level: level); } bool get isInline => scope == AttributeScope.INLINE; bool get isBlockExceptHeader => blockKeysExceptHeader.contains(key); Map toJson() => {key: value}; static Attribute? fromKeyValue(String key, dynamic value) { final origin = _registry[key]; if (origin == null) { return null; } final attribute = clone(origin, value); return attribute; } static int getRegistryOrder(Attribute attribute) { var order = 0; for (final attr in _registry.values) { if (attr.key == attribute.key) { break; } order++; } return order; } static Attribute clone(Attribute origin, dynamic value) { return Attribute(origin.key, origin.scope, value); } @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! Attribute) return false; final typedOther = other; return key == typedOther.key && scope == typedOther.scope && value == typedOther.value; } @override int get hashCode => hash3(key, scope, value); @override String toString() { return 'Attribute{key: $key, scope: $scope, value: $value}'; } } class BoldAttribute extends Attribute { const BoldAttribute() : super('bold', AttributeScope.INLINE, true); } class ItalicAttribute extends Attribute { const ItalicAttribute() : super('italic', AttributeScope.INLINE, true); } class SmallAttribute extends Attribute { const SmallAttribute() : super('small', AttributeScope.INLINE, true); } class UnderlineAttribute extends Attribute { const UnderlineAttribute() : super('underline', AttributeScope.INLINE, true); } class StrikeThroughAttribute extends Attribute { const StrikeThroughAttribute() : super('strike', AttributeScope.INLINE, true); } class InlineCodeAttribute extends Attribute { const InlineCodeAttribute() : super('code', AttributeScope.INLINE, true); } class FontAttribute extends Attribute { const FontAttribute(String? val) : super('font', AttributeScope.INLINE, val); } class SizeAttribute extends Attribute { const SizeAttribute(String? val) : super('size', AttributeScope.INLINE, val); } class LinkAttribute extends Attribute { const LinkAttribute(String? val) : super('link', AttributeScope.INLINE, val); } class ColorAttribute extends Attribute { const ColorAttribute(String? val) : super('color', AttributeScope.INLINE, val); } class BackgroundAttribute extends Attribute { const BackgroundAttribute(String? val) : super('background', AttributeScope.INLINE, val); } /// This is custom attribute for hint class PlaceholderAttribute extends Attribute { const PlaceholderAttribute() : super('placeholder', AttributeScope.INLINE, true); } class HeaderAttribute extends Attribute { const HeaderAttribute({int? level}) : super('header', AttributeScope.BLOCK, level); } class IndentAttribute extends Attribute { const IndentAttribute({int? level}) : super('indent', AttributeScope.BLOCK, level); } class AlignAttribute extends Attribute { const AlignAttribute(String? val) : super('align', AttributeScope.BLOCK, val); } class ListAttribute extends Attribute { const ListAttribute(String? val) : super('list', AttributeScope.BLOCK, val); } class CodeBlockAttribute extends Attribute { const CodeBlockAttribute() : super('code-block', AttributeScope.BLOCK, true); } class BlockQuoteAttribute extends Attribute { const BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true); } class DirectionAttribute extends Attribute { const DirectionAttribute(String? val) : super('direction', AttributeScope.BLOCK, val); } class WidthAttribute extends Attribute { const WidthAttribute(String? val) : super('width', AttributeScope.IGNORE, val); } class HeightAttribute extends Attribute { const HeightAttribute(String? val) : super('height', AttributeScope.IGNORE, val); } class StyleAttribute extends Attribute { const StyleAttribute(String? val) : super('style', AttributeScope.IGNORE, val); } class TokenAttribute extends Attribute { const TokenAttribute(String val) : super('token', AttributeScope.IGNORE, val); } // `script` is supposed to be inline attribute but it is not supported yet class ScriptAttribute extends Attribute { ScriptAttribute(ScriptAttributes? val) : super('script', AttributeScope.IGNORE, val?.value); } enum ScriptAttributes { sup('super'), sub('sup'); const ScriptAttributes(this.value); final String value; } class ImageAttribute extends Attribute { const ImageAttribute(String? url) : super('image', AttributeScope.EMBEDS, url); } class VideoAttribute extends Attribute { const VideoAttribute(String? url) : super('video', AttributeScope.EMBEDS, url); }