Markdown toolbar - update new line logic for block syntaxes (#15343)

* update new line logic for block syntaxes

* use padStart
This commit is contained in:
Suzanne Aitchison 2021-11-19 06:59:21 +00:00 committed by GitHub
parent 7ba0b49ea8
commit 460d2f433a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 724 additions and 27 deletions

View file

@ -914,7 +914,7 @@ describe('markdownSyntaxFormatters', () => {
it('formats multiple lines of text as an ordered list', () => {
const textAreaValue = 'one\ntwo\nthree';
const expectedNewTextAreaValue = '\n\n1. one\n2. two\n3. three\n';
const expectedNewTextAreaValue = '1. one\n2. two\n3. three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
@ -998,7 +998,7 @@ describe('markdownSyntaxFormatters', () => {
it("formats as an ordered list if at least one line of selection doesn't match ordered list format", () => {
const textAreaValue = '1. one\ntwo\n3. three';
const expectedNewTextAreaValue = '\n\n1. 1. one\n2. two\n3. 3. three\n';
const expectedNewTextAreaValue = '1. 1. one\n2. two\n3. 3. three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
@ -1012,6 +1012,91 @@ describe('markdownSyntaxFormatters', () => {
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. 1. one\n2. two\n3. 3. three');
});
it("doesn't add new lines before list, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '1. one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. one');
});
it('adds one new line before list, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n1. one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. one');
});
it('adds two new lines before list, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n1. two\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. two');
});
it("doesn't add a new line after list if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '1. one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. one');
});
it('adds a new line after list if none exists', () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '1. one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['orderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('1. one');
});
});
describe('unorderedList', () => {
@ -1034,7 +1119,7 @@ describe('markdownSyntaxFormatters', () => {
it('formats multiple lines of text as an unordered list', () => {
const textAreaValue = 'one\ntwo\nthree';
const expectedNewTextAreaValue = '\n\n- one\n- two\n- three\n';
const expectedNewTextAreaValue = '- one\n- two\n- three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
@ -1118,7 +1203,7 @@ describe('markdownSyntaxFormatters', () => {
it("formats as an unordered list if at least one line of selection doesn't match unordered list format", () => {
const textAreaValue = '- one\ntwo\n- three';
const expectedNewTextAreaValue = '\n\n- - one\n- two\n- - three\n';
const expectedNewTextAreaValue = '- - one\n- two\n- - three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
@ -1132,6 +1217,91 @@ describe('markdownSyntaxFormatters', () => {
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- - one\n- two\n- - three');
});
it("doesn't add new lines before list, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '- one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- one');
});
it('adds one new line before list, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n- one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- one');
});
it('adds two new lines before list, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n- two\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- two');
});
it("doesn't add a new line after list if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '- one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- one');
});
it('adds a new line after list if none exists', () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '- one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['unorderedList'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('- one');
});
});
describe('heading', () => {
@ -1262,6 +1432,91 @@ describe('markdownSyntaxFormatters', () => {
expect(newCursorStart).toEqual(5);
expect(newCursorEnd).toEqual(8);
});
it("doesn't add new lines before heading, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '## one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['heading'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds one new line before heading, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n## one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['heading'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds two new lines before heading, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n## two\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['heading'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('two');
});
it("doesn't add a new line after heading if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '## one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['heading'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds a new line after heading if none exists', () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '## one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['heading'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
});
describe('quote', () => {
@ -1284,7 +1539,7 @@ describe('markdownSyntaxFormatters', () => {
it('formats multiple lines of text as a quote', () => {
const textAreaValue = 'one\ntwo\nthree';
const expectedNewTextAreaValue = '\n\n> one\n> two\n> three\n';
const expectedNewTextAreaValue = '> one\n> two\n> three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
@ -1368,7 +1623,7 @@ describe('markdownSyntaxFormatters', () => {
it("formats as a quote if at least one line of selection doesn't match quote format", () => {
const textAreaValue = '> one\ntwo\n> three';
const expectedNewTextAreaValue = '\n\n> > one\n> two\n> > three\n';
const expectedNewTextAreaValue = '> > one\n> two\n> > three\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
@ -1382,6 +1637,91 @@ describe('markdownSyntaxFormatters', () => {
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> > one\n> two\n> > three');
});
it("doesn't add new lines before quote, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '> one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> one');
});
it('adds one new line before quote, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n> one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> one');
});
it('adds two new lines before quote, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n> two\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> two');
});
it("doesn't add a new line after quote if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '> one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> one');
});
it('adds a new line after quote if none exists', () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '> one\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['quote'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('> one');
});
});
describe('codeBlock', () => {
@ -1404,7 +1744,7 @@ describe('markdownSyntaxFormatters', () => {
it('formats multiple lines of text as a code block', () => {
const textAreaValue = 'one\ntwo\nthree';
const expectedNewTextAreaValue = '\n\n```\none\ntwo\nthree\n```\n';
const expectedNewTextAreaValue = '```\none\ntwo\nthree\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
@ -1503,6 +1843,91 @@ describe('markdownSyntaxFormatters', () => {
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('two');
});
it("doesn't add new lines before code block, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '```\none\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds one new line before code block, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n```\none\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds two new lines before code block, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n```\ntwo\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('two');
});
it("doesn't add a new line after code block if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '```\none\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds a new line after code block if none exists', () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '```\none\n```\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
coreSyntaxFormatters['codeBlock'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
});
describe('divider', () => {
@ -1571,6 +1996,74 @@ describe('markdownSyntaxFormatters', () => {
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('two');
});
it("doesn't add new lines before divider, if at the beginning of text area", () => {
const textAreaValue = 'one';
const expectedNewTextAreaValue = '---\none\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
secondarySyntaxFormatters['divider'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds one new line before divider, if directly preceded by a single new line', () => {
const textAreaValue = '\none';
const expectedNewTextAreaValue = '\n\n---\none\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
secondarySyntaxFormatters['divider'].getFormatting({
value: textAreaValue,
selectionStart: 1,
selectionEnd: 4,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
it('adds two new lines before divider, if no new lines already exist before it', () => {
const textAreaValue = 'one two';
const expectedNewTextAreaValue = 'one \n\n---\ntwo\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
secondarySyntaxFormatters['divider'].getFormatting({
value: textAreaValue,
selectionStart: 4,
selectionEnd: 7,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('two');
});
it("doesn't add a new line after divider if one already exists", () => {
const textAreaValue = 'one\n';
const expectedNewTextAreaValue = '---\none\n';
const { newTextAreaValue, newCursorStart, newCursorEnd } =
secondarySyntaxFormatters['divider'].getFormatting({
value: textAreaValue,
selectionStart: 0,
selectionEnd: 3,
});
expect(newTextAreaValue).toEqual(expectedNewTextAreaValue);
expect(
newTextAreaValue.substring(newCursorStart, newCursorEnd),
).toEqual('one');
});
});
});
});

View file

@ -2,6 +2,8 @@
import {
getLastIndexOfCharacter,
getNextIndexOfCharacter,
getNumberOfNewLinesFollowingSelection,
getNumberOfNewLinesPrecedingSelection,
getSelectionData,
} from '../../utilities/textAreaUtils';
import {
@ -24,6 +26,37 @@ const MARKDOWN_LINK_REGEX =
/^\[([\w\s\d]*)\]\((url|(https?:\/\/[\w\d./?=#]+))\)$/;
const URL_PLACEHOLDER_TEXT = 'url';
const NUMBER_OF_NEW_LINES_BEFORE_BLOCK_SYNTAX = 2;
const NUMBER_OF_NEW_LINES_BEFORE_AFTER_SYNTAX = 1;
const getNewLinePrefixSuffixes = ({ selectionStart, selectionEnd, value }) => {
const numberOfNewLinesBeforeSelection = getNumberOfNewLinesPrecedingSelection(
{ selectionStart, value },
);
const numberOfNewLinesFollowingSelection =
getNumberOfNewLinesFollowingSelection({ selectionEnd, value });
// We only add new lines if we're not at the beginning of the text area
const numberOfNewLinesNeededAtStart =
selectionStart === 0
? 0
: NUMBER_OF_NEW_LINES_BEFORE_BLOCK_SYNTAX -
numberOfNewLinesBeforeSelection;
const newLinesPrefix = String.prototype.padStart(
numberOfNewLinesNeededAtStart,
'\n',
);
const newLinesSuffix =
numberOfNewLinesFollowingSelection >=
NUMBER_OF_NEW_LINES_BEFORE_AFTER_SYNTAX
? ''
: '\n';
return { newLinesPrefix, newLinesSuffix };
};
const handleLinkFormattingForEmptyTextSelection = ({
textBeforeSelection,
textAfterSelection,
@ -316,18 +349,18 @@ const undoOrAddFormattingForMultilineSyntax = ({
}
// Add the formatting
const numberOfNewLinesBeforeSelection = (
textBeforeSelection.slice(-2).match(/\n/g) || []
).length;
const { newLinesPrefix, newLinesSuffix } = getNewLinePrefixSuffixes({
selectionStart,
selectionEnd,
value,
});
const { length: newLinePrefixLength } = newLinesPrefix;
// Multiline insertions should occur after two new lines (whether added already by user or inserted automatically)
const newLinesToAddBeforeSelection = 2 - numberOfNewLinesBeforeSelection;
let newtextBeforeSelection = textBeforeSelection;
Array.from({ length: newLinesToAddBeforeSelection }, () => {
newtextBeforeSelection += '\n';
});
const newtextBeforeSelection = `${textBeforeSelection}${newLinesPrefix}`;
const cursorStartBaseline = selectionStart + newLinesToAddBeforeSelection;
const cursorStartBaseline = selectionStart + newLinePrefixLength;
const cursorStartBlockPrefixOffset = blockPrefix ? blockPrefix.length : 0;
const cursorStartLinePrefixOffset =
selectedText === '' && linePrefix ? linePrefix.length : 0;
@ -335,7 +368,9 @@ const undoOrAddFormattingForMultilineSyntax = ({
return {
newTextAreaValue: `${newtextBeforeSelection}${
blockPrefix ? blockPrefix : ''
}${formattedText}${blockSuffix ? blockSuffix : ''}\n${textAfterSelection}`,
}${formattedText}${
blockSuffix ? blockSuffix : ''
}${newLinesSuffix}${textAfterSelection}`,
newCursorStart:
cursorStartBaseline +
cursorStartBlockPrefixOffset +
@ -344,7 +379,7 @@ const undoOrAddFormattingForMultilineSyntax = ({
selectionEnd +
formattedText.length -
selectedText.length +
newLinesToAddBeforeSelection +
newLinePrefixLength +
(blockPrefix?.length || 0),
};
};
@ -453,6 +488,14 @@ export const coreSyntaxFormatters = {
const { selectedText, textBeforeSelection, textAfterSelection } =
getSelectionData({ selectionStart, selectionEnd, value });
const { newLinesPrefix, newLinesSuffix } = getNewLinePrefixSuffixes({
selectionStart,
selectionEnd,
value,
});
const { length: newLinePrefixLength } = newLinesPrefix;
const { length: newLineSuffixLength } = newLinesSuffix;
if (selectedText === '' && textBeforeSelection !== '') {
// Check start of line for whether we're in an empty ordered list
const lastNewLine = getLastIndexOfCharacter({
@ -479,9 +522,9 @@ export const coreSyntaxFormatters = {
if (selectedText === '') {
// Otherwise insert an empty list for an empty selection
return {
newTextAreaValue: `${textBeforeSelection}\n\n1. \n${textAfterSelection}`,
newCursorStart: selectionStart + 5,
newCursorEnd: selectionEnd + 5,
newTextAreaValue: `${textBeforeSelection}${newLinesPrefix}1. ${newLinesSuffix}${textAfterSelection}`,
newCursorStart: selectionStart + 3 + newLinePrefixLength,
newCursorEnd: selectionEnd + 3 + newLinePrefixLength,
};
}
@ -508,15 +551,18 @@ export const coreSyntaxFormatters = {
};
}
// Otherwise convert to an ordered list
const formattedList = `\n\n${splitByNewLine
const formattedList = `${newLinesPrefix}${splitByNewLine
.map((textChunk, index) => `${index + 1}. ${textChunk}`)
.join('\n')}\n`;
.join('\n')}${newLinesSuffix}`;
const cursorOffsetStart =
selectedText.length === 0 ? 4 : newLinePrefixLength;
return {
newTextAreaValue: `${textBeforeSelection}${formattedList}${textAfterSelection}`,
newCursorStart: selectionStart + (selectedText.length === 0 ? 4 : 2),
newCursorStart: selectionStart + cursorOffsetStart,
newCursorEnd:
selectionEnd + formattedList.length - selectedText.length - 1,
selectionStart + formattedList.length - newLineSuffixLength,
};
},
},
@ -578,13 +624,20 @@ export const coreSyntaxFormatters = {
};
}
const { newLinesPrefix, newLinesSuffix } = getNewLinePrefixSuffixes({
selectionStart,
selectionEnd,
value,
});
const { length: newLinePrefixLength } = newLinesPrefix;
const adjustingHeading = currentHeadingIndex > 0;
const cursorOffset = adjustingHeading ? 1 : 5;
const cursorOffset = adjustingHeading ? 1 : 3 + newLinePrefixLength;
return {
newTextAreaValue: adjustingHeading
? `${textBeforeSelection}#${selectedText}${textAfterSelection}`
: `${textBeforeSelection}\n\n## ${selectedText}\n${textAfterSelection}`,
: `${textBeforeSelection}${newLinesPrefix}## ${selectedText}${newLinesSuffix}${textAfterSelection}`,
newCursorStart: selectionStart + cursorOffset,
newCursorEnd: selectionEnd + cursorOffset,
};

View file

@ -3,6 +3,8 @@ import {
getLastIndexOfCharacter,
getNextIndexOfCharacter,
getSelectionData,
getNumberOfNewLinesPrecedingSelection,
getNumberOfNewLinesFollowingSelection,
} from '../textAreaUtils';
describe('getMentionWordData', () => {
@ -188,3 +190,97 @@ describe('getSelectionData', () => {
});
});
});
describe('getNumberOfNewLinesPrecedingSelection', () => {
it('returns 0 when selection start is 0', () => {
expect(
getNumberOfNewLinesPrecedingSelection({
selectionStart: 0,
value: 'some text',
}),
).toEqual(0);
});
it('returns 0 if no new lines exist before selection', () => {
expect(
getNumberOfNewLinesPrecedingSelection({
selectionStart: 9,
value: 'some text',
}),
).toEqual(0);
});
it('returns count of new lines before selection', () => {
expect(
getNumberOfNewLinesPrecedingSelection({
selectionStart: 6,
value: 'some\n\ntext',
}),
).toEqual(2);
});
it('only returns count of new lines immediately before selection', () => {
expect(
getNumberOfNewLinesPrecedingSelection({
selectionStart: 7,
value: 'some\n\ntext',
}),
).toEqual(0);
});
it('stops counting new lines as soon as any other character occurs', () => {
expect(
getNumberOfNewLinesPrecedingSelection({
selectionStart: 9,
value: '\n\n\nsome\n\ntext',
}),
).toEqual(2);
});
});
describe('getNumberOfNewLinesFollowingSelection', () => {
it('returns 0 when selection end is end of text area value', () => {
expect(
getNumberOfNewLinesFollowingSelection({
selectionEnd: 9,
value: 'some text',
}),
).toEqual(0);
});
it('returns 0 if no new lines exist after selection', () => {
expect(
getNumberOfNewLinesFollowingSelection({
selectionEnd: 1,
value: 'some text',
}),
).toEqual(0);
});
it('returns count of new lines after selection', () => {
expect(
getNumberOfNewLinesFollowingSelection({
selectionEnd: 4,
value: 'some\n\ntext',
}),
).toEqual(2);
});
it('only returns count of new lines immediately after selection', () => {
expect(
getNumberOfNewLinesFollowingSelection({
selectionEnd: 1,
value: 'some\n\ntext',
}),
).toEqual(0);
});
it('stops counting new lines as soon as any other character occurs', () => {
expect(
getNumberOfNewLinesFollowingSelection({
selectionEnd: 0,
value: '\n\n\nsome\n\ntext',
}),
).toEqual(3);
});
});

View file

@ -175,6 +175,61 @@ export const getNextIndexOfCharacter = ({
return -1;
};
/**
* Counts how many new lines come immediately before the user's current selection start
* @param {object} args
* @param {number} args.selectionStart The index of user's current selection start
* @param {string} args.value The value of the textarea
*
* @returns {number} Number of new lines directly before selection start
*/
export const getNumberOfNewLinesPrecedingSelection = ({
selectionStart,
value,
}) => {
if (selectionStart === 0) {
return 0;
}
let count = 0;
let searchIndex = selectionStart - 1;
while (searchIndex >= 0 && value.charAt(searchIndex) === '\n') {
count++;
searchIndex--;
}
return count;
};
/**
* Counts how many new lines come immediately after the user's current selection end
*
* @param {object} args
* @param {number} args.selectionEnd The index of user's current selection end
* @param {string} args.value The value of the textarea
*
* @returns {number} the count of new line characters immediately following selection
*/
export const getNumberOfNewLinesFollowingSelection = ({
selectionEnd,
value,
}) => {
if (selectionEnd === value.length) {
return 0;
}
let count = 0;
let searchIndex = selectionEnd;
while (searchIndex < value.length && value.charAt(searchIndex) === '\n') {
count++;
searchIndex++;
}
return count;
};
/**
* Retrieve data about the user's current text selection
*