Skip to content

Commit 53e3bc6

Browse files
committed
Merge branch 'local'
2 parents 7964134 + d1157e0 commit 53e3bc6

21 files changed

+954
-0
lines changed

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
.dart_tool/
3+
4+
.packages
5+
.pub/
6+
7+
build/
8+
ios/.generated/
9+
ios/Flutter/Generated.xcconfig
10+
ios/Runner/GeneratedPluginRegistrant.*

.metadata

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b
8+
channel: stable
9+
10+
project_type: package

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## [0.0.1] - 2/20/2019
2+
3+
* Initial Release
4+
* Very basic support for some of the Rich Text block/inline items
5+
* More to come soon!

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# contentful_rich_text
2+
3+
Rich Text renderer that parses Contentful Rich Text JSON object
4+
and returns a renderable Flutter widget
5+
6+
## Getting Started
7+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.flutter.plugins;
2+
3+
import io.flutter.plugin.common.PluginRegistry;
4+
5+
/**
6+
* Generated file. Do not edit.
7+
*/
8+
public final class GeneratedPluginRegistrant {
9+
public static void registerWith(PluginRegistry registry) {
10+
if (alreadyRegisteredWith(registry)) {
11+
return;
12+
}
13+
}
14+
15+
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
16+
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
17+
if (registry.hasPlugin(key)) {
18+
return true;
19+
}
20+
registry.registrarFor(key);
21+
return false;
22+
}
23+
}

contentful_rich_text.iml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 Packages" level="project" />
16+
<orderEntry type="library" name="Dart SDK" level="project" />
17+
<orderEntry type="library" name="Flutter Plugins" level="project" />
18+
</component>
19+
</module>

lib/contentful_rich_text.dart

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
library contentful_rich_text;
2+
3+
import 'package:contentful_rich_text/types/blocks.dart';
4+
import 'package:contentful_rich_text/types/helpers.dart';
5+
import 'package:contentful_rich_text/types/inlines.dart';
6+
import 'package:contentful_rich_text/types/marks.dart';
7+
import 'package:contentful_rich_text/types/types.dart';
8+
import 'package:contentful_rich_text/widgets/heading.dart';
9+
import 'package:contentful_rich_text/widgets/list_item.dart';
10+
import 'package:contentful_rich_text/widgets/ordered_list.dart';
11+
import 'package:contentful_rich_text/widgets/unordered_list.dart';
12+
import 'package:flutter/material.dart';
13+
import 'package:html_unescape/html_unescape_small.dart';
14+
15+
/// Contentful Rich Text widget
16+
class ContentFulRichText {
17+
RenderNode defaultNodeRenderers = RenderNode({
18+
BLOCKS.PARAGRAPH.value: (node, next) => RichText(
19+
text: TextSpan(text: node.value, children: next(node.content)),
20+
),
21+
BLOCKS.HEADING_1.value: (node, next) => Heading(level: BLOCKS.HEADING_1, text: node.value, content: node.content),
22+
BLOCKS.HEADING_2.value: (node, next) => Heading(level: BLOCKS.HEADING_2, text: node.value, content: node.content),
23+
BLOCKS.HEADING_3.value: (node, next) => Heading(level: BLOCKS.HEADING_3, text: node.value, content: node.content),
24+
BLOCKS.HEADING_4.value: (node, next) => Heading(level: BLOCKS.HEADING_4, text: node.value, content: node.content),
25+
BLOCKS.HEADING_5.value: (node, next) => Heading(level: BLOCKS.HEADING_5, text: node.value, content: node.content),
26+
BLOCKS.HEADING_6.value: (node, next) => Heading(level: BLOCKS.HEADING_6, text: node.value, content: node.content),
27+
BLOCKS.EMBEDDED_ENTRY.value: (node, next) => Container(), // TODO: implement
28+
BLOCKS.UL_LIST.value: (node, next) => UnorderedList(node.content),
29+
BLOCKS.OL_LIST.value: (node, next) => OrderedList(node.content),
30+
BLOCKS.LIST_ITEM.value: (node, next) => ListItem(
31+
text: node.value,
32+
// TODO: not sure we can use nodeType to determine the type of LIST_ITEM
33+
type: node.nodeType == BLOCKS.OL_LIST.value ? LI_TYPE.ORDERED : LI_TYPE.UNORDERED,
34+
),
35+
BLOCKS.QUOTE.value: (node, next) => Container(), // TODO: implement
36+
BLOCKS.HR.value: (node, next) => Container(), // TODO: implement
37+
INLINES.ASSET_HYPERLINK.value: (node, next) => defaultInline(INLINES.ASSET_HYPERLINK, node as Inline),
38+
INLINES.ENTRY_HYPERLINK.value: (node, next) => defaultInline(INLINES.ENTRY_HYPERLINK, node as Inline),
39+
INLINES.EMBEDDED_ENTRY.value: (node, next) => defaultInline(INLINES.EMBEDDED_ENTRY, node as Inline),
40+
INLINES.HYPERLINK.value: (node, next) => Container(), // TODO: implement
41+
});
42+
43+
RenderMark defaultMarkRenderers = RenderMark({
44+
MARKS.BOLD.value: (text) => TextSpan(text: text, style: TextStyle(fontWeight: FontWeight.bold)),
45+
MARKS.ITALIC.value: (text) => TextSpan(text: text, style: TextStyle(fontStyle: FontStyle.italic)),
46+
MARKS.UNDERLINE.value: (text) => TextSpan(text: text, style: TextStyle(decoration: TextDecoration.underline)),
47+
MARKS.CODE.value: (text) => TextSpan(text: text, style: TextStyle(decoration: TextDecoration.underline)),
48+
});
49+
50+
static Widget defaultInline(INLINES type, Inline node) => Container(); // TODO: implement
51+
52+
Document richTextDocument;
53+
Options options;
54+
55+
ContentFulRichText(this.richTextDocument, {this.options});
56+
57+
Widget get documentToWidgetTree {
58+
if (richTextDocument == null || richTextDocument.content == null) {
59+
return Container();
60+
}
61+
62+
Map<dynamic, Function> renderNode = Map.from(defaultNodeRenderers.renderNodes);
63+
renderNode.addAll(options?.renderNode?.renderNodes);
64+
Map<dynamic, Function> renderMark = Map.from(defaultMarkRenderers.renderMarks);
65+
renderMark.addAll(options?.renderMark?.renderMarks);
66+
67+
return nodeListToWidget(
68+
richTextDocument.content,
69+
renderNode: renderNode,
70+
renderMark: renderMark,
71+
);
72+
}
73+
74+
Widget nodeListToWidget(
75+
List<TopLevelBlock> nodes, {
76+
Map<dynamic, Function> renderNode,
77+
Map<dynamic, Function> renderMark,
78+
}) {
79+
return Column(
80+
children: nodes.map<Widget>(
81+
(TopLevelBlock node) => nodeToWidget(node, renderNode: renderNode, renderMark: renderMark),
82+
),
83+
);
84+
}
85+
86+
Widget nodeToWidget(
87+
TopLevelBlock node, {
88+
Map<dynamic, Function> renderNode,
89+
Map<dynamic, Function> renderMark,
90+
}) {
91+
if (Helpers.isText(node)) {
92+
TextNode textNode = node as TextNode;
93+
String nodeValue = HtmlUnescape().convert(textNode.value);
94+
if (textNode.marks.length > 0) {
95+
return RichText(
96+
text: TextSpan(
97+
children: textNode.marks.map<TextSpan>(
98+
(Mark mark) => renderMark[mark.type](textNode.value),
99+
),
100+
),
101+
);
102+
}
103+
return Text(nodeValue);
104+
} else {
105+
Next nextNode = (nodes) => nodeListToWidget(
106+
nodes,
107+
renderNode: renderNode,
108+
renderMark: renderMark,
109+
);
110+
if (node.nodeType == null || renderNode[node.nodeType] == null) {
111+
// TODO: Figure what to return when passed an unrecognized node.
112+
return Container();
113+
}
114+
return renderNode[node.nodeType](node, nextNode);
115+
}
116+
}
117+
}

lib/types/blocks.dart

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/// Map of all Contentful block types.
2+
/// Blocks contain inline or block nodes.
3+
class BLOCKS {
4+
final String _key;
5+
final String _value;
6+
const BLOCKS._internal(this._key, this._value);
7+
8+
@override
9+
String toString() => 'BLOCKS.$_key';
10+
String get key => _key;
11+
String get value => _value;
12+
13+
static const DOCUMENT = const BLOCKS._internal('DOCUMENT', 'document');
14+
static const PARAGRAPH = const BLOCKS._internal('PARAGRAPH', 'paragraph');
15+
static const HEADING_1 = const BLOCKS._internal('HEADING_1', 'heading-1');
16+
static const HEADING_2 = const BLOCKS._internal('HEADING_2', 'heading-2');
17+
static const HEADING_3 = const BLOCKS._internal('HEADING_3', 'heading-3');
18+
static const HEADING_4 = const BLOCKS._internal('HEADING_4', 'heading-4');
19+
static const HEADING_5 = const BLOCKS._internal('HEADING_5', 'heading-5');
20+
static const HEADING_6 = const BLOCKS._internal('HEADING_6', 'heading-6');
21+
static const OL_LIST = const BLOCKS._internal('OL_LIST', 'ordered-list');
22+
static const UL_LIST = const BLOCKS._internal('UL_LIST', 'unordered-list');
23+
static const LIST_ITEM = const BLOCKS._internal('LIST_ITEM', 'list-item');
24+
static const HR = const BLOCKS._internal('HR', 'hr');
25+
static const QUOTE = const BLOCKS._internal('QUOTE', 'blockquote');
26+
static const EMBEDDED_ENTRY = const BLOCKS._internal('EMBEDDED_ENTRY', 'embedded-entry-block');
27+
static const EMBEDDED_ASSET = const BLOCKS._internal('EMBEDDED_ASSET', 'embedded-asset-block');
28+
29+
static List<String> get keys => [
30+
'DOCUMENT',
31+
'PARAGRAPH',
32+
'HEADING_1',
33+
'HEADING_2',
34+
'HEADING_3',
35+
'HEADING_4',
36+
'HEADING_5',
37+
'HEADING_6',
38+
'OL_LIST',
39+
'UL_LIST',
40+
'LIST_ITEM',
41+
'HR',
42+
'QUOTE',
43+
'EMBEDDED_ENTRY',
44+
'EMBEDDED_ASSET',
45+
];
46+
static List<String> get values => [
47+
'document',
48+
'paragraph',
49+
'heading-1',
50+
'heading-2',
51+
'heading-3',
52+
'heading-4',
53+
'heading-5',
54+
'heading-6',
55+
'ordered-list',
56+
'unordered-list',
57+
'list-item',
58+
'hr',
59+
'blockquote',
60+
'embedded-entry-block',
61+
'embedded-asset-block',
62+
];
63+
}

lib/types/helpers.dart

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'package:contentful_rich_text/types/blocks.dart';
2+
import 'package:contentful_rich_text/types/inlines.dart';
3+
import 'package:contentful_rich_text/types/schema_constraints.dart';
4+
import 'package:contentful_rich_text/types/types.dart';
5+
6+
class Helpers {
7+
/// Checks if the node is an instance of Inline
8+
static bool isInline(Node node) {
9+
return INLINES.values.contains(node.nodeType);
10+
}
11+
12+
/// Checks if the node is an instance of Block
13+
static bool isBlock(Node node) {
14+
return BLOCKS.values.contains(node.nodeType);
15+
}
16+
17+
/// Checks if the node is an instance of TopLevelBlocks
18+
static bool isTopLevelBlock(Node node) {
19+
return TopLevelBlockEnum.values.contains(node.nodeType);
20+
}
21+
22+
/// Checks if the node is an instance of Text
23+
static bool isText(Node node) {
24+
return node.nodeType == 'text';
25+
}
26+
}

lib/types/inlines.dart

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// Map of all Contentful inlines
2+
class INLINES {
3+
final String _key;
4+
final String _value;
5+
const INLINES._internal(this._key, this._value);
6+
7+
@override
8+
String toString() => 'INLINES.$_key';
9+
String get key => _key;
10+
String get value => _value;
11+
12+
static const HYPERLINK = const INLINES._internal('HYPERLINK', 'hyperlink');
13+
static const ENTRY_HYPERLINK = const INLINES._internal('ENTRY_HYPERLINK', 'entry-hyperlink');
14+
static const ASSET_HYPERLINK = const INLINES._internal('ASSET_HYPERLINK', 'asset-hyperlink');
15+
static const EMBEDDED_ENTRY = const INLINES._internal('EMBEDDED_ENTRY', 'embedded-entry-inline');
16+
17+
static List<String> get keys => ['HYPERLINK', 'ENTRY_HYPERLINK', 'ASSET_HYPERLINK', 'EMBEDDED_ENTRY'];
18+
static List<String> get values => ['hyperlink', 'entry-hyperlink', 'asset-hyperlink', 'embedded-entry-inline'];
19+
}

lib/types/marks.dart

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// Map of all Contentful marks
2+
class MARKS {
3+
final String _key;
4+
final String _value;
5+
const MARKS._internal(this._key, this._value);
6+
7+
@override
8+
String toString() => 'MARKS.$_key';
9+
String get key => _key;
10+
String get value => _value;
11+
12+
static const BOLD = const MARKS._internal('BOLD', 'bold');
13+
static const ITALIC = const MARKS._internal('ITALIC', 'italic');
14+
static const UNDERLINE = const MARKS._internal('UNDERLINE', 'underline');
15+
static const CODE = const MARKS._internal('CODE', 'code');
16+
17+
static List<String> get keys => ['BOLD', 'ITALIC', 'UNDERLINE', 'CODE'];
18+
static List<String> get values => ['bold', 'italic', 'underline', 'underline'];
19+
}

0 commit comments

Comments
 (0)