Procházet zdrojové kódy

add line decoration

lucky1213 před 4 roky
rodič
revize
bf0d8cc03a

+ 40
- 1
packages/notus/lib/src/document/attributes.dart Zobrazit soubor

@@ -66,6 +66,7 @@ abstract class NotusAttributeBuilder<T> implements NotusAttributeKey<T> {
66 66
 /// List of supported attributes:
67 67
 ///
68 68
 ///   * [NotusAttribute.bold]
69
+///   * [NotusAttribute.line]
69 70
 ///   * [NotusAttribute.italic]
70 71
 ///   * [NotusAttribute.link]
71 72
 ///   * [NotusAttribute.heading]
@@ -73,6 +74,7 @@ abstract class NotusAttributeBuilder<T> implements NotusAttributeKey<T> {
73 74
 class NotusAttribute<T> implements NotusAttributeBuilder<T> {
74 75
   static final Map<String, NotusAttributeBuilder> _registry = {
75 76
     NotusAttribute.bold.key: NotusAttribute.bold,
77
+    NotusAttribute.line.key: NotusAttribute.line,
76 78
     NotusAttribute.italic.key: NotusAttribute.italic,
77 79
     NotusAttribute.link.key: NotusAttribute.link,
78 80
     NotusAttribute.heading.key: NotusAttribute.heading,
@@ -93,6 +95,13 @@ class NotusAttribute<T> implements NotusAttributeBuilder<T> {
93 95
   static const link = LinkAttributeBuilder._();
94 96
 
95 97
   // Line attributes
98
+  static const line = LineAttributeBuilder._();
99
+
100
+  /// Alias for [NotusAttribute.line.underline].
101
+  static NotusAttribute<String> get underline => line.underline;
102
+
103
+  /// Alias for [NotusAttribute.line.deleteline].
104
+  static NotusAttribute<String> get deleteline => line.deleteline;
96 105
 
97 106
   /// Heading style attribute.
98 107
   // ignore: const_eval_throws_exception
@@ -107,6 +116,15 @@ class NotusAttribute<T> implements NotusAttributeBuilder<T> {
107 116
   /// Alias for [NotusAttribute.heading.level3].
108 117
   static NotusAttribute<int> get h3 => heading.level3;
109 118
 
119
+  /// Alias for [NotusAttribute.heading.level4].
120
+  static NotusAttribute<int> get h4 => heading.level4;
121
+
122
+  /// Alias for [NotusAttribute.heading.level5].
123
+  static NotusAttribute<int> get h5 => heading.level5;
124
+
125
+  /// Alias for [NotusAttribute.heading.level6].
126
+  static NotusAttribute<int> get h6 => heading.level6;
127
+
110 128
   /// Block attribute
111 129
   // ignore: const_eval_throws_exception
112 130
   static const block = BlockAttributeBuilder._();
@@ -254,7 +272,7 @@ class NotusStyle {
254 272
   /// Returns collection of all attributes in this set.
255 273
   Iterable<NotusAttribute> get values => _data.values;
256 274
 
257
-  /// Puts [attribute] into this attribute set and returns result as a new set.
275
+  /// Puts [ 
258 276
   NotusStyle put(NotusAttribute attribute) {
259 277
     final result = Map<String, NotusAttribute>.from(_data);
260 278
     result[attribute.key] = attribute;
@@ -345,6 +363,18 @@ class LinkAttributeBuilder extends NotusAttributeBuilder<String> {
345 363
       NotusAttribute<String>._(key, scope, value);
346 364
 }
347 365
 
366
+class LineAttributeBuilder extends NotusAttributeBuilder<String> {
367
+  static const _kLine = 'line';
368
+  const LineAttributeBuilder._()
369
+      : super._(_kLine, NotusAttributeScope.inline);
370
+
371
+  // underline
372
+  NotusAttribute<String> get underline => NotusAttribute<String>._(key, scope, 'underline');
373
+
374
+  // deleteline
375
+  NotusAttribute<String> get deleteline => NotusAttribute<String>._(key, scope, 'deleteline');
376
+}
377
+
348 378
 /// Builder for heading attribute styles.
349 379
 ///
350 380
 /// There is no need to use this class directly, consider using
@@ -362,6 +392,15 @@ class HeadingAttributeBuilder extends NotusAttributeBuilder<int> {
362 392
 
363 393
   /// Level 3 heading, equivalent of `H3` in HTML.
364 394
   NotusAttribute<int> get level3 => NotusAttribute<int>._(key, scope, 3);
395
+
396
+  /// Level 3 heading, equivalent of `H4` in HTML.
397
+  NotusAttribute<int> get level4 => NotusAttribute<int>._(key, scope, 4);
398
+
399
+  /// Level 3 heading, equivalent of `H5` in HTML.
400
+  NotusAttribute<int> get level5 => NotusAttribute<int>._(key, scope, 5);
401
+
402
+  /// Level 3 heading, equivalent of `H6` in HTML.
403
+  NotusAttribute<int> get level6 => NotusAttribute<int>._(key, scope, 6);
365 404
 }
366 405
 
367 406
 /// Builder for block attribute styles (number/bullet lists, code and quote).

+ 25
- 12
packages/zefyr/example/lib/src/full_page.dart Zobrazit soubor

@@ -31,11 +31,14 @@ class FullPageEditorScreen extends StatefulWidget {
31 31
 }
32 32
 
33 33
 final doc =
34
-    r'[{"insert":"Zefyr"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":'
35
-    r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr'
36
-    r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle'
37
-    r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin'
38
-    r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]';
34
+    r'[{"insert":"Zefyr", "attributes":{"line": "underline"}},{"insert":"\n"}]';
35
+    
36
+    
37
+    // ,{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":'
38
+    // r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr'
39
+    // r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle'
40
+    // r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin'
41
+    // r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]';
39 42
 
40 43
 Delta getDelta() {
41 44
   return Delta.fromJson(json.decode(doc) as List);
@@ -83,13 +86,23 @@ class _FullPageEditorScreenState extends State<FullPageEditorScreen> {
83 86
         ],
84 87
       ),
85 88
       body: ZefyrScaffold(
86
-        child: ZefyrEditor(
87
-          autofocus: false,
88
-          controller: _controller,
89
-          focusNode: _focusNode,
90
-          mode: ZefyrMode.edit,
91
-          imageDelegate: CustomImageDelegate(),
92
-          keyboardAppearance: _darkTheme ? Brightness.dark : Brightness.light,
89
+        child: ZefyrTheme(
90
+          data: ZefyrThemeData(
91
+            // attributeTheme: AttributeTheme(
92
+            //   link: TextStyle(
93
+            //     color: Colors.red,
94
+            //   ),
95
+            // ),
96
+          ),
97
+          child: ZefyrEditor(
98
+            autofocus: false,
99
+            controller: _controller,
100
+            focusNode: _focusNode,
101
+            mode: ZefyrMode.edit,
102
+            imageDelegate: CustomImageDelegate(),
103
+            linkDelegate: CustomLinkDelegate(),
104
+            keyboardAppearance: _darkTheme ? Brightness.dark : Brightness.light,
105
+          ),
93 106
         ),
94 107
       ),
95 108
     );

+ 64
- 0
packages/zefyr/example/lib/src/images.dart Zobrazit soubor

@@ -53,3 +53,67 @@ class CustomImageDelegate implements ZefyrImageDelegate<ImageSource> {
53 53
     // return compressFile.uri.toString();
54 54
   }
55 55
 }
56
+
57
+class CustomLinkDelegate implements ZefyrLinkDelegate {
58
+
59
+  @override
60
+  Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value) async {
61
+    TextEditingController _controller = TextEditingController();
62
+    return showGeneralDialog<ZefyrLinkEntity>(
63
+      context: context,
64
+      pageBuilder: (BuildContext buildContext, Animation<double> animation,
65
+          Animation<double> secondaryAnimation) {
66
+        return GestureDetector(
67
+          onTap: () {
68
+            Navigator.pop(buildContext);
69
+          },
70
+          child: Material(
71
+            type: MaterialType.transparency,
72
+            child: Center(
73
+              child: Container(
74
+                height: 200,
75
+                width: 200,
76
+                color: Colors.white,
77
+                child: Column(
78
+                  children: <Widget>[
79
+                    TextField(controller: _controller,),
80
+                    FlatButton(
81
+                      onPressed: () {
82
+                        Navigator.pop(buildContext, ZefyrLinkEntity(
83
+                          text: '测试用',
84
+                          url: _controller.text,
85
+                        ));
86
+                      },
87
+                      child: Text('确定'),
88
+                    )
89
+                  ],
90
+                ),
91
+              ),
92
+            ),
93
+          ),
94
+        );
95
+      },
96
+      barrierDismissible: true,
97
+      barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
98
+      barrierColor: Colors.black45,
99
+      transitionDuration: const Duration(milliseconds: 150),
100
+      transitionBuilder: _buildMaterialDialogTransitions,
101
+      useRootNavigator: true,
102
+    );
103
+  }
104
+  
105
+}
106
+
107
+Widget _buildMaterialDialogTransitions(
108
+    BuildContext context,
109
+    Animation<double> animation,
110
+    Animation<double> secondaryAnimation,
111
+    Widget child) {
112
+  return FadeTransition(
113
+    opacity: CurvedAnimation(
114
+      parent: animation,
115
+      curve: Curves.easeOut,
116
+    ),
117
+    child: child,
118
+  );
119
+}

+ 27
- 3
packages/zefyr/lib/src/widgets/buttons.dart Zobrazit soubor

@@ -12,6 +12,7 @@ import 'package:photo/photo.dart';
12 12
 import 'package:photo_manager/photo_manager.dart';
13 13
 import 'package:url_launcher/url_launcher.dart';
14 14
 
15
+import 'link.dart';
15 16
 import 'scope.dart';
16 17
 import 'theme.dart';
17 18
 import 'toolbar.dart';
@@ -806,7 +807,8 @@ class _LinkButtonState extends State<LinkButton> {
806 807
     return toolbar.buildButton(
807 808
       context,
808 809
       ZefyrToolbarAction.link,
809
-      onPressed: enabled ? showOverlay : null,
810
+      // onPressed: enabled ? showOverlay : null,
811
+      onPressed: showOverlay,
810 812
     );
811 813
   }
812 814
 
@@ -821,9 +823,31 @@ class _LinkButtonState extends State<LinkButton> {
821 823
     return defaultValue;
822 824
   }
823 825
 
824
-  void showOverlay() {
826
+  OverlayEntry _overlayEntry;
827
+
828
+  Future<void> showOverlay() async {
825 829
     final toolbar = ZefyrToolbar.of(context);
826
-    toolbar.showOverlay(buildOverlay, ZefyrToolbarAction.link).whenComplete(cancelEdit);
830
+    var _selection = toolbar.editor.selection;
831
+    var value = ZefyrLinkEntity();
832
+    if (hasLink(toolbar.editor.selectionStyle)) {
833
+      value = value.copyWith(url: toolbar.editor.selectionStyle.value(NotusAttribute.link));
834
+    }
835
+    // 已选中
836
+    if(!toolbar.editor.selection.isCollapsed) {
837
+      var text = toolbar.editor.controller.document.toPlainText().substring(toolbar.editor.selection.start, toolbar.editor.selection.end);
838
+      value = value.copyWith(text: text);
839
+    };
840
+    var result = await toolbar.editor.linkDelegate.fillLink(context, value);
841
+    
842
+    if (result != null) {
843
+      toolbar.editor.updateSelection(_selection, source: ChangeSource.local);
844
+      _selection = toolbar.editor.selection;
845
+      toolbar.editor.controller.replaceText(_selection.start, _selection.end - _selection.start, result.text, selection: _selection.copyWith(
846
+        baseOffset: _selection.start,
847
+        extentOffset: _selection.start + result.text.length,
848
+      ));
849
+      toolbar.editor.formatSelection(NotusAttribute.link.fromString(result.url));
850
+    }
827 851
   }
828 852
 
829 853
   void closeOverlay() {

+ 8
- 0
packages/zefyr/lib/src/widgets/common.dart Zobrazit soubor

@@ -140,6 +140,14 @@ class _ZefyrLineState extends State<ZefyrLine> {
140 140
     if (style.containsSame(NotusAttribute.bold)) {
141 141
       result = result.merge(theme.attributeTheme.bold);
142 142
     }
143
+    if (style.contains(NotusAttribute.line)) {
144
+      final _style = style.get(NotusAttribute.line);
145
+      if (_style == NotusAttribute.line.underline) {
146
+        result = result.merge(theme.attributeTheme.underline);
147
+      } else if (_style == NotusAttribute.line.deleteline) {
148
+        result = result.merge(theme.attributeTheme.deleteline);
149
+      }
150
+    }
143 151
     if (style.containsSame(NotusAttribute.italic)) {
144 152
       result = result.merge(theme.attributeTheme.italic);
145 153
     }

+ 11
- 0
packages/zefyr/lib/src/widgets/controller.dart Zobrazit soubor

@@ -172,6 +172,17 @@ class ZefyrController extends ChangeNotifier {
172 172
       // Add the attribute to our toggledStyle. It will be used later upon insertion.
173 173
       _toggledStyles = toggledStyles.put(attribute);
174 174
     }
175
+    // if (attribute.key == NotusAttribute.underline.key) {
176
+    //   if ((attribute.value ?? false) == true) {
177
+    //     toggledStyles.merge(NotusAttribute.deleteline);
178
+    //   }
179
+    // }
180
+    // if (attribute.key == NotusAttribute.deleteline.key) {
181
+    //   if ((attribute.value ?? false) == true) {
182
+    //     toggledStyles.merge(NotusAttribute.underline);
183
+    //   }
184
+    // }
185
+    // if (change.last.key == '')
175 186
 
176 187
     // Transform selection against the composed change and give priority to
177 188
     // the change. This is needed in cases when format operation actually

+ 3
- 0
packages/zefyr/lib/src/widgets/editable_text.dart Zobrazit soubor

@@ -248,6 +248,9 @@ class _ZefyrEditableTextState extends State<ZefyrEditableText>
248 248
         return ZefyrHeading(node: node);
249 249
       }
250 250
       return ZefyrParagraph(node: node);
251
+      //  else if (node.style.contains(NotusAttribute.line)) {
252
+      //   return ZefyrLineDecoration(node: node);
253
+      // }
251 254
     }
252 255
 
253 256
     final BlockNode block = node;

+ 11
- 1
packages/zefyr/lib/src/widgets/editor.dart Zobrazit soubor

@@ -4,11 +4,11 @@
4 4
 import 'package:flutter/cupertino.dart';
5 5
 import 'package:flutter/material.dart';
6 6
 import 'package:flutter/widgets.dart';
7
+import 'package:zefyr/src/widgets/link.dart';
7 8
 
8 9
 import 'controller.dart';
9 10
 import 'editable_text.dart';
10 11
 import 'image.dart';
11
-import 'input.dart';
12 12
 import 'mode.dart';
13 13
 import 'scaffold.dart';
14 14
 import 'scope.dart';
@@ -26,6 +26,7 @@ class ZefyrEditor extends StatefulWidget {
26 26
     this.padding = const EdgeInsets.symmetric(horizontal: 16.0),
27 27
     this.toolbarDelegate,
28 28
     this.imageDelegate,
29
+    this.linkDelegate,
29 30
     this.selectionControls,
30 31
     this.physics,
31 32
     this.keyboardAppearance,
@@ -60,6 +61,8 @@ class ZefyrEditor extends StatefulWidget {
60 61
   /// This delegate is required if embedding images is allowed.
61 62
   final ZefyrImageDelegate imageDelegate;
62 63
 
64
+  final ZefyrLinkDelegate linkDelegate;
65
+
63 66
   /// Optional delegate for building the text selection handles and toolbar.
64 67
   ///
65 68
   /// If not provided then platform-specific implementation is used by default.
@@ -84,6 +87,7 @@ class ZefyrEditor extends StatefulWidget {
84 87
 
85 88
 class _ZefyrEditorState extends State<ZefyrEditor> {
86 89
   ZefyrImageDelegate _imageDelegate;
90
+  ZefyrLinkDelegate _linkDelegate;
87 91
   ZefyrScope _scope;
88 92
   ZefyrThemeData _themeData;
89 93
   GlobalKey<ZefyrToolbarState> _toolbarKey;
@@ -134,6 +138,7 @@ class _ZefyrEditorState extends State<ZefyrEditor> {
134 138
   void initState() {
135 139
     super.initState();
136 140
     _imageDelegate = widget.imageDelegate;
141
+    _linkDelegate = widget.linkDelegate;
137 142
   }
138 143
 
139 144
   @override
@@ -146,6 +151,10 @@ class _ZefyrEditorState extends State<ZefyrEditor> {
146 151
       _imageDelegate = widget.imageDelegate;
147 152
       _scope.imageDelegate = _imageDelegate;
148 153
     }
154
+    if (widget.linkDelegate != oldWidget.linkDelegate) {
155
+      _linkDelegate = widget.linkDelegate;
156
+      _scope.linkDelegate = _linkDelegate;
157
+    }
149 158
   }
150 159
 
151 160
   @override
@@ -161,6 +170,7 @@ class _ZefyrEditorState extends State<ZefyrEditor> {
161 170
       _scope = ZefyrScope.editable(
162 171
         mode: widget.mode,
163 172
         imageDelegate: _imageDelegate,
173
+        linkDelegate: _linkDelegate,
164 174
         controller: widget.controller,
165 175
         focusNode: widget.focusNode,
166 176
         focusScope: FocusScope.of(context),

+ 0
- 1
packages/zefyr/lib/src/widgets/image.dart Zobrazit soubor

@@ -11,7 +11,6 @@ import 'package:flutter/widgets.dart';
11 11
 import 'package:meta/meta.dart';
12 12
 import 'package:notus/notus.dart';
13 13
 import 'package:zefyr/zefyr.dart';
14
-import 'package:photo_manager/photo_manager.dart';
15 14
 
16 15
 import 'editable_box.dart';
17 16
 

+ 27
- 0
packages/zefyr/lib/src/widgets/link.dart Zobrazit soubor

@@ -0,0 +1,27 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:meta/meta.dart';
3
+
4
+class ZefyrLinkEntity {
5
+  const ZefyrLinkEntity({this.text = '', this.url = ''});
6
+  final String text;
7
+  final String url;
8
+
9
+  ZefyrLinkEntity copyWith({
10
+    String text,
11
+    String url,
12
+  }) {
13
+    return ZefyrLinkEntity(
14
+      text: text ?? this.text,
15
+      url: url ?? this.url,
16
+    );
17
+  }
18
+
19
+  @override
20
+  String toString() => 'text: $text, url: $url';
21
+}
22
+
23
+@experimental
24
+abstract class ZefyrLinkDelegate {
25
+
26
+  Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value);
27
+}

+ 5
- 1
packages/zefyr/lib/src/widgets/list.dart Zobrazit soubor

@@ -79,7 +79,11 @@ class ZefyrListItem extends StatelessWidget {
79 79
       );
80 80
       padding = blockTheme.linePadding;
81 81
     }
82
-
82
+    //  else if (node.style.contains(NotusAttribute.line)) {
83
+    //   textStyle = ZefyrLineDecoration.themeOf(node, context);
84
+    //   padding = theme.defaultLineTheme.padding;
85
+    //   content = ZefyrLineDecoration(node: node);
86
+    // }
83 87
     Widget bullet =
84 88
         SizedBox(width: 24.0, child: Text(bulletText, style: textStyle));
85 89
     if (padding != null) {

+ 39
- 0
packages/zefyr/lib/src/widgets/paragraph.dart Zobrazit soubor

@@ -30,6 +30,39 @@ class ZefyrParagraph extends StatelessWidget {
30 30
   }
31 31
 }
32 32
 
33
+class ZefyrLineDecoration extends StatelessWidget {
34
+  ZefyrLineDecoration({Key key, @required this.node, this.blockStyle})
35
+      : super(key: key);
36
+
37
+  final LineNode node;
38
+  final TextStyle blockStyle;
39
+
40
+  @override
41
+  Widget build(BuildContext context) {
42
+    final theme = ZefyrTheme.of(context);
43
+    TextStyle style = themeOf(node, context);
44
+    if (blockStyle != null) {
45
+      style = style.merge(blockStyle);
46
+    }
47
+    return ZefyrLine(
48
+      node: node,
49
+      style: style,
50
+      padding: theme.defaultLineTheme.padding,
51
+    );
52
+  }
53
+
54
+  static TextStyle themeOf(LineNode node, BuildContext context) {
55
+    final theme = ZefyrTheme.of(context);
56
+    final style = node.style.get(NotusAttribute.line);
57
+    if (style == NotusAttribute.line.underline) {
58
+      return theme.attributeTheme.underline;
59
+    } else if (style == NotusAttribute.line.deleteline) {
60
+      return theme.attributeTheme.deleteline;
61
+    }
62
+    throw UnimplementedError('Unsupported heading style $style');
63
+  }
64
+}
65
+
33 66
 /// Represents heading-styled line in [ZefyrEditor].
34 67
 class ZefyrHeading extends StatelessWidget {
35 68
   ZefyrHeading({Key key, @required this.node, this.blockStyle})
@@ -62,6 +95,12 @@ class ZefyrHeading extends StatelessWidget {
62 95
       return theme.attributeTheme.heading2;
63 96
     } else if (style == NotusAttribute.heading.level3) {
64 97
       return theme.attributeTheme.heading3;
98
+    } else if (style == NotusAttribute.heading.level4) {
99
+      return theme.attributeTheme.heading4;
100
+    } else if (style == NotusAttribute.heading.level5) {
101
+      return theme.attributeTheme.heading5;
102
+    } else if (style == NotusAttribute.heading.level6) {
103
+      return theme.attributeTheme.heading6;
65 104
     }
66 105
     throw UnimplementedError('Unsupported heading style $style');
67 106
   }

+ 12
- 0
packages/zefyr/lib/src/widgets/scope.dart Zobrazit soubor

@@ -8,6 +8,7 @@ import 'cursor_timer.dart';
8 8
 import 'editor.dart';
9 9
 import 'image.dart';
10 10
 import 'input.dart';
11
+import 'link.dart';
11 12
 import 'mode.dart';
12 13
 import 'render_context.dart';
13 14
 import 'toolbar.dart';
@@ -42,6 +43,7 @@ class ZefyrScope extends ChangeNotifier {
42 43
     @required FocusNode focusNode,
43 44
     @required FocusScopeNode focusScope,
44 45
     ZefyrImageDelegate imageDelegate,
46
+    ZefyrLinkDelegate linkDelegate,
45 47
   })  : assert(mode != null),
46 48
         assert(controller != null),
47 49
         assert(focusNode != null),
@@ -50,6 +52,7 @@ class ZefyrScope extends ChangeNotifier {
50 52
         _mode = mode,
51 53
         _controller = controller,
52 54
         _imageDelegate = imageDelegate,
55
+        _linkDelegate = linkDelegate,
53 56
         _focusNode = focusNode,
54 57
         _focusScope = focusScope,
55 58
         _cursorTimer = CursorTimer(),
@@ -130,6 +133,15 @@ class ZefyrScope extends ChangeNotifier {
130 133
     }
131 134
   }
132 135
 
136
+  ZefyrLinkDelegate _linkDelegate;
137
+  ZefyrLinkDelegate get linkDelegate => _linkDelegate;
138
+  set linkDelegate(ZefyrLinkDelegate value) {
139
+    if (_linkDelegate != value) {
140
+      _linkDelegate = value;
141
+      notifyListeners();
142
+    }
143
+  }
144
+
133 145
   ZefyrMode _mode;
134 146
   ZefyrMode get mode => _mode;
135 147
   set mode(ZefyrMode value) {

+ 73
- 4
packages/zefyr/lib/src/widgets/theme.dart Zobrazit soubor

@@ -285,6 +285,12 @@ class AttributeTheme {
285 285
   /// Style used to render "bold" text.
286 286
   final TextStyle bold;
287 287
 
288
+  /// Style used to render "underline" text.
289
+  final TextStyle underline;
290
+
291
+  /// Style used to render "deleteline" text.
292
+  final TextStyle deleteline;
293
+
288 294
   /// Style used to render "italic" text.
289 295
   final TextStyle italic;
290 296
 
@@ -300,6 +306,15 @@ class AttributeTheme {
300 306
   /// Style theme used to render smaller headings.
301 307
   final LineTheme heading3;
302 308
 
309
+  /// Style theme used to render largest headings.
310
+  final LineTheme heading4;
311
+
312
+  /// Style theme used to render medium headings.
313
+  final LineTheme heading5;
314
+
315
+  /// Style theme used to render smaller headings.
316
+  final LineTheme heading6;
317
+
303 318
   /// Style theme used to render bullet lists.
304 319
   final BlockTheme bulletList;
305 320
 
@@ -315,11 +330,16 @@ class AttributeTheme {
315 330
   /// Creates a [AttributeTheme] given a set of exact values.
316 331
   AttributeTheme({
317 332
     this.bold,
333
+    this.underline,
334
+    this.deleteline,
318 335
     this.italic,
319 336
     this.link,
320 337
     this.heading1,
321 338
     this.heading2,
322 339
     this.heading3,
340
+    this.heading4,
341
+    this.heading5,
342
+    this.heading6,
323 343
     this.bulletList,
324 344
     this.numberList,
325 345
     this.quote,
@@ -346,6 +366,8 @@ class AttributeTheme {
346 366
 
347 367
     return AttributeTheme(
348 368
       bold: TextStyle(fontWeight: FontWeight.bold),
369
+      underline: defaultLineTheme.textStyle.copyWith(decoration: TextDecoration.underline),
370
+      deleteline: defaultLineTheme.textStyle.copyWith(decoration: TextDecoration.lineThrough),
349 371
       italic: TextStyle(fontStyle: FontStyle.italic),
350 372
       link: TextStyle(
351 373
         decoration: TextDecoration.underline,
@@ -353,7 +375,7 @@ class AttributeTheme {
353 375
       ),
354 376
       heading1: LineTheme(
355 377
         textStyle: defaultLineTheme.textStyle.copyWith(
356
-          fontSize: 34.0,
378
+          fontSize: 38.0,
357 379
           color: defaultLineTheme.textStyle.color.withOpacity(0.7),
358 380
           height: 1.15,
359 381
           fontWeight: FontWeight.w300,
@@ -362,22 +384,49 @@ class AttributeTheme {
362 384
       ),
363 385
       heading2: LineTheme(
364 386
         textStyle: defaultLineTheme.textStyle.copyWith(
365
-          fontSize: 24.0,
387
+          fontSize: 34.0,
366 388
           color: defaultLineTheme.textStyle.color.withOpacity(0.7),
367 389
           height: 1.15,
368 390
           fontWeight: FontWeight.normal,
369 391
         ),
370
-        padding: EdgeInsets.only(top: 8.0),
392
+        padding: EdgeInsets.only(top: 16.0),
371 393
       ),
372 394
       heading3: LineTheme(
373 395
         textStyle: defaultLineTheme.textStyle.copyWith(
374
-          fontSize: 20.0,
396
+          fontSize: 30.0,
397
+          color: defaultLineTheme.textStyle.color.withOpacity(0.7),
398
+          height: 1.15,
399
+          fontWeight: FontWeight.w500,
400
+        ),
401
+        padding: EdgeInsets.only(top: 16.0),
402
+      ),
403
+      heading4: LineTheme(
404
+        textStyle: defaultLineTheme.textStyle.copyWith(
405
+          fontSize: 26.0,
406
+          color: defaultLineTheme.textStyle.color.withOpacity(0.7),
407
+          height: 1.15,
408
+          fontWeight: FontWeight.w500,
409
+        ),
410
+        padding: EdgeInsets.only(top: 12.0),
411
+      ),
412
+      heading5: LineTheme(
413
+        textStyle: defaultLineTheme.textStyle.copyWith(
414
+          fontSize: 22.0,
375 415
           color: defaultLineTheme.textStyle.color.withOpacity(0.7),
376 416
           height: 1.15,
377 417
           fontWeight: FontWeight.w500,
378 418
         ),
379 419
         padding: EdgeInsets.only(top: 8.0),
380 420
       ),
421
+      heading6: LineTheme(
422
+        textStyle: defaultLineTheme.textStyle.copyWith(
423
+          fontSize: 18.0,
424
+          color: defaultLineTheme.textStyle.color.withOpacity(0.7),
425
+          height: 1.15,
426
+          fontWeight: FontWeight.w500,
427
+        ),
428
+        padding: EdgeInsets.only(top: 4.0),
429
+      ),
381 430
       bulletList: BlockTheme(
382 431
         padding: EdgeInsets.symmetric(vertical: 8.0),
383 432
         linePadding: EdgeInsets.symmetric(vertical: 2.0),
@@ -411,11 +460,15 @@ class AttributeTheme {
411 460
   /// been merged with the matching property from the `other` object.
412 461
   AttributeTheme copyWith({
413 462
     TextStyle bold,
463
+    TextStyle underline,
414 464
     TextStyle italic,
415 465
     TextStyle link,
416 466
     LineTheme heading1,
417 467
     LineTheme heading2,
418 468
     LineTheme heading3,
469
+    LineTheme heading4,
470
+    LineTheme heading5,
471
+    LineTheme heading6,
419 472
     BlockTheme bulletList,
420 473
     BlockTheme numberList,
421 474
     BlockTheme quote,
@@ -423,11 +476,15 @@ class AttributeTheme {
423 476
   }) {
424 477
     return AttributeTheme(
425 478
       bold: bold ?? this.bold,
479
+      underline: underline ?? this.underline,
426 480
       italic: italic ?? this.italic,
427 481
       link: link ?? this.link,
428 482
       heading1: heading1 ?? this.heading1,
429 483
       heading2: heading2 ?? this.heading2,
430 484
       heading3: heading3 ?? this.heading3,
485
+      heading4: heading4 ?? this.heading4,
486
+      heading5: heading5 ?? this.heading5,
487
+      heading6: heading6 ?? this.heading6,
431 488
       bulletList: bulletList ?? this.bulletList,
432 489
       numberList: numberList ?? this.numberList,
433 490
       quote: quote ?? this.quote,
@@ -441,11 +498,15 @@ class AttributeTheme {
441 498
     if (other == null) return this;
442 499
     return copyWith(
443 500
       bold: bold?.merge(other.bold) ?? other.bold,
501
+      underline: underline?.merge(other.underline) ?? other.underline,
444 502
       italic: italic?.merge(other.italic) ?? other.italic,
445 503
       link: link?.merge(other.link) ?? other.link,
446 504
       heading1: heading1?.merge(other.heading1) ?? other.heading1,
447 505
       heading2: heading2?.merge(other.heading2) ?? other.heading2,
448 506
       heading3: heading3?.merge(other.heading3) ?? other.heading3,
507
+      heading4: heading4?.merge(other.heading4) ?? other.heading4,
508
+      heading5: heading5?.merge(other.heading5) ?? other.heading5,
509
+      heading6: heading6?.merge(other.heading6) ?? other.heading6,
449 510
       bulletList: bulletList?.merge(other.bulletList) ?? other.bulletList,
450 511
       numberList: numberList?.merge(other.numberList) ?? other.numberList,
451 512
       quote: quote?.merge(other.quote) ?? other.quote,
@@ -458,11 +519,15 @@ class AttributeTheme {
458 519
     if (other.runtimeType != runtimeType) return false;
459 520
     final AttributeTheme otherTheme = other;
460 521
     return (otherTheme.bold == bold) &&
522
+        (otherTheme.underline == underline) &&
461 523
         (otherTheme.italic == italic) &&
462 524
         (otherTheme.link == link) &&
463 525
         (otherTheme.heading1 == heading1) &&
464 526
         (otherTheme.heading2 == heading2) &&
465 527
         (otherTheme.heading3 == heading3) &&
528
+        (otherTheme.heading4 == heading4) &&
529
+        (otherTheme.heading5 == heading5) &&
530
+        (otherTheme.heading6 == heading6) &&
466 531
         (otherTheme.bulletList == bulletList) &&
467 532
         (otherTheme.numberList == numberList) &&
468 533
         (otherTheme.quote == quote) &&
@@ -473,11 +538,15 @@ class AttributeTheme {
473 538
   int get hashCode {
474 539
     return hashList([
475 540
       bold,
541
+      underline,
476 542
       italic,
477 543
       link,
478 544
       heading1,
479 545
       heading2,
480 546
       heading3,
547
+      heading4,
548
+      heading5,
549
+      heading6,
481 550
       bulletList,
482 551
       numberList,
483 552
       quote,

+ 5
- 0
packages/zefyr/lib/src/widgets/toolbar.dart Zobrazit soubor

@@ -51,6 +51,8 @@ enum ZefyrToolbarAction {
51 51
 
52 52
 final kZefyrToolbarAttributeActions = <ZefyrToolbarAction, NotusAttributeKey>{
53 53
   ZefyrToolbarAction.bold: NotusAttribute.bold,
54
+  ZefyrToolbarAction.underline: NotusAttribute.underline,
55
+  ZefyrToolbarAction.deleteline: NotusAttribute.deleteline,
54 56
   ZefyrToolbarAction.italic: NotusAttribute.italic,
55 57
   ZefyrToolbarAction.link: NotusAttribute.link,
56 58
   // ZefyrToolbarAction.text: NotusAttribute.heading,
@@ -58,6 +60,9 @@ final kZefyrToolbarAttributeActions = <ZefyrToolbarAction, NotusAttributeKey>{
58 60
   ZefyrToolbarAction.headingLevel1: NotusAttribute.heading.level1,
59 61
   ZefyrToolbarAction.headingLevel2: NotusAttribute.heading.level2,
60 62
   ZefyrToolbarAction.headingLevel3: NotusAttribute.heading.level3,
63
+  ZefyrToolbarAction.headingLevel4: NotusAttribute.heading.level4,
64
+  ZefyrToolbarAction.headingLevel5: NotusAttribute.heading.level5,
65
+  ZefyrToolbarAction.headingLevel6: NotusAttribute.heading.level6,
61 66
   ZefyrToolbarAction.bulletList: NotusAttribute.block.bulletList,
62 67
   ZefyrToolbarAction.numberList: NotusAttribute.block.numberList,
63 68
   ZefyrToolbarAction.code: NotusAttribute.block.code,

+ 3
- 0
packages/zefyr/lib/src/widgets/view.dart Zobrazit soubor

@@ -91,6 +91,9 @@ class ZefyrViewState extends State<ZefyrView> {
91 91
         return ZefyrHeading(node: node);
92 92
       }
93 93
       return ZefyrParagraph(node: node);
94
+      //  else if (node.style.contains(NotusAttribute.line)) {
95
+      //   return ZefyrLineDecoration(node: node);
96
+      // }
94 97
     }
95 98
 
96 99
     final BlockNode block = node;

+ 1
- 0
packages/zefyr/lib/zefyr.dart Zobrazit soubor

@@ -18,6 +18,7 @@ export 'src/widgets/editor.dart';
18 18
 export 'src/widgets/field.dart';
19 19
 export 'src/widgets/horizontal_rule.dart';
20 20
 export 'src/widgets/image.dart';
21
+export 'src/widgets/link.dart';
21 22
 export 'src/widgets/list.dart';
22 23
 export 'src/widgets/mode.dart';
23 24
 export 'src/widgets/paragraph.dart';