Skip to content

Commit 8e1d43e

Browse files
committed
fix: insert multiline code block as a fenced block on its own lines
The code-block formatting button wrapped the selection inline, but fenced code blocks are only parsed when the ``` markers are alone on a line. Tapping the button therefore produced six backticks around the cursor on a single line, which does not render as a code block. Insert the opening and closing fences on their own lines instead, adding a leading newline when the cursor is not already at the start of a line, and leave the cursor on the (possibly empty) content line between the fences. Any selected text is wrapped inside the block. The logic is extracted into a pure wrapCodeBlock helper with unit tests. Closes #7099
1 parent 6bbf88a commit 8e1d43e

5 files changed

Lines changed: 72 additions & 3 deletions

File tree

app/containers/MessageComposer/MessageComposer.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ describe('MessageComposer', () => {
403403
await user.press(screen.getByTestId('message-composer-code-block'));
404404
await user.press(screen.getByTestId('message-composer-send'));
405405
expect(onSendMessage).toHaveBeenCalledTimes(1);
406-
expect(onSendMessage).toHaveBeenCalledWith('``````', false);
406+
expect(onSendMessage).toHaveBeenCalledWith('```\n\n```', false);
407407
expect(screen.toJSON()).toMatchSnapshot();
408408
});
409409

@@ -422,7 +422,7 @@ describe('MessageComposer', () => {
422422
await user.press(screen.getByTestId('message-composer-code-block'));
423423
await user.press(screen.getByTestId('message-composer-send'));
424424
expect(onSendMessage).toHaveBeenCalledTimes(1);
425-
expect(onSendMessage).toHaveBeenCalledWith('```test```', false);
425+
expect(onSendMessage).toHaveBeenCalledWith('```\ntest\n```', false);
426426
expect(screen.toJSON()).toMatchSnapshot();
427427
});
428428
});

app/containers/MessageComposer/components/ComposerInput.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
type TSetInput
1515
} from '../interfaces';
1616
import { useAutocompleteParams, useFocused, useMessageComposerApi, useMicOrSend } from '../context';
17-
import { fetchIsAllOrHere, getMentionRegexp } from '../helpers';
17+
import { fetchIsAllOrHere, getMentionRegexp, wrapCodeBlock } from '../helpers';
1818
import { useAutoSaveDraft } from '../hooks';
1919
import sharedStyles from '../../../views/Styles';
2020
import { useTheme } from '../../../theme';
@@ -131,6 +131,11 @@ export const ComposerInput = memo(
131131
emitter.on('addMarkdown', ({ style }) => {
132132
const { start, end } = selectionRef.current;
133133
const text = textRef.current;
134+
if (style === 'code-block') {
135+
const { updatedText, selection } = wrapCodeBlock(text, start, end);
136+
setInput(updatedText, selection);
137+
return;
138+
}
134139
const markdown = MARKDOWN_STYLES[style];
135140
const newText = `${text.substr(0, start)}${markdown}${text.substr(start, end - start)}${markdown}${text.substr(end)}`;
136141
setInput(newText, {

app/containers/MessageComposer/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './forceJpgExtension';
33
export * from './getMentionRegexp';
44
export * from './prepareQuoteMessage';
55
export * from './insertEmojiAtCursor';
6+
export * from './wrapCodeBlock';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { wrapCodeBlock } from './wrapCodeBlock';
2+
3+
describe('wrapCodeBlock', () => {
4+
it('inserts an empty fenced block with the cursor on the middle line', () => {
5+
expect(wrapCodeBlock('', 0, 0)).toEqual({
6+
updatedText: '```\n\n```',
7+
selection: { start: 4, end: 4 }
8+
});
9+
});
10+
11+
it('adds a leading newline when the cursor is not at the start of a line', () => {
12+
expect(wrapCodeBlock('comment', 7, 7)).toEqual({
13+
updatedText: 'comment\n```\n\n```',
14+
selection: { start: 12, end: 12 }
15+
});
16+
});
17+
18+
it('does not add a leading newline when already at the start of a line', () => {
19+
expect(wrapCodeBlock('comment\n', 8, 8)).toEqual({
20+
updatedText: 'comment\n```\n\n```',
21+
selection: { start: 12, end: 12 }
22+
});
23+
});
24+
25+
it('wraps the selected text inside the block and keeps it selected', () => {
26+
expect(wrapCodeBlock('comment\nfoo', 8, 11)).toEqual({
27+
updatedText: 'comment\n```\nfoo\n```',
28+
selection: { start: 12, end: 15 }
29+
});
30+
});
31+
32+
it('adds a trailing newline so the closing fence stays on its own line', () => {
33+
expect(wrapCodeBlock('abc', 0, 0)).toEqual({
34+
updatedText: '```\n\n```\nabc',
35+
selection: { start: 4, end: 4 }
36+
});
37+
});
38+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Wraps the current selection (or the cursor when nothing is selected) in a fenced code block.
2+
// Fenced code blocks are only parsed when the ``` markers sit alone on their own line, so the
3+
// opening and closing fences are placed on dedicated lines and separated from the surrounding
4+
// text. The cursor is left on the (possibly empty) content line between the fences.
5+
// See https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/7099
6+
export const wrapCodeBlock = (
7+
text: string,
8+
start: number,
9+
end: number
10+
): { updatedText: string; selection: { start: number; end: number } } => {
11+
const before = text.substring(0, start);
12+
const selected = text.substring(start, end);
13+
const after = text.substring(end);
14+
15+
const prefix = before.length > 0 && !before.endsWith('\n') ? '\n```\n' : '```\n';
16+
const suffix = after.length > 0 && !after.startsWith('\n') ? '\n```\n' : '\n```';
17+
18+
const updatedText = `${before}${prefix}${selected}${suffix}${after}`;
19+
const cursor = before.length + prefix.length;
20+
21+
return {
22+
updatedText,
23+
selection: { start: cursor, end: cursor + selected.length }
24+
};
25+
};

0 commit comments

Comments
 (0)