Compare commits

..

4 Commits

Author SHA1 Message Date
Elias Nahum
d451cf6bde Use commonmark & patch it instead of a fork (#5498) 2021-07-20 12:25:15 -04:00
Elias Nahum
38fa757942 Bump Version to 1.45.1 and Build to 364 (#5558)
* Bump app build number to  364

* Bump app version number to  1.45.1
2021-07-20 12:06:45 -04:00
Mattermost Build
ad249e9fe4 Fix crash on markdown emoji with hardbreak parser (#5547) (#5549)
(cherry picked from commit 90cce88358)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-20 12:00:11 -04:00
Mattermost Build
44708f5bba Properly group Android push notifications by channel (#5548) (#5557)
(cherry picked from commit ba78bc9d00)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-20 11:47:15 -04:00
16 changed files with 8171 additions and 1371 deletions

View File

@@ -132,8 +132,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 363
versionName "1.45.0"
versionCode 364
versionName "1.45.1"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,5 +1,6 @@
package com.mattermost.rnbeta;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.SharedPreferences;
@@ -66,11 +67,9 @@ public class CustomPushNotification extends PushNotification {
notificationsInChannel.remove(channelId);
saveNotificationsMap(context, notificationsInChannel);
if (context != null) {
for (final Integer notificationId : notifications) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(notificationId);
}
for (final Integer notificationId : notifications) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(notificationId);
}
}
}
@@ -120,23 +119,32 @@ public class CustomPushNotification extends PushNotification {
switch (type) {
case PUSH_TYPE_MESSAGE:
case PUSH_TYPE_SESSION:
boolean createSummary = type.equals(PUSH_TYPE_MESSAGE);
if (!mAppLifecycleFacade.isAppVisible()) {
if (type.equals(PUSH_TYPE_MESSAGE)) {
if (channelId != null) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
synchronized (notificationsInChannel) {
List<Integer> list = notificationsInChannel.get(channelId);
if (list == null) {
list = Collections.synchronizedList(new ArrayList(0));
}
list.add(0, notificationId);
notificationsInChannel.put(channelId, list);
saveNotificationsMap(mContext, notificationsInChannel);
List<Integer> list = notificationsInChannel.get(channelId);
if (list == null) {
list = Collections.synchronizedList(new ArrayList(0));
}
list.add(0, notificationId);
if (list.size() > 1) {
createSummary = false;
}
if (createSummary) {
// Add the summary notification id as well
list.add(0, notificationId + 1);
}
notificationsInChannel.put(channelId, list);
saveNotificationsMap(mContext, notificationsInChannel);
}
}
super.postNotification(notificationId);
buildNotification(notificationId, createSummary);
} else {
notifyReceivedToJS();
}
@@ -173,10 +181,25 @@ public class CustomPushNotification extends PushNotification {
}
}
private void buildNotification(Integer notificationId, boolean createSummary) {
final PendingIntent pendingIntent = super.getCTAPendingIntent();
final Notification notification = buildNotification(pendingIntent);
if (createSummary) {
final Notification summary = getNotificationSummaryBuilder(pendingIntent).build();
super.postNotification(summary, notificationId + 1);
}
super.postNotification(notification, notificationId);
}
@Override
protected NotificationCompat.Builder getNotificationBuilder(PendingIntent intent) {
Bundle bundle = mNotificationProps.asBundle();
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle);
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, false);
}
protected NotificationCompat.Builder getNotificationSummaryBuilder(PendingIntent intent) {
Bundle bundle = mNotificationProps.asBundle();
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
}
private void notificationReceiptDelivery(String ackId, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {

View File

@@ -137,7 +137,7 @@ public class CustomPushNotificationHelper {
.addAction(replyAction);
}
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle) {
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle, boolean createSummary) {
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_HIGH_IMPORTANCE_ID);
String channelId = bundle.getString("channel_id");
@@ -148,7 +148,7 @@ public class CustomPushNotificationHelper {
addNotificationExtras(notification, bundle);
setNotificationIcons(context, notification, bundle);
setNotificationMessagingStyle(context, notification, bundle);
setNotificationGroup(notification, channelId);
setNotificationGroup(notification, channelId, createSummary);
setNotificationBadgeType(notification);
setNotificationSound(notification, notificationPreferences);
setNotificationVibrate(notification, notificationPreferences);
@@ -373,10 +373,13 @@ public class CustomPushNotificationHelper {
notification.setStyle(messagingStyle);
}
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId) {
notification
.setGroup(channelId)
.setGroupSummary(true);
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId, boolean setAsSummary) {
notification.setGroup(channelId);
if (setAsSummary) {
// if this is the first notification for the channel then set as summary, otherwise skip
notification.setGroupSummary(true);
}
}
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {

View File

@@ -4,7 +4,6 @@ import android.content.Context;
import android.content.RestrictionsManager;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.io.File;
import java.util.HashMap;
import java.util.List;
@@ -101,7 +100,7 @@ private final ReactNativeHost mReactNativeHost =
@Override
protected JSIModulePackage getJSIModulePackage() {
return new CustomMMKVJSIModulePackage();
return (JSIModulePackage) new CustomMMKVJSIModulePackage();
}
};

View File

@@ -9,11 +9,13 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import java.io.IOException;
import java.util.Objects;
import okhttp3.Call;
import okhttp3.MediaType;
@@ -21,7 +23,6 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.annotations.EverythingIsNonNull;
import org.json.JSONObject;
import org.json.JSONException;
@@ -88,21 +89,19 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
@EverythingIsNonNull
public void onFailure(Call call, IOException e) {
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
onReplyFailed(notificationId);
}
@Override
@EverythingIsNonNull
public void onResponse(Call call, final Response response) throws IOException {
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
onReplySuccess(notificationId, message);
Log.i("ReactNative", "Reply SUCCESS");
} else {
assert response.body() != null;
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), response.body().string()));
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), Objects.requireNonNull(response.body()).string()));
onReplyFailed(notificationId);
}
}
@@ -133,7 +132,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
final Intent cta = new Intent(mContext, ProxyService.class);
final PushNotificationProps notificationProps = new PushNotificationProps(bundle);
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, cta, notificationProps);
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle);
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle, false);
Notification notification = builder.build();
NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
assert messagingStyle != null;

View File

@@ -31,3 +31,164 @@ exports[`MarkdownEmoji should match snapshot 1`] = `
</Unknown>
</Unknown>
`;
exports[`MarkdownEmoji should render with hardbreaks 1`] = `
<Unknown
context={Array []}
first={true}
last={true}
literal={null}
nodeKey="22"
>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="4"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="3"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="5"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="8"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="7"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=""
nodeKey="9"
/>
<Unknown
context={
Array [
"paragraph",
]
}
literal={null}
nodeKey="10"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="13"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="12"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="14"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="17"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="16"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="18"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="21"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="20"
/>
</Unknown>
</Unknown>
`;

View File

@@ -39,6 +39,7 @@ export default class MarkdownEmoji extends PureComponent {
paragraph: this.renderParagraph,
document: this.renderParagraph,
text: this.renderText,
hardbreak: this.renderNewLine,
},
});
};
@@ -73,10 +74,14 @@ export default class MarkdownEmoji extends PureComponent {
renderText = ({context, literal}) => {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{literal}</Text>;
};
renderNewLine = ({context}) => {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{'\n'}</Text>;
}
renderEditedIndicator = ({context}) => {
let spacer = '';
if (context[0] === 'paragraph') {
@@ -101,7 +106,7 @@ export default class MarkdownEmoji extends PureComponent {
};
render() {
const ast = this.parser.parse(this.props.value);
const ast = this.parser.parse(this.props.value.replace(/\n*$/, ''));
if (this.props.isEdited) {
const editIndicatorNode = new Node('edited_indicator');

View File

@@ -24,4 +24,17 @@ describe('MarkdownEmoji', () => {
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should render with hardbreaks', () => {
const wrapper = shallow(
<MarkdownEmoji
{...baseProps}
value={`:fire: :fire:
:fire: :fire: :fire:
`}
/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View File

@@ -912,7 +912,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 363;
CURRENT_PROJECT_VERSION = 364;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -954,7 +954,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 363;
CURRENT_PROJECT_VERSION = 364;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.45.0</string>
<string>1.45.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.45.0</string>
<string>1.45.1</string>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.45.0</string>
<string>1.45.1</string>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

2426
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "1.45.0",
"version": "1.45.1",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -19,8 +19,8 @@
"@rudderstack/rudder-sdk-react-native": "1.0.10",
"@sentry/react-native": "2.5.2",
"analytics-react-native": "1.2.0",
"commonmark": "github:mattermost/commonmark.js#d716e1c89e0a6721051df7bc74ad7683e1ae438f",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81af294317ebe19b5cc195d7fbc4f4a58177854c",
"commonmark": "0.30.0",
"commonmark-react-renderer": "4.3.5",
"deep-equal": "2.0.5",
"deepmerge": "4.2.2",
"emoji-regex": "9.2.2",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
diff --git a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
index 91b0001..05b80fa 100644
--- a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
+++ b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
@@ -12,7 +12,12 @@ var typeAliases = {
htmlblock: 'html_block',
htmlinline: 'html_inline',
codeblock: 'code_block',
- hardbreak: 'linebreak'
+ hardbreak: 'linebreak',
+ atmention: 'at_mention',
+ channellink: 'channel_link',
+ editedindicator: 'edited_indicator',
+ tableRow: 'table_row',
+ tableCell: 'table_cell'
};
var defaultRenderers = {
@@ -24,6 +29,7 @@ var defaultRenderers = {
link: 'a',
paragraph: 'p',
strong: 'strong',
+ del: 'del',
thematic_break: 'hr', // eslint-disable-line camelcase
html_block: HtmlRenderer, // eslint-disable-line camelcase
@@ -52,7 +58,71 @@ var defaultRenderers = {
},
text: null,
- softbreak: null
+ softbreak: null,
+
+ at_mention: function AtMention(props) {
+ var newProps = getCoreProps(props);
+ if (props.username) {
+ props['data-mention-name'] = props.username;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ channel_link: function ChannelLink(props) {
+ var newProps = getCoreProps(props);
+ if (props.channelName) {
+ props['data-channel-name'] = props.channelName;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ emoji: function Emoji(props) {
+ var newProps = getCoreProps(props);
+ if (props.emojiName) {
+ props['data-emoji-name'] = props.emojiName;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ edited_indicator: null,
+ hashtag: function Hashtag(props) {
+ var newProps = getCoreProps(props);
+ if (props.hashtag) {
+ props['data-hashtag'] = props.hashtag;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ mention_highlight: function MentionHighlight(props) {
+ var newProps = getCoreProps(props);
+ newProps['data-mention-highlight'] = 'true';
+ return createElement('span', newProps, props.children);
+ },
+ search_highlight: function SearchHighlight(props) {
+ var newProps = getCoreProps(props);
+ newProps['data-search-highlight'] = 'true';
+ return createElement('span', newProps, props.children);
+ },
+
+ table: function Table(props) {
+ var childrenArray = React.Children.toArray(props.children);
+
+ var children = [createElement('thead', {'key': 'thead'}, childrenArray.slice(0, 1))];
+ if (childrenArray.length > 1) {
+ children.push(createElement('tbody', {'key': 'tbody'}, childrenArray.slice(1)));
+ }
+
+ return createElement('table', getCoreProps(props), children);
+ },
+ table_row: 'tr',
+ table_cell: function TableCell(props) {
+ var newProps = getCoreProps(props);
+ if (props.align) {
+ newProps.className = 'align-' + props.align;
+ }
+
+ return createElement('td', newProps, props.children);
+ }
};
var coreTypes = Object.keys(defaultRenderers);
@@ -147,7 +217,7 @@ function flattenPosition(pos) {
}
// For some nodes, we want to include more props than for others
-function getNodeProps(node, key, opts, renderer) {
+function getNodeProps(node, key, opts, renderer, context) {
var props = { key: key }, undef;
// `sourcePos` is true if the user wants source information (line/column info from markdown source)
@@ -194,16 +264,49 @@ function getNodeProps(node, key, opts, renderer) {
// Commonmark treats image description as children. We just want the text
props.alt = node.react.children.join('');
- node.react.children = undef;
break;
case 'list':
props.start = node.listStart;
props.type = node.listType;
props.tight = node.listTight;
break;
+ case 'at_mention':
+ props.mentionName = node.mentionName;
+ break;
+ case 'channel_link':
+ props.channelName = node.channelName;
+ break;
+ case 'emoji':
+ props.emojiName = node.emojiName;
+ props.literal = node.literal;
+ break;
+ case 'hashtag':
+ props.hashtag = node.hashtag;
+ break;
+ case 'paragraph':
+ props.first = !(node._prev && node._prev.type === 'paragraph');
+ props.last = !(node._next && node._next.type === 'paragraph');
+ break;
+ case 'edited_indicator':
+ break;
+ case 'table':
+ props.numRows = countRows(node);
+ props.numColumns = countColumns(node);
+ break;
+ case 'table_row':
+ props.isHeading = node.isHeading;
+ break;
+ case 'table_cell':
+ props.isHeading = node.isHeading;
+ props.align = node.align;
+ break;
default:
}
+ if (opts.getExtraPropsForNode) {
+ props = Object.assign(props, opts.getExtraPropsForNode(node));
+ }
+
if (typeof renderer !== 'string') {
props.literal = node.literal;
}
@@ -213,9 +316,29 @@ function getNodeProps(node, key, opts, renderer) {
props.children = children.reduce(reduceChildren, []) || null;
}
+ props.context = context.slice();
+
return props;
}
+function countChildren(node) {
+ var count = 0;
+
+ for (var child = node.firstChild; child; child = child.next) {
+ count += 1;
+ }
+
+ return count;
+}
+
+function countRows(table) {
+ return countChildren(table);
+}
+
+function countColumns(table) {
+ return countChildren(table.firstChild);
+}
+
function getPosition(node) {
if (!node) {
return null;
@@ -238,26 +361,23 @@ function renderNodes(block) {
transformLinkUri: this.transformLinkUri,
transformImageUri: this.transformImageUri,
softBreak: this.softBreak,
- linkTarget: this.linkTarget
+ linkTarget: this.linkTarget,
+ getExtraPropsForNode: this.getExtraPropsForNode
};
- var e, node, entering, leaving, type, doc, key, nodeProps, prevPos, prevIndex = 0;
+ var e;
+ var doc;
+ var context = [];
+ var index = 0;
while ((e = walker.next())) {
- var pos = getPosition(e.node.sourcepos ? e.node : e.node.parent);
- if (prevPos === pos) {
- key = pos + prevIndex;
- prevIndex++;
- } else {
- key = pos;
- prevIndex = 0;
- }
+ var key = String(index);
+ index += 1;
- prevPos = pos;
- entering = e.entering;
- leaving = !entering;
- node = e.node;
- type = normalizeTypeName(node.type);
- nodeProps = null;
+ var entering = e.entering;
+ var leaving = !entering;
+ var node = e.node;
+ var type = normalizeTypeName(node.type);
+ var nodeProps = null;
// If we have not assigned a document yet, assume the current node is just that
if (!doc) {
@@ -270,7 +390,7 @@ function renderNodes(block) {
}
// In HTML, we don't want paragraphs inside of list items
- if (type === 'paragraph' && isGrandChildOfList(node)) {
+ if (!this.renderParagraphsInLists && type === 'paragraph' && isGrandChildOfList(node)) {
continue;
}
@@ -289,7 +409,7 @@ function renderNodes(block) {
if (this.allowNode && (isCompleteParent || !node.isContainer)) {
var nodeChildren = isCompleteParent ? node.react.children : [];
- nodeProps = getNodeProps(node, key, propOptions, renderer);
+ nodeProps = getNodeProps(node, key, propOptions, renderer, context);
disallowedByUser = !this.allowNode({
type: pascalCase(type),
renderer: this.renderers[type],
@@ -298,6 +418,30 @@ function renderNodes(block) {
});
}
+ if (node.isContainer) {
+ var contextType = node.type;
+ if (node.level) {
+ contextType = node.type + node.level;
+ } else if (node.type === 'table_row' && node.parent.firstChild === node) {
+ contextType = 'table_header_row';
+ } else {
+ contextType = node.type;
+ }
+
+ if (entering) {
+ context.push(contextType);
+ } else {
+ var popped = context.pop();
+
+ if (!popped) {
+ throw new Error('Attempted to pop empty stack');
+ } else if (!popped === contextType) {
+ throw new Error('Popped context of type `' + pascalCase(popped) +
+ '` when expecting context of type `' + pascalCase(contextType) + '`');
+ }
+ }
+ }
+
if (!isDocument && (disallowedByUser || disallowedByConfig)) {
if (!this.unwrapDisallowed && entering && node.isContainer) {
walker.resumeAt(node, false);
@@ -313,15 +457,25 @@ function renderNodes(block) {
);
}
- if (node.isContainer && entering) {
+ if (context.length > this.maxDepth) {
+ // Do nothing, we should not regularly be nested this deeply and we don't want to cause React to
+ // overflow the stack
+ } else if (node.isContainer && entering) {
node.react = {
component: renderer,
props: {},
children: []
};
} else {
- var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer);
- if (renderer) {
+ var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer, context);
+ if (renderer === ReactRenderer.forwardChildren) {
+ if (childProps.children) {
+ for (var i = 0; i < childProps.children.length; i++) {
+ var child = childProps.children[i];
+ addChild(node, child);
+ }
+ }
+ } else if (renderer) {
childProps = typeof renderer === 'string'
? childProps
: assign(childProps, {nodeKey: childProps.key});
@@ -341,6 +495,10 @@ function renderNodes(block) {
}
}
+ if (context.length !== 0) {
+ throw new Error('Expected context to be empty after rendering, but has `' + context.join(', ') + '`');
+ }
+
return doc.react.children;
}
@@ -401,21 +559,31 @@ function ReactRenderer(options) {
renderers: assign({}, defaultRenderers, normalizeRenderers(opts.renderers)),
escapeHtml: Boolean(opts.escapeHtml),
skipHtml: Boolean(opts.skipHtml),
+ renderParagraphsInLists: Boolean(opts.renderParagraphsInLists),
transformLinkUri: linkFilter,
transformImageUri: imageFilter,
allowNode: opts.allowNode,
allowedTypes: allowedTypes,
unwrapDisallowed: Boolean(opts.unwrapDisallowed),
render: renderNodes,
- linkTarget: opts.linkTarget || false
+ linkTarget: opts.linkTarget || false,
+ maxDepth: opts.maxDepth || 30,
+ getExtraPropsForNode: opts.getExtraPropsForNode
};
}
+function forwardChildren(props) {
+ return props.children;
+}
+
ReactRenderer.uriTransformer = defaultLinkUriFilter;
ReactRenderer.types = coreTypes.map(pascalCase);
ReactRenderer.renderers = coreTypes.reduce(function(renderers, type) {
renderers[pascalCase(type)] = defaultRenderers[type];
return renderers;
}, {});
+ReactRenderer.countRows = countRows;
+ReactRenderer.countColumns = countColumns;
+ReactRenderer.forwardChildren = forwardChildren;
module.exports = ReactRenderer;