Skip to content

Commit 9473a73

Browse files
committed
updates to lists, add HR, add README
1 parent a3d2747 commit 9473a73

14 files changed

+396
-91
lines changed

README.md

+158-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,161 @@
1-
# contentful_rich_text
1+
# Contentful Rich Text Renderer for Flutter
22

3-
Rich Text renderer that parses Contentful Rich Text JSON object
4-
and returns a renderable Flutter widget
3+
Rich Text renderer that parses Contentful Ric
54

6-
## Getting Started
5+
## Installation
76

7+
To use this plugin, add `contentful_rich_text` as a dependency in your pubspec.yaml file.
8+
9+
## Usage
10+
11+
```dart
12+
import 'package:contentful_rich_text/contentful_rich_text.dart';
13+
14+
var document = {
15+
nodeType: 'document',
16+
content: [
17+
{
18+
nodeType: 'paragraph',
19+
content: [
20+
{
21+
nodeType: 'text',
22+
value: 'Hello world!',
23+
marks: [],
24+
},
25+
],
26+
},
27+
],
28+
};
29+
30+
class Demo extends StatelessWidget {
31+
32+
@override
33+
Widget build(BuildContext context) {
34+
return ContentfulRichText(document).documentToWidgetTree;
35+
// RichText(text: 'Hello World');
36+
}
37+
}
38+
```
39+
40+
```dart
41+
import 'package:contentful_rich_text/contentful_rich_text.dart';
42+
43+
var document = {
44+
nodeType: 'document',
45+
content: [
46+
{
47+
nodeType: 'paragraph',
48+
content: [
49+
{
50+
nodeType: 'text',
51+
value: 'Hello',
52+
marks: [{ type: 'bold' }],
53+
},
54+
{
55+
nodeType: 'text',
56+
value: ' world!',
57+
marks: [{ type: 'italic' }],
58+
},
59+
],
60+
},
61+
],
62+
};
63+
64+
class Demo extends StatelessWidget {
65+
66+
@override
67+
Widget build(BuildContext context) {
68+
return ContentfulRichText(document).documentToWidgetTree;
69+
// RichText(
70+
// text: '',
71+
// children: <Widgets>[
72+
// TextSpan(text: 'Hello', style: { fontWeight: FontWeight.bold }),
73+
// TextSpan(text: ' world!', style: { fontStyle: FontStyle.italic }),
74+
// ],
75+
// );
76+
}
77+
}
78+
```
79+
80+
You can also pass custom renderers for both marks and nodes as an optional parameter like so:
81+
82+
```javascript
83+
import 'package:contentful_rich_text/contentful_rich_text.dart';
84+
85+
var document = {
86+
nodeType: 'document',
87+
data: {},
88+
content: [
89+
{
90+
nodeType: 'paragraph',
91+
data:{},
92+
content: [
93+
{
94+
nodeType: 'text',
95+
value: 'Hello',
96+
marks: [{ type: 'bold' }],
97+
data: {}
98+
},
99+
{
100+
nodeType: 'text',
101+
value: ' world!',
102+
marks: [{ type: 'italic' }]
103+
data: {}
104+
},
105+
],
106+
},
107+
]
108+
};
109+
110+
var options = {
111+
renderMark: RenderMark defaultMarkRenderers = RenderMark({
112+
[MARKS.BOLD.value]: () => CustomBoldTextStyle, // returns TextStyle
113+
}),
114+
renderNode: RenderNode defaultNodeRenderers = RenderNode({
115+
[BLOCKS.PARAGRAPH.value]: (node, next) => CustomParagraph(next: next(node.content))
116+
})
117+
}
118+
119+
class Demo extends StatelessWidget {
120+
121+
@override
122+
Widget build(BuildContext context) {
123+
return ContentfulRichText(document, options).documentToWidgetTree;
124+
// CustomParagraph(
125+
// ...
126+
// );
127+
}
128+
}
129+
```
130+
131+
The `renderNode` keys should be the `value` of one of the following `BLOCKS` and `INLINES` properties as defined in [`contentful_rich_text/types`](https://github.com/JOOLHealth/contentful_rich_text/tree/master/lib/types):
132+
133+
- `BLOCKS`
134+
- `DOCUMENT`
135+
- `PARAGRAPH`
136+
- `HEADING_1`
137+
- `HEADING_2`
138+
- `HEADING_3`
139+
- `HEADING_4`
140+
- `HEADING_5`
141+
- `HEADING_6`
142+
- `UL_LIST`
143+
- `OL_LIST`
144+
- `LIST_ITEM`
145+
- ~~`QUOTE`~~ // not implemented yet
146+
- `HR`
147+
- ~~`EMBEDDED_ENTRY`~~ // not implemented yet
148+
- ~~`EMBEDDED_ASSET`~~ // not implemented yet
149+
150+
- `INLINES`
151+
- `EMBEDDED_ENTRY` (this is different from the `BLOCKS.EMBEDDED_ENTRY`) // not implemented yet
152+
- `HYPERLINK` // not implemented yet
153+
- ~~`ENTRY_HYPERLINK`~~ // not implemented yet
154+
- ~~`ASSET_HYPERLINK`~~ // not implemented yet
155+
156+
The `renderMark` keys should be the value of one of the following `MARKS` properties as defined in [`contentful_rich_text/types`](https://github.com/JOOLHealth/contentful_rich_text/tree/master/lib/types):
157+
158+
- `BOLD`
159+
- `ITALIC`
160+
- `UNDERLINE`
161+
- ~~`CODE`~~ // not implemented yet

android/local.properties

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sdk.dir=/Users/mikebehnke/Library/Android/sdk
2+
flutter.sdk=/Users/mikebehnke/dev/flutter_sdk/flutter
3+
flutter.versionName=0.0.1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module type="JAVA_MODULE" version="4">
3+
<component name="NewModuleRootManager" inherit-compiler-output="true">
4+
<exclude-output />
5+
<content url="file://$MODULE_DIR$">
6+
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
7+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
8+
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
9+
<excludeFolder url="file://$MODULE_DIR$/.idea" />
10+
<excludeFolder url="file://$MODULE_DIR$/.pub" />
11+
<excludeFolder url="file://$MODULE_DIR$/build" />
12+
</content>
13+
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
14+
<orderEntry type="sourceFolder" forTests="false" />
15+
<orderEntry type="library" name="Dart SDK" level="project" />
16+
<orderEntry type="library" name="Flutter Plugins" level="project" />
17+
</component>
18+
</module>

lib/contentful_rich_text.dart

+57-29
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,64 @@ import 'package:contentful_rich_text/types/inlines.dart';
66
import 'package:contentful_rich_text/types/marks.dart';
77
import 'package:contentful_rich_text/types/types.dart';
88
import 'package:contentful_rich_text/widgets/heading.dart';
9+
import 'package:contentful_rich_text/widgets/hr.dart';
910
import 'package:contentful_rich_text/widgets/list_item.dart';
1011
import 'package:contentful_rich_text/widgets/ordered_list.dart';
12+
import 'package:contentful_rich_text/widgets/paragraph.dart';
1113
import 'package:contentful_rich_text/widgets/unordered_list.dart';
1214
import 'package:flutter/material.dart';
1315
import 'package:html_unescape/html_unescape_small.dart';
1416

1517
/// Contentful Rich Text widget
1618
class ContentfulRichText {
1719
RenderNode defaultNodeRenderers = RenderNode({
18-
BLOCKS.PARAGRAPH.value: (node, next) => RichText(
19-
text: TextSpan(
20-
children: next(node['content']),
21-
),
20+
BLOCKS.PARAGRAPH.value: (node, next) => Paragraph(node, next),
21+
BLOCKS.HEADING_1.value: (node, next) => Heading(
22+
level: BLOCKS.HEADING_1,
23+
text: node['value'],
24+
content: node['content'],
25+
next: next,
26+
),
27+
BLOCKS.HEADING_2.value: (node, next) => Heading(
28+
level: BLOCKS.HEADING_2,
29+
text: node['value'],
30+
content: node['content'],
31+
next: next,
32+
),
33+
BLOCKS.HEADING_3.value: (node, next) => Heading(
34+
level: BLOCKS.HEADING_3,
35+
text: node['value'],
36+
content: node['content'],
37+
next: next,
38+
),
39+
BLOCKS.HEADING_4.value: (node, next) => Heading(
40+
level: BLOCKS.HEADING_4,
41+
text: node['value'],
42+
content: node['content'],
43+
next: next,
44+
),
45+
BLOCKS.HEADING_5.value: (node, next) => Heading(
46+
level: BLOCKS.HEADING_5,
47+
text: node['value'],
48+
content: node['content'],
49+
next: next,
50+
),
51+
BLOCKS.HEADING_6.value: (node, next) => Heading(
52+
level: BLOCKS.HEADING_6,
53+
text: node['value'],
54+
content: node['content'],
55+
next: next,
2256
),
23-
BLOCKS.HEADING_1.value: (node, next) =>
24-
Heading(level: BLOCKS.HEADING_1, text: node['value'], content: node['content']),
25-
BLOCKS.HEADING_2.value: (node, next) =>
26-
Heading(level: BLOCKS.HEADING_2, text: node['value'], content: node['content']),
27-
BLOCKS.HEADING_3.value: (node, next) =>
28-
Heading(level: BLOCKS.HEADING_3, text: node['value'], content: node['content']),
29-
BLOCKS.HEADING_4.value: (node, next) =>
30-
Heading(level: BLOCKS.HEADING_4, text: node['value'], content: node['content']),
31-
BLOCKS.HEADING_5.value: (node, next) =>
32-
Heading(level: BLOCKS.HEADING_5, text: node['value'], content: node['content']),
33-
BLOCKS.HEADING_6.value: (node, next) =>
34-
Heading(level: BLOCKS.HEADING_6, text: node['value'], content: node['content']),
3557
BLOCKS.EMBEDDED_ENTRY.value: (node, next) => Container(), // TODO: implement
36-
BLOCKS.UL_LIST.value: (node, next) => UnorderedList(node['content']),
37-
BLOCKS.OL_LIST.value: (node, next) => OrderedList(node['content']),
58+
BLOCKS.UL_LIST.value: (node, next) => UnorderedList(node['content'], next),
59+
BLOCKS.OL_LIST.value: (node, next) => OrderedList(node['content'], next),
3860
BLOCKS.LIST_ITEM.value: (node, next) => ListItem(
3961
text: node.value,
40-
// TODO: not sure we can use nodeType to determine the type of LIST_ITEM
4162
type: node.nodeType == BLOCKS.OL_LIST.value ? LI_TYPE.ORDERED : LI_TYPE.UNORDERED,
63+
children: node['content'],
4264
),
4365
BLOCKS.QUOTE.value: (node, next) => Container(), // TODO: implement
44-
BLOCKS.HR.value: (node, next) => Container(), // TODO: implement
66+
BLOCKS.HR.value: (node, next) => Hr(),
4567
INLINES.ASSET_HYPERLINK.value: (node, next) => defaultInline(INLINES.ASSET_HYPERLINK, node as Inline),
4668
INLINES.ENTRY_HYPERLINK.value: (node, next) => defaultInline(INLINES.ENTRY_HYPERLINK, node as Inline),
4769
INLINES.EMBEDDED_ENTRY.value: (node, next) => defaultInline(INLINES.EMBEDDED_ENTRY, node as Inline),
@@ -87,15 +109,19 @@ class ContentfulRichText {
87109

88110
Widget nodeListToWidget(List<dynamic> nodes,
89111
{Map<dynamic, Function> renderNode, Map<dynamic, TextStyle> renderMark}) {
90-
print('nodeListToWidget ${nodes.length}');
112+
print('nodeListToWidget ${nodes?.length}');
91113
return Column(
92-
children: List<Widget>.from(
93-
nodes.map<Widget>((dynamic node) => nodeToWidget(
94-
node,
95-
renderNode: renderNode,
96-
renderMark: renderMark,
97-
)),
98-
),
114+
mainAxisAlignment: MainAxisAlignment.start,
115+
crossAxisAlignment: CrossAxisAlignment.start,
116+
children: nodes != null
117+
? List<Widget>.from(
118+
nodes.map<Widget>((dynamic node) => nodeToWidget(
119+
node,
120+
renderNode: renderNode,
121+
renderMark: renderMark,
122+
)),
123+
)
124+
: [],
99125
);
100126
}
101127

@@ -105,9 +131,11 @@ class ContentfulRichText {
105131
Map<dynamic, TextStyle> renderMark,
106132
}) {
107133
print('nodeToWidget entry $node');
134+
print('nodeToWidget nodeType ${node['nodeType']}');
108135
if (Helpers.isText(node)) {
109136
return RichText(text: _processTextNode(node, renderMark));
110-
} else if (Helpers.isParagraph(node)) {
137+
} else if (Helpers.isParagraph(node) || Helpers.isHeader(node)) {
138+
print('isParagraph or Header: ${node['nodeType']}');
111139
return renderNode[node['nodeType']](
112140
node,
113141
(nodes) => List<TextSpan>.from(nodes.map((node) => _processTextNode(node, renderMark))),

lib/types/helpers.dart

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ class Helpers {
2424
return node['nodeType'] == 'paragraph';
2525
}
2626

27+
/// Checks if the node is a Header
28+
static bool isHeader(dynamic node) {
29+
print('isHeader: ${node['nodeType']}');
30+
return node['nodeType'].contains('heading');
31+
}
32+
2733
/// Checks if the node is an instance of Text
2834
static bool isText(dynamic node) {
2935
print('isText: ${node['nodeType']}');

0 commit comments

Comments
 (0)