Markdown toolbar - update new line logic for block syntaxes (#15343)
* update new line logic for block syntaxes * use padStart
This commit is contained in:
parent
7ba0b49ea8
commit
460d2f433a
4 changed files with 724 additions and 27 deletions
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue