feat: rename delta and document (#153)
* chore: rename delta * fix: rename attributes to data * chore: rename document to page * fix: flutter analyze
This commit is contained in:
parent
6b5d48cfcb
commit
c573b9a80f
26 changed files with 243 additions and 247 deletions
|
|
@ -169,7 +169,7 @@ We'll use `network_image` in this case. And we add `network_image_src` to the `a
|
||||||
```JSON
|
```JSON
|
||||||
{
|
{
|
||||||
"type": "network_image",
|
"type": "network_image",
|
||||||
"attributes": {
|
"data": {
|
||||||
"network_image_src": "https://docs.flutter.dev/assets/images/dash/dash-fainting.gif"
|
"network_image_src": "https://docs.flutter.dev/assets/images/dash/dash-fainting.gif"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"document": {
|
"document": {
|
||||||
"type": "document",
|
"type": "page",
|
||||||
"children": [{
|
"children": [{
|
||||||
"type": "heading",
|
"type": "heading",
|
||||||
"attributes": {
|
"data": {
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "👋 "
|
"insert": "👋 "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "Welcome to",
|
"insert": "Welcome to",
|
||||||
"attributes": {
|
"data": {
|
||||||
"bold": true
|
"bold": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "AppFlowy Editor",
|
"insert": "AppFlowy Editor",
|
||||||
"attributes": {
|
"data": {
|
||||||
"href": "appflowy.io",
|
"href": "appflowy.io",
|
||||||
"italic": true,
|
"italic": true,
|
||||||
"bold": true
|
"bold": true
|
||||||
|
|
@ -30,13 +30,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": []
|
"delta": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "AppFlowy Editor is a"
|
"insert": "AppFlowy Editor is a"
|
||||||
},
|
},
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "highly customizable",
|
"insert": "highly customizable",
|
||||||
"attributes": {
|
"data": {
|
||||||
"bold": true
|
"bold": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "rich-text editor",
|
"insert": "rich-text editor",
|
||||||
"attributes": {
|
"data": {
|
||||||
"italic": true
|
"italic": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "Flutter",
|
"insert": "Flutter",
|
||||||
"attributes": {
|
"data": {
|
||||||
"underline": true
|
"underline": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "todo_list",
|
"type": "todo_list",
|
||||||
"attributes": {
|
"data": {
|
||||||
"checked": true,
|
"checked": true,
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "Customizable"
|
"insert": "Customizable"
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "todo_list",
|
"type": "todo_list",
|
||||||
"attributes": {
|
"data": {
|
||||||
"checked": true,
|
"checked": true,
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "Test-covered"
|
"insert": "Test-covered"
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "todo_list",
|
"type": "todo_list",
|
||||||
"attributes": {
|
"data": {
|
||||||
"checked": false,
|
"checked": false,
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "more to come!"
|
"insert": "more to come!"
|
||||||
|
|
@ -99,13 +99,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": []
|
"delta": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "quote",
|
"type": "quote",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "Here is an example you can give a try"
|
"insert": "Here is an example you can give a try"
|
||||||
}]
|
}]
|
||||||
|
|
@ -113,19 +113,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": []
|
"delta": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "You can also use "
|
"insert": "You can also use "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert": "AppFlowy Editor",
|
"insert": "AppFlowy Editor",
|
||||||
"attributes": {
|
"data": {
|
||||||
"italic": true,
|
"italic": true,
|
||||||
"bold": true,
|
"bold": true,
|
||||||
"textColor": "0xffD70040",
|
"textColor": "0xffD70040",
|
||||||
|
|
@ -140,13 +140,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": []
|
"delta": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "bulleted_list",
|
"type": "bulleted_list",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "Use / to insert blocks"
|
"insert": "Use / to insert blocks"
|
||||||
}]
|
}]
|
||||||
|
|
@ -155,7 +155,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "bulleted_list",
|
"type": "bulleted_list",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "Select text to trigger to the toolbar to format your notes."
|
"insert": "Select text to trigger to the toolbar to format your notes."
|
||||||
}]
|
}]
|
||||||
|
|
@ -164,13 +164,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": []
|
"delta": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "paragraph",
|
"type": "paragraph",
|
||||||
"attributes": {
|
"data": {
|
||||||
"delta": [{
|
"delta": [{
|
||||||
"insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
"insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||||
}]
|
}]
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
);
|
);
|
||||||
name = "Thin Binary";
|
name = "Thin Binary";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export 'src/service/shortcut_event/shortcut_event.dart';
|
||||||
export 'src/service/shortcut_event/shortcut_event_handler.dart';
|
export 'src/service/shortcut_event/shortcut_event_handler.dart';
|
||||||
export 'src/render/rich_text/default_selectable.dart';
|
export 'src/render/rich_text/default_selectable.dart';
|
||||||
export 'src/render/rich_text/flowy_rich_text.dart';
|
export 'src/render/rich_text/flowy_rich_text.dart';
|
||||||
|
export 'src/render/rich_text/flowy_rich_text_keys.dart';
|
||||||
export 'src/render/selection_menu/selection_menu_widget.dart';
|
export 'src/render/selection_menu/selection_menu_widget.dart';
|
||||||
export 'src/render/selection_menu/selection_menu_item_widget.dart';
|
export 'src/render/selection_menu/selection_menu_item_widget.dart';
|
||||||
export 'src/l10n/l10n.dart';
|
export 'src/l10n/l10n.dart';
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class Document {
|
||||||
|
|
||||||
factory Document.blank({bool withInitialText = false}) {
|
factory Document.blank({bool withInitialText = false}) {
|
||||||
final root = Node(
|
final root = Node(
|
||||||
type: 'document',
|
type: 'page',
|
||||||
children: withInitialText ? [paragraphNode()] : [],
|
children: withInitialText ? [paragraphNode()] : [],
|
||||||
);
|
);
|
||||||
return Document(
|
return Document(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import 'package:nanoid/nanoid.dart';
|
||||||
///
|
///
|
||||||
/// It contains three parts:
|
/// It contains three parts:
|
||||||
/// - [type]: The type of the node to determine which block component to render it.
|
/// - [type]: The type of the node to determine which block component to render it.
|
||||||
/// - [attributes]: The attributes of the node to determine how to render it.
|
/// - [data]: The data of the node to determine how to render it.
|
||||||
/// - [children]: The children of the node.
|
/// - [children]: The children of the node.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|
@ -41,7 +41,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
||||||
factory Node.fromJson(Map<String, Object> json) {
|
factory Node.fromJson(Map<String, Object> json) {
|
||||||
final node = Node(
|
final node = Node(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
attributes: Attributes.from(json['attributes'] as Map? ?? {}),
|
attributes: Attributes.from(json['data'] as Map? ?? {}),
|
||||||
children: (json['children'] as List? ?? [])
|
children: (json['children'] as List? ?? [])
|
||||||
.map((e) => Map<String, Object>.from(e))
|
.map((e) => Map<String, Object>.from(e))
|
||||||
.map((e) => Node.fromJson(e)),
|
.map((e) => Node.fromJson(e)),
|
||||||
|
|
@ -195,7 +195,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
||||||
.toList(growable: false);
|
.toList(growable: false);
|
||||||
}
|
}
|
||||||
if (attributes.isNotEmpty) {
|
if (attributes.isNotEmpty) {
|
||||||
map['attributes'] = attributes;
|
map['data'] = attributes;
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// entry
|
// entry
|
||||||
export 'entry/document_component.dart';
|
export 'entry/page_block_component.dart';
|
||||||
|
|
||||||
// ime
|
// ime
|
||||||
export 'service/ime/delta_input_service.dart';
|
export 'service/ime/delta_input_service.dart';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PageBlockKeys {
|
||||||
|
const PageBlockKeys._();
|
||||||
|
|
||||||
|
static const String type = 'page';
|
||||||
|
}
|
||||||
|
|
||||||
|
Node pageNode({
|
||||||
|
required Iterable<Node> children,
|
||||||
|
Attributes attributes = const {},
|
||||||
|
}) {
|
||||||
|
return Node(
|
||||||
|
type: PageBlockKeys.type,
|
||||||
|
children: children,
|
||||||
|
attributes: attributes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
|
@override
|
||||||
|
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||||
|
return PageBlockComponent(
|
||||||
|
key: blockComponentContext.node.key,
|
||||||
|
node: blockComponentContext.node,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageBlockComponent extends BlockComponentStatelessWidget {
|
||||||
|
const PageBlockComponent({
|
||||||
|
super.key,
|
||||||
|
required super.node,
|
||||||
|
super.showActions,
|
||||||
|
super.actionBuilder,
|
||||||
|
super.configuration = const BlockComponentConfiguration(),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final editorState = Provider.of<EditorState>(context, listen: false);
|
||||||
|
final children = editorState.renderer.buildList(context, node.children);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,13 +49,17 @@ void showColorMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
void _formatHighlightColor(EditorState editorState, String color) {
|
void _formatHighlightColor(EditorState editorState, String color) {
|
||||||
final selection = editorState.selection!;
|
editorState.formatDelta(
|
||||||
editorState.formatDelta(selection, {'highlightColor': color});
|
editorState.selection,
|
||||||
|
{FlowyRichTextKeys.highlightColor: color},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _formatFontColor(EditorState editorState, String color) {
|
void _formatFontColor(EditorState editorState, String color) {
|
||||||
final selection = editorState.selection!;
|
editorState.formatDelta(
|
||||||
editorState.formatDelta(selection, {'textColor': color});
|
editorState.selection,
|
||||||
|
{FlowyRichTextKeys.textColor: color},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ColorOption> _generateTextColorOptions(EditorState editorState) {
|
List<ColorOption> _generateTextColorOptions(EditorState editorState) {
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@ final highlightColorItem = ToolbarItem(
|
||||||
final nodes = editorState.getNodesInSelection(selection);
|
final nodes = editorState.getNodesInSelection(selection);
|
||||||
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
||||||
return delta.everyAttributes(
|
return delta.everyAttributes(
|
||||||
(attributes) {
|
(attributes) => attributes[FlowyRichTextKeys.highlightColor] != null,
|
||||||
highlightColorHex = attributes['highlightColor'];
|
|
||||||
return (highlightColorHex != null);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return IconItemWidget(
|
return IconItemWidget(
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ final textColorItem = ToolbarItem(
|
||||||
final nodes = editorState.getNodesInSelection(selection);
|
final nodes = editorState.getNodesInSelection(selection);
|
||||||
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
||||||
return delta.everyAttributes(
|
return delta.everyAttributes(
|
||||||
(attributes) {
|
(attributes) => attributes[FlowyRichTextKeys.textColor] != null,
|
||||||
textColorHex = attributes['textColor'];
|
|
||||||
return (textColorHex != null);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return IconItemWidget(
|
return IconItemWidget(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
extension NodeAttributesExtensions on Attributes {
|
extension NodeAttributesExtensions on Attributes {
|
||||||
String? get heading {
|
String? get heading {
|
||||||
|
|
@ -24,14 +23,6 @@ extension NodeAttributesExtensions on Attributes {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get code {
|
|
||||||
if (containsKey(BuiltInAttributeKey.code) &&
|
|
||||||
this[BuiltInAttributeKey.code] is bool) {
|
|
||||||
return this[BuiltInAttributeKey.code];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get check {
|
bool get check {
|
||||||
if (containsKey(BuiltInAttributeKey.checkbox) &&
|
if (containsKey(BuiltInAttributeKey.checkbox) &&
|
||||||
this[BuiltInAttributeKey.checkbox] is bool) {
|
this[BuiltInAttributeKey.checkbox] is bool) {
|
||||||
|
|
@ -40,51 +31,3 @@ extension NodeAttributesExtensions on Attributes {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DeltaAttributesExtensions on Attributes {
|
|
||||||
bool get bold {
|
|
||||||
return (containsKey(BuiltInAttributeKey.bold) &&
|
|
||||||
this[BuiltInAttributeKey.bold] == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get italic {
|
|
||||||
return (containsKey(BuiltInAttributeKey.italic) &&
|
|
||||||
this[BuiltInAttributeKey.italic] == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get underline {
|
|
||||||
return (containsKey(BuiltInAttributeKey.underline) &&
|
|
||||||
this[BuiltInAttributeKey.underline] == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get strikethrough {
|
|
||||||
return (containsKey(BuiltInAttributeKey.strikethrough) &&
|
|
||||||
this[BuiltInAttributeKey.strikethrough] == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const whiteInt = 0XFFFFFFFF;
|
|
||||||
|
|
||||||
Color? get color {
|
|
||||||
final textColor = this[BuiltInAttributeKey.textColor] as String?;
|
|
||||||
if (textColor != null) {
|
|
||||||
return textColor.toColor();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color? get backgroundColor {
|
|
||||||
final highlightColor = this[BuiltInAttributeKey.highlightColor] as String?;
|
|
||||||
if (highlightColor != null) {
|
|
||||||
return highlightColor.toColor();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? get href {
|
|
||||||
if (containsKey(BuiltInAttributeKey.href) &&
|
|
||||||
this[BuiltInAttributeKey.href] is String) {
|
|
||||||
return this[BuiltInAttributeKey.href];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class HTMLToNodesConverter {
|
||||||
final childNodes = _document.body?.nodes.toList() ?? <html.Node>[];
|
final childNodes = _document.body?.nodes.toList() ?? <html.Node>[];
|
||||||
return Document.fromJson({
|
return Document.fromJson({
|
||||||
'document': {
|
'document': {
|
||||||
'type': 'document',
|
'type': 'page',
|
||||||
'children': _handleContainer(childNodes).map((e) => e.toJson()).toList()
|
'children': _handleContainer(childNodes).map((e) => e.toJson()).toList()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -78,19 +78,19 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
Attributes? attributes;
|
Attributes? attributes;
|
||||||
switch (localName) {
|
switch (localName) {
|
||||||
case HTMLTags.bold || HTMLTags.strong:
|
case HTMLTags.bold || HTMLTags.strong:
|
||||||
attributes = {'bold': true};
|
attributes = {FlowyRichTextKeys.bold: true};
|
||||||
break;
|
break;
|
||||||
case HTMLTags.italic || HTMLTags.em:
|
case HTMLTags.italic || HTMLTags.em:
|
||||||
attributes = {'italic': true};
|
attributes = {FlowyRichTextKeys.italic: true};
|
||||||
break;
|
break;
|
||||||
case HTMLTags.underline:
|
case HTMLTags.underline:
|
||||||
attributes = {'underline': true};
|
attributes = {FlowyRichTextKeys.underline: true};
|
||||||
break;
|
break;
|
||||||
case HTMLTags.del:
|
case HTMLTags.del:
|
||||||
attributes = {'strikethrough': true};
|
attributes = {FlowyRichTextKeys.strikethrough: true};
|
||||||
break;
|
break;
|
||||||
case HTMLTags.code:
|
case HTMLTags.code:
|
||||||
attributes = {'code': true};
|
attributes = {FlowyRichTextKeys.code: true};
|
||||||
case HTMLTags.span:
|
case HTMLTags.span:
|
||||||
attributes = _getDeltaAttributesFromHTMLAttributes(
|
attributes = _getDeltaAttributesFromHTMLAttributes(
|
||||||
element.attributes,
|
element.attributes,
|
||||||
|
|
@ -100,7 +100,7 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
final href = element.attributes['href'];
|
final href = element.attributes['href'];
|
||||||
if (href != null) {
|
if (href != null) {
|
||||||
attributes = {
|
attributes = {
|
||||||
'href': href,
|
FlowyRichTextKeys.href: href,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -180,11 +180,11 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
final fontWeight = css['font-weight'];
|
final fontWeight = css['font-weight'];
|
||||||
if (fontWeight != null) {
|
if (fontWeight != null) {
|
||||||
if (fontWeight == 'bold') {
|
if (fontWeight == 'bold') {
|
||||||
attributes['bold'] = true;
|
attributes[FlowyRichTextKeys.bold] = true;
|
||||||
} else {
|
} else {
|
||||||
final weight = int.tryParse(fontWeight);
|
final weight = int.tryParse(fontWeight);
|
||||||
if (weight != null && weight >= 500) {
|
if (weight != null && weight >= 500) {
|
||||||
attributes['bold'] = true;
|
attributes[FlowyRichTextKeys.bold] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,10 +196,10 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
for (final decoration in decorations) {
|
for (final decoration in decorations) {
|
||||||
switch (decoration) {
|
switch (decoration) {
|
||||||
case 'underline':
|
case 'underline':
|
||||||
attributes['underline'] = true;
|
attributes[FlowyRichTextKeys.underline] = true;
|
||||||
break;
|
break;
|
||||||
case 'line-through':
|
case 'line-through':
|
||||||
attributes['strike'] = true;
|
attributes[FlowyRichTextKeys.strikethrough] = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -212,14 +212,14 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
if (backgroundColor != null) {
|
if (backgroundColor != null) {
|
||||||
final highlightColor = backgroundColor.tryToColor()?.toHex();
|
final highlightColor = backgroundColor.tryToColor()?.toHex();
|
||||||
if (highlightColor != null) {
|
if (highlightColor != null) {
|
||||||
attributes['highlightColor'] = highlightColor;
|
attributes[FlowyRichTextKeys.highlightColor] = highlightColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// italic
|
// italic
|
||||||
final fontStyle = css['font-style'];
|
final fontStyle = css['font-style'];
|
||||||
if (fontStyle == 'italic') {
|
if (fontStyle == 'italic') {
|
||||||
attributes['italic'] = true;
|
attributes[FlowyRichTextKeys.italic] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes.isEmpty ? null : attributes;
|
return attributes.isEmpty ? null : attributes;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/core/document/attributes.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/document/node.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/document/text_delta.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/location/position.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/location/selection.dart';
|
||||||
|
import 'package:appflowy_editor/src/editor/toolbar/toolbar.dart';
|
||||||
|
import 'package:appflowy_editor/src/editor_state.dart';
|
||||||
|
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text_keys.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||||
|
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||||
|
import 'package:appflowy_editor/src/editor/util/color_util.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/document/path.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
|
|
||||||
const _kRichTextDebugMode = false;
|
const _kRichTextDebugMode = false;
|
||||||
|
|
||||||
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
||||||
|
|
@ -376,3 +387,35 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
||||||
return textSelection;
|
return textSelection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension FlowyRichTextAttributes on Attributes {
|
||||||
|
bool get bold => this[FlowyRichTextKeys.bold] == true;
|
||||||
|
|
||||||
|
bool get italic => this[FlowyRichTextKeys.italic] == true;
|
||||||
|
|
||||||
|
bool get underline => this[FlowyRichTextKeys.underline] == true;
|
||||||
|
|
||||||
|
bool get code => this[FlowyRichTextKeys.code] == true;
|
||||||
|
|
||||||
|
bool get strikethrough {
|
||||||
|
return (containsKey(FlowyRichTextKeys.strikethrough) &&
|
||||||
|
this[FlowyRichTextKeys.strikethrough] == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? get color {
|
||||||
|
final textColor = this[FlowyRichTextKeys.textColor] as String?;
|
||||||
|
return textColor?.toColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? get backgroundColor {
|
||||||
|
final highlightColor = this[FlowyRichTextKeys.highlightColor] as String?;
|
||||||
|
return highlightColor?.toColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get href {
|
||||||
|
if (this[FlowyRichTextKeys.href] is String) {
|
||||||
|
return this[FlowyRichTextKeys.href];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
10
lib/src/render/rich_text/flowy_rich_text_keys.dart
Normal file
10
lib/src/render/rich_text/flowy_rich_text_keys.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
class FlowyRichTextKeys {
|
||||||
|
static String bold = 'bold';
|
||||||
|
static String italic = 'italic';
|
||||||
|
static String underline = 'underline';
|
||||||
|
static String strikethrough = 'strikethrough';
|
||||||
|
static String textColor = 'font_color';
|
||||||
|
static String highlightColor = 'bg_color';
|
||||||
|
static String code = 'code';
|
||||||
|
static String href = 'href';
|
||||||
|
}
|
||||||
|
|
@ -227,7 +227,7 @@ ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
|
||||||
textNode.path,
|
textNode.path,
|
||||||
Node.fromJson({
|
Node.fromJson({
|
||||||
'type': 'image',
|
'type': 'image',
|
||||||
'attributes': {
|
'data': {
|
||||||
'image_src': imgUrl,
|
'image_src': imgUrl,
|
||||||
'align': 'center',
|
'align': 'center',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,37 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
const standardBlockComponentConfiguration = BlockComponentConfiguration();
|
const standardBlockComponentConfiguration = BlockComponentConfiguration();
|
||||||
|
|
||||||
final Map<String, BlockComponentBuilder> standardBlockComponentBuilderMap = {
|
final Map<String, BlockComponentBuilder> standardBlockComponentBuilderMap = {
|
||||||
'document': DocumentComponentBuilder(),
|
PageBlockKeys.type: PageBlockComponentBuilder(),
|
||||||
'paragraph': TextBlockComponentBuilder(
|
ParagraphBlockKeys.type: TextBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration,
|
configuration: standardBlockComponentConfiguration,
|
||||||
),
|
),
|
||||||
'todo_list': TodoListBlockComponentBuilder(
|
TodoListBlockKeys.type: TodoListBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration.copyWith(
|
configuration: standardBlockComponentConfiguration.copyWith(
|
||||||
placeholderText: (_) => 'To-do',
|
placeholderText: (_) => 'To-do',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'bulleted_list': BulletedListBlockComponentBuilder(
|
BulletedListBlockKeys.type: BulletedListBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration.copyWith(
|
configuration: standardBlockComponentConfiguration.copyWith(
|
||||||
placeholderText: (_) => 'List',
|
placeholderText: (_) => 'List',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'numbered_list': NumberedListBlockComponentBuilder(
|
NumberedListBlockKeys.type: NumberedListBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration.copyWith(
|
configuration: standardBlockComponentConfiguration.copyWith(
|
||||||
placeholderText: (_) => 'List',
|
placeholderText: (_) => 'List',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'quote': QuoteBlockComponentBuilder(
|
QuoteBlockKeys.type: QuoteBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration.copyWith(
|
configuration: standardBlockComponentConfiguration.copyWith(
|
||||||
placeholderText: (_) => 'Quote',
|
placeholderText: (_) => 'Quote',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'heading': HeadingBlockComponentBuilder(
|
HeadingBlockKeys.type: HeadingBlockComponentBuilder(
|
||||||
configuration: standardBlockComponentConfiguration.copyWith(
|
configuration: standardBlockComponentConfiguration.copyWith(
|
||||||
placeholderText: (node) =>
|
placeholderText: (node) =>
|
||||||
'Heading ${node.attributes[HeadingBlockKeys.level]}',
|
'Heading ${node.attributes[HeadingBlockKeys.level]}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'image': ImageBlockComponentBuilder(),
|
ImageBlockKeys.type: ImageBlockComponentBuilder(),
|
||||||
};
|
};
|
||||||
|
|
||||||
final List<CharacterShortcutEvent> standardCharacterShortcutEvents = [
|
final List<CharacterShortcutEvent> standardCharacterShortcutEvents = [
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ void main() async {
|
||||||
'children': [
|
'children': [
|
||||||
{'type': 'text'}
|
{'type': 'text'}
|
||||||
],
|
],
|
||||||
'attributes': {'a': 'a'}
|
'data': {'a': 'a'}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
final document = Document.fromJson(json);
|
final document = Document.fromJson(json);
|
||||||
|
|
@ -78,11 +78,11 @@ void main() async {
|
||||||
true,
|
true,
|
||||||
Document.fromJson({
|
Document.fromJson({
|
||||||
'document': {
|
'document': {
|
||||||
'type': 'document',
|
'type': 'page',
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'paragraph',
|
'type': 'paragraph',
|
||||||
'attributes': {
|
'data': {
|
||||||
'delta': [],
|
'delta': [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ void main() async {
|
||||||
true,
|
true,
|
||||||
Document.fromJson({
|
Document.fromJson({
|
||||||
'document': {
|
'document': {
|
||||||
'type': 'document',
|
'type': 'page',
|
||||||
'children': [],
|
'children': [],
|
||||||
}
|
}
|
||||||
}).isEmpty,
|
}).isEmpty,
|
||||||
|
|
@ -105,11 +105,11 @@ void main() async {
|
||||||
true,
|
true,
|
||||||
Document.fromJson({
|
Document.fromJson({
|
||||||
'document': {
|
'document': {
|
||||||
'type': 'document',
|
'type': 'page',
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'paragraph',
|
'type': 'paragraph',
|
||||||
'attributes': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': ''}
|
{'insert': ''}
|
||||||
],
|
],
|
||||||
|
|
@ -124,11 +124,11 @@ void main() async {
|
||||||
false,
|
false,
|
||||||
Document.fromJson({
|
Document.fromJson({
|
||||||
'document': {
|
'document': {
|
||||||
'type': 'document',
|
'type': 'page',
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'paragraph',
|
'type': 'paragraph',
|
||||||
'attributes': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': 'Welcome to AppFlowy!'}
|
{'insert': 'Welcome to AppFlowy!'}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ void main() async {
|
||||||
);
|
);
|
||||||
expect(node.toJson(), {
|
expect(node.toJson(), {
|
||||||
'type': 'example',
|
'type': 'example',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -30,13 +30,13 @@ void main() async {
|
||||||
);
|
);
|
||||||
expect(nodeWithChildren.toJson(), {
|
expect(nodeWithChildren.toJson(), {
|
||||||
'type': 'example',
|
'type': 'example',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'example',
|
'type': 'example',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -57,7 +57,7 @@ void main() async {
|
||||||
);
|
);
|
||||||
expect(textNode.toJson(), {
|
expect(textNode.toJson(), {
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
'delta': [
|
'delta': [
|
||||||
|
|
@ -78,7 +78,7 @@ void main() async {
|
||||||
);
|
);
|
||||||
expect(textNodeWithChildren.toJson(), {
|
expect(textNodeWithChildren.toJson(), {
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
'delta': [
|
'delta': [
|
||||||
|
|
@ -87,7 +87,7 @@ void main() async {
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
'delta': [
|
'delta': [
|
||||||
|
|
@ -185,7 +185,7 @@ void main() async {
|
||||||
'children': [
|
'children': [
|
||||||
{
|
{
|
||||||
'type': 'example',
|
'type': 'example',
|
||||||
'attributes': {
|
'data': {
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue