Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.

Commit 80fad2c

Browse files
authored
Merge pull request #4759 from lookapanda/message-syntax-highlighting
Message syntax highlighting
2 parents 9566a41 + fb5ced8 commit 80fad2c

File tree

16 files changed

+178
-87
lines changed

16 files changed

+178
-87
lines changed

api/mutations/directMessageThread/createDirectMessageThread.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import type { FileUpload } from 'shared/types';
1717
import { events } from 'shared/analytics';
1818
import { trackQueue } from 'shared/bull/queues';
1919
import { isAuthedResolver as requireAuth } from '../../utils/permissions';
20+
import { messageTypeObj } from 'shared/draft-utils/process-message-content';
21+
import type { MessageType } from 'shared/draft-utils/process-message-content';
2022

2123
export type CreateDirectMessageThreadInput = {
2224
input: {
2325
participants: Array<string>,
2426
message: {
25-
messageType: 'text' | 'media' | 'draftjs',
27+
messageType: MessageType,
2628
threadType: string,
2729
content: {
2830
body: string,
@@ -80,15 +82,18 @@ export default requireAuth(
8082
}
8183

8284
const handleStoreMessage = async message => {
83-
if (message.messageType === 'text' || message.messageType === 'draftjs') {
85+
if (
86+
message.messageType === messageTypeObj.text ||
87+
message.messageType === messageTypeObj.draftjs
88+
) {
8489
// once we have an id we can generate a proper message object
8590
const messageWithThread = {
8691
...message,
8792
threadId,
8893
};
8994

9095
return await storeMessage(messageWithThread, user.id);
91-
} else if (message.messageType === 'media' && message.file) {
96+
} else if (message.messageType === messageTypeObj.media && message.file) {
9297
let url;
9398
try {
9499
url = await uploadImage(message.file, 'threads', threadId);

api/mutations/message/addMessage.js

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ import {
1919
} from '../../utils/permissions';
2020
import { trackQueue, calculateThreadScoreQueue } from 'shared/bull/queues';
2121
import { validateRawContentState } from '../../utils/validate-draft-js-input';
22+
import processMessageContent, {
23+
messageTypeObj,
24+
} from 'shared/draft-utils/process-message-content';
25+
import type { MessageType } from 'shared/draft-utils/process-message-content';
2226

2327
type Input = {
2428
message: {
2529
threadId: string,
2630
threadType: 'story' | 'directMessageThread',
27-
messageType: 'text' | 'media' | 'draftjs',
31+
messageType: MessageType,
2832
content: {
2933
body: string,
3034
},
@@ -42,20 +46,15 @@ export const addMessage = async (
4246
? events.MESSAGE_SENT_FAILED
4347
: events.DIRECT_MESSAGE_SENT_FAILED;
4448

45-
if (message.messageType === 'text') {
46-
message.content.body = JSON.stringify(
47-
convertToRaw(
48-
stateFromMarkdown(message.content.body, {
49-
parserOptions: {
50-
breaks: true,
51-
},
52-
})
53-
)
49+
if (message.messageType === messageTypeObj.text) {
50+
message.content.body = processMessageContent(
51+
messageTypeObj.text,
52+
message.content.body
5453
);
55-
message.messageType = 'draftjs';
54+
message.messageType = messageTypeObj.draftjs;
5655
}
5756

58-
if (message.messageType === 'draftjs') {
57+
if (message.messageType === messageTypeObj.draftjs) {
5958
let body;
6059
try {
6160
body = JSON.parse(message.content.body);
@@ -107,7 +106,7 @@ export const addMessage = async (
107106

108107
// construct the shape of the object to be stored in the db
109108
let messageForDb = Object.assign({}, message);
110-
if (message.file && message.messageType === 'media') {
109+
if (message.file && message.messageType === messageTypeObj.media) {
111110
const { file } = message;
112111

113112
const fileMetaData = {
@@ -186,7 +185,7 @@ export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => {
186185
}
187186
}
188187

189-
if (message.messageType === 'media' && !message.file) {
188+
if (message.messageType === messageTypeObj.media && !message.file) {
190189
trackQueue.add({
191190
userId: user.id,
192191
event: eventFailed,

api/mutations/message/editMessage.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// @flow
22
import type { GraphQLContext } from '../../';
3-
import { convertToRaw } from 'draft-js';
43
import { stateFromMarkdown } from 'draft-js-import-markdown';
54
import UserError from '../../utils/UserError';
65
import {
@@ -17,11 +16,15 @@ import { events } from 'shared/analytics';
1716
import { isAuthedResolver as requireAuth } from '../../utils/permissions';
1817
import { trackQueue } from 'shared/bull/queues';
1918
import { validateRawContentState } from '../../utils/validate-draft-js-input';
19+
import processMessageContent, {
20+
messageTypeObj,
21+
} from 'shared/draft-utils/process-message-content';
22+
import type { MessageType } from 'shared/draft-utils/process-message-content';
2023

2124
type Args = {
2225
input: {
2326
id: string,
24-
messageType?: 'draftjs' | 'text' | 'media',
27+
messageType?: MessageType,
2528
content: {
2629
body: string,
2730
},
@@ -49,24 +52,16 @@ export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => {
4952
}
5053

5154
let body = content.body;
52-
if (messageType === 'text') {
53-
body = JSON.stringify(
54-
convertToRaw(
55-
stateFromMarkdown(body, {
56-
parserOptions: {
57-
breaks: true,
58-
},
59-
})
60-
)
61-
);
62-
messageType = 'draftjs';
55+
if (messageType === messageTypeObj.text) {
56+
body = processMessageContent(messageTypeObj.text, body);
57+
messageType = messageTypeObj.draftjs;
6358
}
6459
const eventFailed =
6560
message.threadType === 'story'
6661
? events.MESSAGE_EDITED_FAILED
6762
: events.DIRECT_MESSAGE_EDITED_FAILED;
6863

69-
if (messageType === 'draftjs') {
64+
if (messageType === messageTypeObj.draftjs) {
7065
let parsed;
7166
try {
7267
parsed = JSON.parse(body);

api/queries/directMessageThread/snippet.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type { GraphQLContext } from '../../';
44
import { canViewDMThread } from '../../utils/permissions';
55
import { toPlainText, toState } from 'shared/draft-utils';
6+
import { messageTypeObj } from 'shared/draft-utils/process-message-content';
67

78
export default async (
89
{ id }: { id: string },
@@ -17,7 +18,7 @@ export default async (
1718
return loaders.directMessageSnippet.load(id).then(message => {
1819
if (!message) return 'No messages yet...';
1920
if (message.messageType === 'media') return '📷 Photo';
20-
return message.messageType === 'draftjs'
21+
return message.messageType === messageTypeObj.draftjs
2122
? toPlainText(toState(JSON.parse(message.content.body)))
2223
: message.content.body;
2324
});

athena/queues/direct-message-notification.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { toPlainText, toState } from 'shared/draft-utils';
2020
import { sendNewDirectMessageEmailQueue } from 'shared/bull/queues';
2121
import type { Job, DirectMessageNotificationJobData } from 'shared/bull/types';
2222
import { signUser, signMessage } from 'shared/imgix';
23+
import { messageTypeObj } from 'shared/draft-utils/process-message-content';
2324

2425
export default async (job: Job<DirectMessageNotificationJobData>) => {
2526
const { message: incomingMessage, userId: currentUserId } = job.data;
@@ -115,7 +116,7 @@ export default async (job: Job<DirectMessageNotificationJobData>) => {
115116
...signedMessage,
116117
content: {
117118
body:
118-
signedMessage.messageType === 'draftjs'
119+
signedMessage.messageType === messageTypeObj.draftjs
119120
? toPlainText(toState(JSON.parse(signedMessage.content.body)))
120121
: signedMessage.content.body,
121122
},

athena/queues/moderationEvents/message.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { toState, toPlainText } from 'shared/draft-utils';
88
import getPerspectiveScore from './perspective';
99
import { _adminSendToxicContentEmailQueue } from 'shared/bull/queues';
1010
import type { Job, AdminToxicMessageJobData } from 'shared/bull/types';
11+
import { messageTypeObj } from 'shared/draft-utils/process-message-content';
1112

1213
export default async (job: Job<AdminToxicMessageJobData>) => {
1314
debug('new job for admin message moderation');
@@ -16,7 +17,7 @@ export default async (job: Job<AdminToxicMessageJobData>) => {
1617
} = job;
1718

1819
const text =
19-
message.messageType === 'draftjs'
20+
message.messageType === messageTypeObj.draftjs
2021
? toPlainText(toState(JSON.parse(message.content.body)))
2122
: message.content.body;
2223

athena/queues/new-message-in-thread/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { getMessageById } from '../../models/message';
2424
import { sendMentionNotificationQueue } from 'shared/bull/queues';
2525
import type { MessageNotificationJobData, Job } from 'shared/bull/types';
2626
import type { DBMessage } from 'shared/types';
27+
import { messageTypeObj } from 'shared/draft-utils/process-message-content';
2728

2829
export default async (job: Job<MessageNotificationJobData>) => {
2930
const { message: incomingMessage } = job.data;
@@ -95,7 +96,7 @@ export default async (job: Job<MessageNotificationJobData>) => {
9596

9697
// convert the message body to be checked for mentions
9798
const body =
98-
incomingMessage.messageType === 'draftjs'
99+
incomingMessage.messageType === messageTypeObj.draftjs
99100
? toPlainText(toState(JSON.parse(incomingMessage.content.body)))
100101
: incomingMessage.content.body;
101102

shared/clients/draft-js/message/renderer.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @flow
22
import React from 'react';
3+
import Highlight, { defaultProps } from 'prism-react-renderer';
4+
import { getStringElements } from '../utils/getStringElements';
35
import mentionsDecorator from '../mentions-decorator/index';
46
import linksDecorator from '../links-decorator/index';
57
import { Line, Paragraph, BlockQuote } from 'src/components/message/style';
@@ -25,11 +27,29 @@ const messageRenderer = {
2527
children.map((child, index) => (
2628
<Paragraph key={keys[index] || index}>{child}</Paragraph>
2729
)),
28-
'code-block': (children: Array<Node>, { keys }: KeysObj) => (
29-
<Line key={keys.join('|')}>
30-
{children.map((child, i) => [child, <br key={i} />])}
31-
</Line>
32-
),
30+
'code-block': (children: Array<any>, { keys, data }: KeysObj) => {
31+
return children.map((child, index) => (
32+
<Highlight
33+
{...defaultProps}
34+
code={getStringElements(child).join('\n')}
35+
language={Array.isArray(data) && data[0].language}
36+
theme={undefined}
37+
key={keys[index]}
38+
>
39+
{({ className, style, tokens, getLineProps, getTokenProps }) => (
40+
<Line className={className} style={style}>
41+
{tokens.map((line, i) => (
42+
<div {...getLineProps({ line, key: i })}>
43+
{line.map((token, key) => (
44+
<span {...getTokenProps({ token, key })} />
45+
))}
46+
</div>
47+
))}
48+
</Line>
49+
)}
50+
</Highlight>
51+
));
52+
},
3353
blockquote: (children: Array<Node>, { keys }: KeysObj) =>
3454
children.map((child, index) => (
3555
<BlockQuote key={keys[index] || index}>{child}</BlockQuote>

shared/clients/draft-js/thread/renderer.js

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
EmbedContainer,
88
EmbedComponent,
99
} from 'src/components/rich-text-editor/style';
10+
import { getStringElements } from '../utils/getStringElements';
11+
import { hasStringElements } from '../utils/hasStringElements';
1012
import mentionsDecorator from '../mentions-decorator';
1113
import linksDecorator from '../links-decorator';
1214
import type { Node } from 'react';
@@ -54,30 +56,6 @@ const Embed = (props: EmbedProps) => {
5456
}
5557
};
5658

57-
const hasStringElements = (arr: Array<mixed> | mixed) => {
58-
if (Array.isArray(arr)) return arr.some(elem => hasStringElements(elem));
59-
60-
return typeof arr === 'string';
61-
};
62-
63-
const getStringElements = (arr: Array<mixed>): Array<string> => {
64-
return arr
65-
.map(elem => {
66-
if (Array.isArray(elem)) return getStringElements(elem);
67-
if (typeof elem === 'string') return elem;
68-
// Handle React elements being passed as array elements
69-
// $FlowIssue
70-
if (elem.props && elem.props.children)
71-
return getStringElements(elem.props.children);
72-
return null;
73-
})
74-
.filter(Boolean)
75-
.reduce((final, elem) => {
76-
if (Array.isArray(elem)) return [...final, ...elem];
77-
return [...final, elem];
78-
}, []);
79-
};
80-
8159
const threadRenderer = {
8260
inline: {
8361
BOLD: (children: Array<Node>, { key }: KeyObj) => (
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @flow
2+
export const getStringElements = (arr: Array<mixed>): Array<string> => {
3+
return arr
4+
.map(elem => {
5+
if (Array.isArray(elem)) return getStringElements(elem);
6+
if (typeof elem === 'string') return elem;
7+
// Handle React elements being passed as array elements
8+
// $FlowIssue
9+
if (elem.props && elem.props.children)
10+
return getStringElements(elem.props.children);
11+
return null;
12+
})
13+
.filter(Boolean)
14+
.reduce((final, elem) => {
15+
if (Array.isArray(elem)) return [...final, ...elem];
16+
return [...final, elem];
17+
}, []);
18+
};

0 commit comments

Comments
 (0)