1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-01-20 16:50:28 -05:00

Leave list/quote expanison with double enter

When editing a list or similar syntax elements, pressing enter starts a
new line with the line introducer (e.g. `- ` for a plain list).
But currently it's uncomfortable when someone wants to leave the list.
Pressing enter again simply adds more and more lines with the prefix.

With this change the list is terminated if enter is pressed on a line
which contains the introducer but nothing else. This behavior is known
from other markdown editors like the on used by GitLab or GitHub.

Additionally I changed the regex for detecting a prefix.
- Why: With the change you can add a single whitespace at the end if you
  want to keep an "empty" line. So if you want to write:
  ```
  - First
  -
  - Third
  ```
  You just need to add a whitespace in the second line to prevent that
  the prefix will be removed.
- Changes in detail:
  - ordered bullet list prefix detection:
    nothing changed
  - todo list and unordered list prefix detection:
    have been split up:
    - todo list: Changed that only 1 to 4 whitespaces can be between the
      list char (`-`,`*`,`+`) and the checkbox (`[ ]`,`[x]`) - Why? If
      more then 4 spaces are between the list char and the checkbox,
      this is no longer detected as a prefix for a todo item based on
      the markdown standard. Due to the amount of spaces it is instead
      parsed as code.
    - unordered list: The prefix now needs to have exactly one space
      after the list char (`-`,`*`,`+`). More spaces will not be taken
      into account for detecting the prefix.
  - quote prefix detection:
    nothing changed

The current e2e-tests where simplified and duplicated tests where
removed. Test cases for the new functionality where added.

(cherry picked from commit 7ea62c5ce4)
This commit is contained in:
Beowulf 2025-01-17 18:42:42 +01:00 committed by forgejo-backport-action
parent 627634a76e
commit 348e0e1fac
2 changed files with 43 additions and 30 deletions

View file

@ -109,7 +109,7 @@ test('markdown indentation', async ({page}) => {
});
test('markdown list continuation', async ({page}) => {
const initText = `* first\n* second\n* third\n* last`;
const initText = `* first\n* second`;
const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200);
@ -119,25 +119,20 @@ test('markdown list continuation', async ({page}) => {
const indent = page.locator('button[data-md-action="indent"]');
await textarea.fill(initText);
// Test continuation of '* ' prefix
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
// Test continuation of ' * ' prefix
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('rst'), it.value.indexOf('rst')));
await indent.click();
await textarea.press('End');
await textarea.press('Enter');
await textarea.pressSequentially('middle');
await expect(textarea).toHaveValue(`* first\n* second\n* middle\n* third\n* last`);
// Test continuation of ' * ' prefix
await indent.click();
await textarea.press('Enter');
await textarea.pressSequentially('muddle');
await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* muddle\n* third\n* last`);
await expect(textarea).toHaveValue(`${tab}* first\n${tab}* muddle\n* second`);
// Test breaking in the middle of a line
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.lastIndexOf('ddle'), it.value.lastIndexOf('ddle')));
await textarea.pressSequentially('tate');
await textarea.press('Enter');
await textarea.pressSequentially('me');
await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* mutate\n${tab}* meddle\n* third\n* last`);
await expect(textarea).toHaveValue(`${tab}* first\n${tab}* mutate\n${tab}* meddle\n* second`);
// Test not triggering when Shift held
await textarea.fill(initText);
@ -145,35 +140,36 @@ test('markdown list continuation', async ({page}) => {
await textarea.press('Shift+Enter');
await textarea.press('Enter');
await textarea.pressSequentially('...but not least');
await expect(textarea).toHaveValue(`* first\n* second\n* third\n* last\n\n...but not least`);
await expect(textarea).toHaveValue(`* first\n* second\n\n...but not least`);
// Test continuation of ordered list
await textarea.fill(`1. one\n2. two`);
await textarea.fill(`1. one`);
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially(' ');
await textarea.press('Enter');
await textarea.pressSequentially('three');
await expect(textarea).toHaveValue(`1. one\n2. two\n3. three`);
await textarea.press('Enter');
await textarea.press('Enter');
await expect(textarea).toHaveValue(`1. one\n2. \n3. three\n\n`);
// Test continuation of alternative ordered list syntax
await textarea.fill(`1) one\n2) two`);
await textarea.fill(`1) one`);
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially(' ');
await textarea.press('Enter');
await textarea.pressSequentially('three');
await expect(textarea).toHaveValue(`1) one\n2) two\n3) three`);
// Test continuation of blockquote
await textarea.fill(`> knowledge is power`);
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('france is bacon');
await expect(textarea).toHaveValue(`> knowledge is power\n> france is bacon`);
await textarea.press('Enter');
await expect(textarea).toHaveValue(`1) one\n2) \n3) three\n\n`);
// Test continuation of checklists
await textarea.fill(`- [ ] have a problem\n- [x] create a solution`);
await textarea.fill(`- [ ]have a problem\n- [x]create a solution`);
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('write a test');
await expect(textarea).toHaveValue(`- [ ] have a problem\n- [x] create a solution\n- [ ] write a test`);
await expect(textarea).toHaveValue(`- [ ]have a problem\n- [x]create a solution\n- [ ]write a test`);
// Test all conceivable syntax (except ordered lists)
const prefixes = [
@ -189,7 +185,6 @@ test('markdown list continuation', async ({page}) => {
'> ',
'> > ',
'- [ ] ',
'- [ ]', // This does seem to render, so allow.
'* [ ] ',
'+ [ ] ',
];
@ -197,8 +192,12 @@ test('markdown list continuation', async ({page}) => {
await textarea.fill(`${prefix}one`);
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially(' ');
await textarea.press('Enter');
await textarea.pressSequentially('two');
await expect(textarea).toHaveValue(`${prefix}one\n${prefix}two`);
await textarea.press('Enter');
await textarea.press('Enter');
await expect(textarea).toHaveValue(`${prefix}one\n${prefix} \n${prefix}two\n\n`);
}
});

View file

@ -409,13 +409,27 @@ class ComboMarkdownEditor {
// Find the beginning of the current line.
const lineStart = Math.max(0, value.lastIndexOf('\n', start - 1) + 1);
// Find the end and extract the line.
const lineEnd = value.indexOf('\n', start);
const line = value.slice(lineStart, lineEnd === -1 ? value.length : lineEnd);
const nextLF = value.indexOf('\n', start);
const lineEnd = nextLF === -1 ? value.length : nextLF;
const line = value.slice(lineStart, lineEnd);
// Match any whitespace at the start + any repeatable prefix + exactly one space after.
const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s+(\[[ x]\]\s?)?|(>\s+)+)?/);
const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s{1,4}\[[ x]\]\s?|[-*+]\s|(>\s?)+)?/);
// Defer to browser if we can't do anything more useful, or if the cursor is inside the prefix.
if (!prefix || !prefix[0].length || lineStart + prefix[0].length > start) return false;
if (!prefix) return false;
const prefixLength = prefix[0].length;
if (!prefixLength || lineStart + prefixLength > start) return false;
// If the prefix is just indentation (which should always be an even number of spaces or tabs), check if a single whitespace is added to the end of the line.
// If this is the case do not leave the indentation and continue with the prefix.
if ((prefixLength % 2 === 1 && /^ +$/.test(prefix[0])) || /^\t+ $/.test(prefix[0])) {
prefix[0] = prefix[0].slice(0, prefixLength - 1);
} else if (prefixLength === lineEnd - lineStart) {
this.textarea.setSelectionRange(lineStart, lineEnd);
if (!document.execCommand('insertText', false, '\n')) {
this.textarea.setRangeText('\n');
}
return true;
}
// Insert newline + prefix.
let text = `\n${prefix[0]}`;