diff --git a/ts/components/conversation/AddNewLines.md b/ts/components/conversation/AddNewLines.md
new file mode 100644
index 000000000..14d2b35ec
--- /dev/null
+++ b/ts/components/conversation/AddNewLines.md
@@ -0,0 +1,35 @@
+### All newlines
+
+```jsx
+
+```
+
+### Starting and ending with newlines
+
+```jsx
+
+```
+
+### With newlines in the middle
+
+```jsx
+
+```
+
+### No newlines
+
+```jsx
+
+```
+
+### Providing custom non-newline render function
+
+```jsx
+const renderNonNewLine = ({ text, key }) => (
+ This is my custom content!
+);
+;
+```
diff --git a/ts/components/conversation/AddNewLines.tsx b/ts/components/conversation/AddNewLines.tsx
index 7e9647b9b..b4bdfe135 100644
--- a/ts/components/conversation/AddNewLines.tsx
+++ b/ts/components/conversation/AddNewLines.tsx
@@ -1,27 +1,43 @@
import React from 'react';
+import { RenderTextCallback } from '../../types/Util';
+
interface Props {
text: string;
+ /** Allows you to customize now non-newlines are rendered. Simplest is just a . */
+ renderNonNewLine?: RenderTextCallback;
}
export class AddNewLines extends React.Component {
+ public static defaultProps: Partial = {
+ renderNonNewLine: ({ text, key }) => {text},
+ };
+
public render() {
- const { text } = this.props;
+ const { text, renderNonNewLine } = this.props;
const results: Array = [];
const FIND_NEWLINES = /\n/g;
+ // We have to do this, because renderNonNewLine is not required in our Props object,
+ // but it is always provided via defaultProps.
+ if (!renderNonNewLine) {
+ return;
+ }
+
let match = FIND_NEWLINES.exec(text);
let last = 0;
let count = 1;
if (!match) {
- return {text};
+ return renderNonNewLine({ text, key: 0 });
}
while (match) {
if (last < match.index) {
const textWithNoNewline = text.slice(last, match.index);
- results.push({textWithNoNewline});
+ results.push(
+ renderNonNewLine({ text: textWithNoNewline, key: count++ })
+ );
}
results.push(
);
@@ -32,9 +48,9 @@ export class AddNewLines extends React.Component {
}
if (last < text.length) {
- results.push({text.slice(last)});
+ results.push(renderNonNewLine({ text: text.slice(last), key: count++ }));
}
- return {results};
+ return results;
}
}
diff --git a/ts/components/conversation/ContactDetail.tsx b/ts/components/conversation/ContactDetail.tsx
index 4437f6559..1ea63b65a 100644
--- a/ts/components/conversation/ContactDetail.tsx
+++ b/ts/components/conversation/ContactDetail.tsx
@@ -17,7 +17,7 @@ import {
renderSendMessage,
} from './EmbeddedContact';
-type Localizer = (key: string, values?: Array) => string;
+import { Localizer } from '../../types/Util';
interface Props {
contact: Contact;
diff --git a/ts/components/conversation/Emojify.md b/ts/components/conversation/Emojify.md
new file mode 100644
index 000000000..17e2c9e1a
--- /dev/null
+++ b/ts/components/conversation/Emojify.md
@@ -0,0 +1,60 @@
+### All emoji
+
+```jsx
+
+```
+
+### With skin color modifier
+
+```jsx
+
+```
+
+### With `sizeClass` provided
+
+```jsx
+
+```
+
+```jsx
+
+```
+
+```jsx
+
+```
+
+```jsx
+
+```
+
+```jsx
+
+```
+
+### Starting and ending with emoji
+
+```jsx
+
+```
+
+### With emoji in the middle
+
+```jsx
+
+```
+
+### No emoji
+
+```jsx
+
+```
+
+### Providing custom non-link render function
+
+```jsx
+const renderNonEmoji = ({ text, key }) => (
+ This is my custom content
+);
+;
+```
diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx
index 6a323d959..591b30522 100644
--- a/ts/components/conversation/Emojify.tsx
+++ b/ts/components/conversation/Emojify.tsx
@@ -9,7 +9,8 @@ import {
getReplacementData,
getTitle,
} from '../../util/emoji';
-import { AddNewLines } from './AddNewLines';
+
+import { RenderTextCallback } from '../../types/Util';
// Some of this logic taken from emoji-js/replacement
function getImageTag({
@@ -43,27 +44,40 @@ function getImageTag({
interface Props {
text: string;
- sizeClass?: string;
+ /** A class name to be added to the generated emoji images */
+ sizeClass?: '' | 'small' | 'medium' | 'large' | 'jumbo';
+ /** Allows you to customize now non-newlines are rendered. Simplest is just a . */
+ renderNonEmoji?: RenderTextCallback;
}
export class Emojify extends React.Component {
+ public static defaultProps: Partial = {
+ renderNonEmoji: ({ text, key }) => {text},
+ };
+
public render() {
- const { text, sizeClass } = this.props;
+ const { text, sizeClass, renderNonEmoji } = this.props;
const results: Array = [];
const regex = getRegex();
+ // We have to do this, because renderNonEmoji is not required in our Props object,
+ // but it is always provided via defaultProps.
+ if (!renderNonEmoji) {
+ return;
+ }
+
let match = regex.exec(text);
let last = 0;
let count = 1;
if (!match) {
- return ;
+ return renderNonEmoji({ text, key: 0 });
}
while (match) {
if (last < match.index) {
const textWithNoEmoji = text.slice(last, match.index);
- results.push();
+ results.push(renderNonEmoji({ text: textWithNoEmoji, key: count++ }));
}
results.push(getImageTag({ match, sizeClass, key: count++ }));
@@ -73,9 +87,9 @@ export class Emojify extends React.Component {
}
if (last < text.length) {
- results.push();
+ results.push(renderNonEmoji({ text: text.slice(last), key: count++ }));
}
- return {results};
+ return results;
}
}
diff --git a/ts/components/conversation/Linkify.md b/ts/components/conversation/Linkify.md
new file mode 100644
index 000000000..aa53d40c2
--- /dev/null
+++ b/ts/components/conversation/Linkify.md
@@ -0,0 +1,44 @@
+### All link
+
+```jsx
+
+```
+
+### Starting and ending with link
+
+```jsx
+
+```
+
+### With a link in the middle
+
+```jsx
+
+```
+
+### No link
+
+```jsx
+
+```
+
+### Should not render as link
+
+```jsx
+
+```
+
+### Should render as link
+
+```jsx
+
+```
+
+### Providing custom non-link render function
+
+```jsx
+const renderNonLink = ({ text, key }) => (
+ This is my custom non-link content!
+);
+;
+```
diff --git a/ts/components/conversation/Linkify.tsx b/ts/components/conversation/Linkify.tsx
new file mode 100644
index 000000000..bc5217e02
--- /dev/null
+++ b/ts/components/conversation/Linkify.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+
+import createLinkify from 'linkify-it';
+
+import { RenderTextCallback } from '../../types/Util';
+
+const linkify = createLinkify();
+
+interface Props {
+ text: string;
+ /** Allows you to customize now non-links are rendered. Simplest is just a . */
+ renderNonLink?: RenderTextCallback;
+}
+
+const SUPPORTED_PROTOCOLS = /^(http|https):/i;
+
+export class Linkify extends React.Component {
+ public static defaultProps: Partial = {
+ renderNonLink: ({ text, key }) => {text},
+ };
+
+ public render() {
+ const { text, renderNonLink } = this.props;
+ const matchData = linkify.match(text) || [];
+ const results: Array = [];
+ let last = 0;
+ let count = 1;
+
+ // We have to do this, because renderNonLink is not required in our Props object,
+ // but it is always provided via defaultProps.
+ if (!renderNonLink) {
+ return;
+ }
+
+ if (matchData.length === 0) {
+ return renderNonLink({ text, key: 0 });
+ }
+
+ matchData.forEach(
+ (match: {
+ index: number;
+ url: string;
+ lastIndex: number;
+ text: string;
+ }) => {
+ if (last < match.index) {
+ const textWithNoLink = text.slice(last, match.index);
+ results.push(renderNonLink({ text: textWithNoLink, key: count++ }));
+ }
+
+ const { url, text: originalText } = match;
+ if (SUPPORTED_PROTOCOLS.test(url)) {
+ results.push(
+
+ {originalText}
+
+ );
+ } else {
+ results.push(renderNonLink({ text: originalText, key: count++ }));
+ }
+
+ last = match.lastIndex;
+ }
+ );
+
+ if (last < text.length) {
+ results.push(renderNonLink({ text: text.slice(last), key: count++ }));
+ }
+
+ return results;
+ }
+}
diff --git a/ts/components/conversation/MessageBody.md b/ts/components/conversation/MessageBody.md
index be171b7ce..8c1f36349 100644
--- a/ts/components/conversation/MessageBody.md
+++ b/ts/components/conversation/MessageBody.md
@@ -1,11 +1,7 @@
-### Plain text
+### All components: emoji, links, newline
```jsx
-
-```
-
-```jsx
-
+
```
### Jumbo emoji
@@ -31,33 +27,17 @@
```
```jsx
-
+
```
-### Text and emoji
-
-```jsx
-
-```
+### Jumbomoji disabled
```jsx
-
+
```
-### Links
-
-```jsx
-
-```
-
-```jsx
-
-```
-
-```jsx
-
-```
+### Links disabled
```jsx
-
+
```
diff --git a/ts/components/conversation/MessageBody.tsx b/ts/components/conversation/MessageBody.tsx
index 703d902c9..058c79368 100644
--- a/ts/components/conversation/MessageBody.tsx
+++ b/ts/components/conversation/MessageBody.tsx
@@ -1,67 +1,46 @@
import React from 'react';
-import createLinkify from 'linkify-it';
-
import { getSizeClass } from '../../util/emoji';
import { Emojify } from './Emojify';
+import { AddNewLines } from './AddNewLines';
+import { Linkify } from './Linkify';
-const linkify = createLinkify();
+import { RenderTextCallback } from '../../types/Util';
interface Props {
text: string;
+ /** If set, all emoji will be the same size. Otherwise, just one emoji will be large. */
disableJumbomoji?: boolean;
+ /** If set, links will be left alone instead of turned into clickable `` tags. */
disableLinks?: boolean;
}
-const SUPPORTED_PROTOCOLS = /^(http|https):/i;
-
+const renderNewLines: RenderTextCallback = ({
+ text: textWithNewLines,
+ key,
+}) => ;
+
+const renderLinks: RenderTextCallback = ({ text: textWithLinks, key }) => (
+
+);
+
+/**
+ * This component makes it very easy to use all three of our message formatting
+ * components: `Emojify`, `Linkify`, and `AddNewLines`. Because each of them is fully
+ * configurable with their `renderXXX` props, this component will assemble all three of
+ * them for you.
+ */
export class MessageBody extends React.Component {
public render() {
const { text, disableJumbomoji, disableLinks } = this.props;
- const matchData = linkify.match(text) || [];
- const results: Array = [];
- let last = 0;
- let count = 1;
-
- // We only use this sizeClass if there was no link detected, because jumbo emoji
- // only fire when there's no other text in the message.
const sizeClass = disableJumbomoji ? '' : getSizeClass(text);
- if (disableLinks || matchData.length === 0) {
- return ;
- }
-
- matchData.forEach(
- (match: {
- index: number;
- url: string;
- lastIndex: number;
- text: string;
- }) => {
- if (last < match.index) {
- const textWithNoLink = text.slice(last, match.index);
- results.push();
- }
-
- const { url, text: originalText } = match;
- if (SUPPORTED_PROTOCOLS.test(url)) {
- results.push(
-
- {originalText}
-
- );
- } else {
- results.push();
- }
-
- last = match.lastIndex;
- }
+ return (
+
);
-
- if (last < text.length) {
- results.push();
- }
-
- return {results};
}
}
diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts
index fdc9d628a..d7ec25285 100644
--- a/ts/styleguide/StyleGuideUtil.ts
+++ b/ts/styleguide/StyleGuideUtil.ts
@@ -218,11 +218,3 @@ parent.storage.put('regionCode', 'US');
// Telling Lodash to relinquish _ for use by underscore
// @ts-ignore
_.noConflict();
-
-parent.emoji.signalReplace = (html: string): string => {
- return html.replace(
- /🔥/g,
- '
'
- );
-};
diff --git a/ts/types/Util.ts b/ts/types/Util.ts
new file mode 100644
index 000000000..9819b5a85
--- /dev/null
+++ b/ts/types/Util.ts
@@ -0,0 +1,8 @@
+export type RenderTextCallback = (
+ options: {
+ text: string;
+ key: number;
+ }
+) => JSX.Element;
+
+export type Localizer = (key: string, values?: Array) => string;