소스 검색

add line decoration

lucky1213 5 년 전
부모
커밋
bf0d8cc03a

+ 40
- 1
packages/notus/lib/src/document/attributes.dart 파일 보기

66
 /// List of supported attributes:
66
 /// List of supported attributes:
67
 ///
67
 ///
68
 ///   * [NotusAttribute.bold]
68
 ///   * [NotusAttribute.bold]
69
+///   * [NotusAttribute.line]
69
 ///   * [NotusAttribute.italic]
70
 ///   * [NotusAttribute.italic]
70
 ///   * [NotusAttribute.link]
71
 ///   * [NotusAttribute.link]
71
 ///   * [NotusAttribute.heading]
72
 ///   * [NotusAttribute.heading]
73
 class NotusAttribute<T> implements NotusAttributeBuilder<T> {
74
 class NotusAttribute<T> implements NotusAttributeBuilder<T> {
74
   static final Map<String, NotusAttributeBuilder> _registry = {
75
   static final Map<String, NotusAttributeBuilder> _registry = {
75
     NotusAttribute.bold.key: NotusAttribute.bold,
76
     NotusAttribute.bold.key: NotusAttribute.bold,
77
+    NotusAttribute.line.key: NotusAttribute.line,
76
     NotusAttribute.italic.key: NotusAttribute.italic,
78
     NotusAttribute.italic.key: NotusAttribute.italic,
77
     NotusAttribute.link.key: NotusAttribute.link,
79
     NotusAttribute.link.key: NotusAttribute.link,
78
     NotusAttribute.heading.key: NotusAttribute.heading,
80
     NotusAttribute.heading.key: NotusAttribute.heading,
93
   static const link = LinkAttributeBuilder._();
95
   static const link = LinkAttributeBuilder._();
94
 
96
 
95
   // Line attributes
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
   /// Heading style attribute.
106
   /// Heading style attribute.
98
   // ignore: const_eval_throws_exception
107
   // ignore: const_eval_throws_exception
107
   /// Alias for [NotusAttribute.heading.level3].
116
   /// Alias for [NotusAttribute.heading.level3].
108
   static NotusAttribute<int> get h3 => heading.level3;
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
   /// Block attribute
128
   /// Block attribute
111
   // ignore: const_eval_throws_exception
129
   // ignore: const_eval_throws_exception
112
   static const block = BlockAttributeBuilder._();
130
   static const block = BlockAttributeBuilder._();
254
   /// Returns collection of all attributes in this set.
272
   /// Returns collection of all attributes in this set.
255
   Iterable<NotusAttribute> get values => _data.values;
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
   NotusStyle put(NotusAttribute attribute) {
276
   NotusStyle put(NotusAttribute attribute) {
259
     final result = Map<String, NotusAttribute>.from(_data);
277
     final result = Map<String, NotusAttribute>.from(_data);
260
     result[attribute.key] = attribute;
278
     result[attribute.key] = attribute;
345
       NotusAttribute<String>._(key, scope, value);
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
 /// Builder for heading attribute styles.
378
 /// Builder for heading attribute styles.
349
 ///
379
 ///
350
 /// There is no need to use this class directly, consider using
380
 /// There is no need to use this class directly, consider using
362
 
392
 
363
   /// Level 3 heading, equivalent of `H3` in HTML.
393
   /// Level 3 heading, equivalent of `H3` in HTML.
364
   NotusAttribute<int> get level3 => NotusAttribute<int>._(key, scope, 3);
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
 /// Builder for block attribute styles (number/bullet lists, code and quote).
406
 /// Builder for block attribute styles (number/bullet lists, code and quote).

+ 25
- 12
packages/zefyr/example/lib/src/full_page.dart 파일 보기

31
 }
31
 }
32
 
32
 
33
 final doc =
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
 Delta getDelta() {
43
 Delta getDelta() {
41
   return Delta.fromJson(json.decode(doc) as List);
44
   return Delta.fromJson(json.decode(doc) as List);
83
         ],
86
         ],
84
       ),
87
       ),
85
       body: ZefyrScaffold(
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 파일 보기

53
     // return compressFile.uri.toString();
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 파일 보기

12
 import 'package:photo_manager/photo_manager.dart';
12
 import 'package:photo_manager/photo_manager.dart';
13
 import 'package:url_launcher/url_launcher.dart';
13
 import 'package:url_launcher/url_launcher.dart';
14
 
14
 
15
+import 'link.dart';
15
 import 'scope.dart';
16
 import 'scope.dart';
16
 import 'theme.dart';
17
 import 'theme.dart';
17
 import 'toolbar.dart';
18
 import 'toolbar.dart';
806
     return toolbar.buildButton(
807
     return toolbar.buildButton(
807
       context,
808
       context,
808
       ZefyrToolbarAction.link,
809
       ZefyrToolbarAction.link,
809
-      onPressed: enabled ? showOverlay : null,
810
+      // onPressed: enabled ? showOverlay : null,
811
+      onPressed: showOverlay,
810
     );
812
     );
811
   }
813
   }
812
 
814
 
821
     return defaultValue;
823
     return defaultValue;
822
   }
824
   }
823
 
825
 
824
-  void showOverlay() {
826
+  OverlayEntry _overlayEntry;
827
+
828
+  Future<void> showOverlay() async {
825
     final toolbar = ZefyrToolbar.of(context);
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
   void closeOverlay() {
853
   void closeOverlay() {

+ 8
- 0
packages/zefyr/lib/src/widgets/common.dart 파일 보기

140
     if (style.containsSame(NotusAttribute.bold)) {
140
     if (style.containsSame(NotusAttribute.bold)) {
141
       result = result.merge(theme.attributeTheme.bold);
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
     if (style.containsSame(NotusAttribute.italic)) {
151
     if (style.containsSame(NotusAttribute.italic)) {
144
       result = result.merge(theme.attributeTheme.italic);
152
       result = result.merge(theme.attributeTheme.italic);
145
     }
153
     }

+ 11
- 0
packages/zefyr/lib/src/widgets/controller.dart 파일 보기

172
       // Add the attribute to our toggledStyle. It will be used later upon insertion.
172
       // Add the attribute to our toggledStyle. It will be used later upon insertion.
173
       _toggledStyles = toggledStyles.put(attribute);
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
     // Transform selection against the composed change and give priority to
187
     // Transform selection against the composed change and give priority to
177
     // the change. This is needed in cases when format operation actually
188
     // the change. This is needed in cases when format operation actually

+ 3
- 0
packages/zefyr/lib/src/widgets/editable_text.dart 파일 보기

248
         return ZefyrHeading(node: node);
248
         return ZefyrHeading(node: node);
249
       }
249
       }
250
       return ZefyrParagraph(node: node);
250
       return ZefyrParagraph(node: node);
251
+      //  else if (node.style.contains(NotusAttribute.line)) {
252
+      //   return ZefyrLineDecoration(node: node);
253
+      // }
251
     }
254
     }
252
 
255
 
253
     final BlockNode block = node;
256
     final BlockNode block = node;

+ 11
- 1
packages/zefyr/lib/src/widgets/editor.dart 파일 보기

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

+ 0
- 1
packages/zefyr/lib/src/widgets/image.dart 파일 보기

11
 import 'package:meta/meta.dart';
11
 import 'package:meta/meta.dart';
12
 import 'package:notus/notus.dart';
12
 import 'package:notus/notus.dart';
13
 import 'package:zefyr/zefyr.dart';
13
 import 'package:zefyr/zefyr.dart';
14
-import 'package:photo_manager/photo_manager.dart';
15
 
14
 
16
 import 'editable_box.dart';
15
 import 'editable_box.dart';
17
 
16
 

+ 27
- 0
packages/zefyr/lib/src/widgets/link.dart 파일 보기

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 파일 보기

79
       );
79
       );
80
       padding = blockTheme.linePadding;
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
     Widget bullet =
87
     Widget bullet =
84
         SizedBox(width: 24.0, child: Text(bulletText, style: textStyle));
88
         SizedBox(width: 24.0, child: Text(bulletText, style: textStyle));
85
     if (padding != null) {
89
     if (padding != null) {

+ 39
- 0
packages/zefyr/lib/src/widgets/paragraph.dart 파일 보기

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
 /// Represents heading-styled line in [ZefyrEditor].
66
 /// Represents heading-styled line in [ZefyrEditor].
34
 class ZefyrHeading extends StatelessWidget {
67
 class ZefyrHeading extends StatelessWidget {
35
   ZefyrHeading({Key key, @required this.node, this.blockStyle})
68
   ZefyrHeading({Key key, @required this.node, this.blockStyle})
62
       return theme.attributeTheme.heading2;
95
       return theme.attributeTheme.heading2;
63
     } else if (style == NotusAttribute.heading.level3) {
96
     } else if (style == NotusAttribute.heading.level3) {
64
       return theme.attributeTheme.heading3;
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
     throw UnimplementedError('Unsupported heading style $style');
105
     throw UnimplementedError('Unsupported heading style $style');
67
   }
106
   }

+ 12
- 0
packages/zefyr/lib/src/widgets/scope.dart 파일 보기

8
 import 'editor.dart';
8
 import 'editor.dart';
9
 import 'image.dart';
9
 import 'image.dart';
10
 import 'input.dart';
10
 import 'input.dart';
11
+import 'link.dart';
11
 import 'mode.dart';
12
 import 'mode.dart';
12
 import 'render_context.dart';
13
 import 'render_context.dart';
13
 import 'toolbar.dart';
14
 import 'toolbar.dart';
42
     @required FocusNode focusNode,
43
     @required FocusNode focusNode,
43
     @required FocusScopeNode focusScope,
44
     @required FocusScopeNode focusScope,
44
     ZefyrImageDelegate imageDelegate,
45
     ZefyrImageDelegate imageDelegate,
46
+    ZefyrLinkDelegate linkDelegate,
45
   })  : assert(mode != null),
47
   })  : assert(mode != null),
46
         assert(controller != null),
48
         assert(controller != null),
47
         assert(focusNode != null),
49
         assert(focusNode != null),
50
         _mode = mode,
52
         _mode = mode,
51
         _controller = controller,
53
         _controller = controller,
52
         _imageDelegate = imageDelegate,
54
         _imageDelegate = imageDelegate,
55
+        _linkDelegate = linkDelegate,
53
         _focusNode = focusNode,
56
         _focusNode = focusNode,
54
         _focusScope = focusScope,
57
         _focusScope = focusScope,
55
         _cursorTimer = CursorTimer(),
58
         _cursorTimer = CursorTimer(),
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
   ZefyrMode _mode;
145
   ZefyrMode _mode;
134
   ZefyrMode get mode => _mode;
146
   ZefyrMode get mode => _mode;
135
   set mode(ZefyrMode value) {
147
   set mode(ZefyrMode value) {

+ 73
- 4
packages/zefyr/lib/src/widgets/theme.dart 파일 보기

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

+ 5
- 0
packages/zefyr/lib/src/widgets/toolbar.dart 파일 보기

51
 
51
 
52
 final kZefyrToolbarAttributeActions = <ZefyrToolbarAction, NotusAttributeKey>{
52
 final kZefyrToolbarAttributeActions = <ZefyrToolbarAction, NotusAttributeKey>{
53
   ZefyrToolbarAction.bold: NotusAttribute.bold,
53
   ZefyrToolbarAction.bold: NotusAttribute.bold,
54
+  ZefyrToolbarAction.underline: NotusAttribute.underline,
55
+  ZefyrToolbarAction.deleteline: NotusAttribute.deleteline,
54
   ZefyrToolbarAction.italic: NotusAttribute.italic,
56
   ZefyrToolbarAction.italic: NotusAttribute.italic,
55
   ZefyrToolbarAction.link: NotusAttribute.link,
57
   ZefyrToolbarAction.link: NotusAttribute.link,
56
   // ZefyrToolbarAction.text: NotusAttribute.heading,
58
   // ZefyrToolbarAction.text: NotusAttribute.heading,
58
   ZefyrToolbarAction.headingLevel1: NotusAttribute.heading.level1,
60
   ZefyrToolbarAction.headingLevel1: NotusAttribute.heading.level1,
59
   ZefyrToolbarAction.headingLevel2: NotusAttribute.heading.level2,
61
   ZefyrToolbarAction.headingLevel2: NotusAttribute.heading.level2,
60
   ZefyrToolbarAction.headingLevel3: NotusAttribute.heading.level3,
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
   ZefyrToolbarAction.bulletList: NotusAttribute.block.bulletList,
66
   ZefyrToolbarAction.bulletList: NotusAttribute.block.bulletList,
62
   ZefyrToolbarAction.numberList: NotusAttribute.block.numberList,
67
   ZefyrToolbarAction.numberList: NotusAttribute.block.numberList,
63
   ZefyrToolbarAction.code: NotusAttribute.block.code,
68
   ZefyrToolbarAction.code: NotusAttribute.block.code,

+ 3
- 0
packages/zefyr/lib/src/widgets/view.dart 파일 보기

91
         return ZefyrHeading(node: node);
91
         return ZefyrHeading(node: node);
92
       }
92
       }
93
       return ZefyrParagraph(node: node);
93
       return ZefyrParagraph(node: node);
94
+      //  else if (node.style.contains(NotusAttribute.line)) {
95
+      //   return ZefyrLineDecoration(node: node);
96
+      // }
94
     }
97
     }
95
 
98
 
96
     final BlockNode block = node;
99
     final BlockNode block = node;

+ 1
- 0
packages/zefyr/lib/zefyr.dart 파일 보기

18
 export 'src/widgets/field.dart';
18
 export 'src/widgets/field.dart';
19
 export 'src/widgets/horizontal_rule.dart';
19
 export 'src/widgets/horizontal_rule.dart';
20
 export 'src/widgets/image.dart';
20
 export 'src/widgets/image.dart';
21
+export 'src/widgets/link.dart';
21
 export 'src/widgets/list.dart';
22
 export 'src/widgets/list.dart';
22
 export 'src/widgets/mode.dart';
23
 export 'src/widgets/mode.dart';
23
 export 'src/widgets/paragraph.dart';
24
 export 'src/widgets/paragraph.dart';