diff --git a/frontend/app_flowy/packages/flowy_editor/assets/images/point.svg b/frontend/app_flowy/packages/flowy_editor/assets/images/point.svg
new file mode 100644
index 0000000000..be88518d0d
--- /dev/null
+++ b/frontend/app_flowy/packages/flowy_editor/assets/images/point.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/app_flowy/packages/flowy_editor/assets/images/quote.svg b/frontend/app_flowy/packages/flowy_editor/assets/images/quote.svg
new file mode 100644
index 0000000000..0f3d33f6d3
--- /dev/null
+++ b/frontend/app_flowy/packages/flowy_editor/assets/images/quote.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/frontend/app_flowy/packages/flowy_editor/example/assets/example.json b/frontend/app_flowy/packages/flowy_editor/example/assets/example.json
new file mode 100644
index 0000000000..a552175146
--- /dev/null
+++ b/frontend/app_flowy/packages/flowy_editor/example/assets/example.json
@@ -0,0 +1,207 @@
+{
+ "document": {
+ "type": "editor",
+ "attributes": {},
+ "children": [
+ {
+ "type": "image",
+ "attributes": {
+ "image_src": "https://images.pexels.com/photos/2253275/pexels-photo-2253275.jpeg?cs=srgb&dl=pexels-helena-lopes-2253275.jpg&fm=jpg"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "🌶 Read Me",
+ "attributes": {
+ "heading": "h1"
+ }
+ }
+ ],
+ "attributes": {
+ "heading": "h1"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "👋 Welcome to Appflowy",
+ "attributes": {
+ "heading": "h2"
+ }
+ }
+ ],
+ "attributes": {
+ "heading": "h2"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Here are the basics:",
+ "attributes": {
+ "heading": "h3"
+ }
+ }
+ ],
+ "attributes": {
+ "heading": "h3"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ { "insert": "Click " },
+ { "insert": "anywhere", "attributes": { "underline": true } },
+ { "insert": " and just typing." }
+ ],
+ "attributes": {
+ "list": "todo",
+ "todo": true
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hit"
+ },
+ {
+ "insert": " / ",
+ "attributes": { "highlightColor": "0xFFFFFF00" }
+ },
+ {
+ "insert": "to see all the types of content you can add - entity, headers, videos, sub pages, etc."
+ }
+ ],
+ "attributes": {
+ "list": "todo",
+ "todo": true
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Highlight any text, and use the menu that pops up to "
+ },
+ { "insert": "style", "attributes": { "bold": true } },
+ { "insert": " your ", "attributes": { "italic": true } },
+ { "insert": "writing", "attributes": { "strikethrough": true } },
+ { "insert": "." }
+ ],
+ "attributes": {
+ "list": "todo",
+ "todo": true
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Here are the examples:",
+ "attributes": {
+ "heading": "h3"
+ }
+ }
+ ],
+ "attributes": {
+ "heading": "h3"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "list": "bullet"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "list": "bullet"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "list": "bullet"
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world",
+ "attributes": { "quote": true }
+ }
+ ],
+ "attributes": {
+ "quote": true
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world",
+ "attributes": { "quote": true }
+ }
+ ],
+ "attributes": {
+ "quote": true
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "number": 1
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "number": 2
+ }
+ },
+ {
+ "type": "text",
+ "delta": [
+ {
+ "insert": "Hello world"
+ }
+ ],
+ "attributes": {
+ "number": 3
+ }
+ }
+ ]
+ }
+}
diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart
index b8b836cc4d..6105703fa0 100644
--- a/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart
+++ b/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart
@@ -112,14 +112,14 @@ class _MyHomePageState extends State {
if (page == 0) {
return _buildFlowyEditor();
} else if (page == 1) {
- return _buildTextfield();
+ return _buildTextField();
}
return Container();
}
Widget _buildFlowyEditor() {
return FutureBuilder(
- future: rootBundle.loadString('assets/document.json'),
+ future: rootBundle.loadString('assets/example.json'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
@@ -167,7 +167,7 @@ class _MyHomePageState extends State {
);
}
- Widget _buildTextfield() {
+ Widget _buildTextField() {
return const Center(
child: TextField(),
);
diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart
index aaca3148c2..e33ff83e2f 100644
--- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart
+++ b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart
@@ -83,7 +83,10 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
Widget _build(BuildContext context) {
return Column(
children: [
- Image.network(src),
+ Image.network(
+ src,
+ height: 150.0,
+ ),
if (node.children.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
diff --git a/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml b/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml
index 11df9b36ee..9a80a73a0a 100644
--- a/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml
+++ b/frontend/app_flowy/packages/flowy_editor/example/pubspec.yaml
@@ -64,6 +64,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- document.json
+ - example.json
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
diff --git a/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart b/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart
index 5e3861e6f4..136b5db4bc 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart
@@ -4,24 +4,36 @@ import 'package:flutter_svg/svg.dart';
class FlowySvg extends StatelessWidget {
const FlowySvg({
Key? key,
- required this.name,
- required this.size,
+ this.name,
+ this.size = const Size(20, 20),
this.color,
+ this.number,
}) : super(key: key);
- final String name;
+ final String? name;
final Size size;
final Color? color;
+ final int? number;
@override
Widget build(BuildContext context) {
- return SizedBox.fromSize(
- size: size,
- child: SvgPicture.asset(
- 'assets/images/$name.svg',
- color: color,
- package: 'flowy_editor',
- ),
- );
+ if (name != null) {
+ return SizedBox.fromSize(
+ size: size,
+ child: SvgPicture.asset(
+ 'assets/images/$name.svg',
+ color: color,
+ package: 'flowy_editor',
+ ),
+ );
+ } else if (number != null) {
+ final numberText =
+ '';
+ return SizedBox.fromSize(
+ size: size,
+ child: SvgPicture.string(numberText),
+ );
+ }
+ return Container();
}
}
diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart
index c0266a15bc..d90e739d97 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart
@@ -1,8 +1,19 @@
+import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/document/position.dart';
+import 'package:flowy_editor/document/selection.dart';
+import 'package:flowy_editor/document/text_delta.dart';
+import 'package:flowy_editor/editor_state.dart';
+import 'package:flowy_editor/document/path.dart';
+import 'package:flowy_editor/operation/transaction_builder.dart';
+import 'package:flowy_editor/render/node_widget_builder.dart';
+import 'package:flowy_editor/render/render_plugins.dart';
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
-import 'package:flowy_editor/flowy_editor.dart';
+import 'package:flowy_editor/infra/flowy_svg.dart';
+import 'package:flowy_editor/extensions/object_extensions.dart';
+import 'package:flowy_editor/render/selection/selectable.dart';
+
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
-import 'package:flowy_editor/infra/flowy_svg.dart';
class RichTextNodeWidgetBuilder extends NodeWidgetBuilder {
RichTextNodeWidgetBuilder.create({
@@ -56,8 +67,12 @@ class _FlowyRichTextState extends State with Selectable {
return _buildTodoListRichText(context);
} else if (attributes.list == 'bullet') {
return _buildBulletedListRichText(context);
- } else if (attributes.quotes == true) {
+ } else if (attributes.quote == true) {
return _buildQuotedRichText(context);
+ } else if (attributes.heading != null) {
+ return _buildHeadingRichText(context);
+ } else if (attributes.number != null) {
+ return _buildNumberListRichText(context);
}
return _buildRichText(context);
}
@@ -151,7 +166,11 @@ class _FlowyRichTextState extends State with Selectable {
}
Widget _buildSingleRichText(BuildContext context) {
- return Expanded(child: RichText(key: _textKey, text: _textSpan));
+ return SizedBox(
+ width:
+ MediaQuery.of(context).size.width - 20, // FIXME: use the const value
+ child: RichText(key: _textKey, text: _textSpan),
+ );
}
Widget _buildTodoListRichText(BuildContext context) {
@@ -161,9 +180,8 @@ class _FlowyRichTextState extends State with Selectable {
children: [
GestureDetector(
child: FlowySvg(
- name: name,
key: _decorationKey,
- size: const Size.square(20),
+ name: name,
),
onTap: () => TransactionBuilder(_editorState)
..updateNode(_textNode, {
@@ -178,9 +196,25 @@ class _FlowyRichTextState extends State with Selectable {
Widget _buildBulletedListRichText(BuildContext context) {
return Row(
- crossAxisAlignment: CrossAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
- Icon(key: _decorationKey, Icons.circle),
+ FlowySvg(
+ key: _decorationKey,
+ name: 'point',
+ ),
+ _buildRichText(context),
+ ],
+ );
+ }
+
+ Widget _buildNumberListRichText(BuildContext context) {
+ return Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ FlowySvg(
+ key: _decorationKey,
+ number: _textNode.attributes.number,
+ ),
_buildRichText(context),
],
);
@@ -190,17 +224,32 @@ class _FlowyRichTextState extends State with Selectable {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Icon(key: _decorationKey, Icons.format_quote),
+ FlowySvg(
+ key: _decorationKey,
+ name: 'quote',
+ ),
_buildRichText(context),
],
);
}
+ Widget _buildHeadingRichText(BuildContext context) {
+ // TODO: customize
+ return Column(
+ children: [
+ const Padding(padding: EdgeInsets.only(top: 5)),
+ _buildRichText(context),
+ const Padding(padding: EdgeInsets.only(top: 5)),
+ ],
+ );
+ }
+
Rect frontWidgetRect() {
// FIXME: find a more elegant way to solve this situation.
- if (_textNode.attributes.list != null) {
- final renderBox =
- _decorationKey.currentContext?.findRenderObject() as RenderBox;
+ final renderBox = _decorationKey.currentContext
+ ?.findRenderObject()
+ ?.unwrapOrNull();
+ if (renderBox != null) {
return renderBox.localToGlobal(Offset.zero) & renderBox.size;
}
return Rect.zero;
diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart
index a1fd8b57a1..07d4bf4429 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart
@@ -8,11 +8,13 @@ class StyleKey {
static String underline = 'underline';
static String strikethrough = 'strikethrough';
static String color = 'color';
+ static String highlightColor = 'highlightColor';
static String font = 'font';
static String href = 'href';
static String heading = 'heading';
- static String quotes = 'quotes';
+ static String quote = 'quote';
static String list = 'list';
+ static String number = 'number';
static String todo = 'todo';
static String code = 'code';
}
@@ -45,6 +47,16 @@ extension AttributesExtensions on Attributes {
return null;
}
+ Color? get hightlightColor {
+ if (containsKey(StyleKey.highlightColor) &&
+ this[StyleKey.highlightColor] is String) {
+ return Color(
+ int.parse(this[StyleKey.highlightColor]),
+ );
+ }
+ return null;
+ }
+
String? get font {
// TODO: unspport now.
return null;
@@ -64,9 +76,9 @@ extension AttributesExtensions on Attributes {
return null;
}
- bool get quotes {
- if (containsKey(StyleKey.quotes) && this[StyleKey.quotes] == true) {
- return this[StyleKey.quotes];
+ bool get quote {
+ if (containsKey(StyleKey.quote) && this[StyleKey.quote] == true) {
+ return this[StyleKey.quote];
}
return false;
}
@@ -78,6 +90,13 @@ extension AttributesExtensions on Attributes {
return null;
}
+ int? get number {
+ if (containsKey(StyleKey.number) && this[StyleKey.number] is int) {
+ return this[StyleKey.number];
+ }
+ return null;
+ }
+
bool get todo {
if (containsKey(StyleKey.todo) && this[StyleKey.todo] is bool) {
return this[StyleKey.todo];
@@ -102,7 +121,7 @@ extension AttributesExtensions on Attributes {
///
/// Supported global rendering types:
/// heading: h1, h2, h3, h4, h5, h6,
-/// block quotes,
+/// block quote,
/// list: ordered list, bulleted list,
/// code block
///
@@ -124,6 +143,7 @@ class RichTextStyle {
fontStyle: fontStyle,
fontSize: fontSize,
color: textColor,
+ backgroundColor: backgroundColor,
decoration: textDecoration,
),
recognizer: recognizer,
@@ -131,8 +151,14 @@ class RichTextStyle {
}
// bold
- FontWeight get fontWeight =>
- attributes.bold ? FontWeight.bold : FontWeight.normal;
+ FontWeight get fontWeight {
+ if (attributes.bold) {
+ return FontWeight.bold;
+ } else if (attributes.heading != null) {
+ return FontWeight.bold;
+ }
+ return FontWeight.normal;
+ }
// underline or strikethrough
TextDecoration get textDecoration {
@@ -152,19 +178,25 @@ class RichTextStyle {
Color get textColor {
if (attributes.href != null) {
return Colors.lightBlue;
+ } else if (attributes.quote) {
+ return Colors.grey;
}
return attributes.color ?? Colors.black;
}
+ Color get backgroundColor {
+ return attributes.hightlightColor ?? Colors.transparent;
+ }
+
// font size
double get fontSize {
final heading = attributes.heading;
if (heading != null) {
final headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- final fontSizes = [30.0, 28.0, 26.0, 24.0, 22.0, 20.0];
+ final fontSizes = [30.0, 25.0, 20.0, 20.0, 20.0, 20.0];
return fontSizes[headings.indexOf(heading)];
} else {
- return 18.0;
+ return 16.0;
}
}