loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix mention and emoji expansion & Improve leaving list completion (#6597)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6597
Reviewed-by: 0ko <0ko@noreply.codeberg.org>

0ko 0379739a 5be7c6cb

+70 -29
+50 -25
tests/e2e/markdown-editor.test.e2e.ts
··· 109 109 }); 110 110 111 111 test('markdown list continuation', async ({page}) => { 112 - const initText = `* first\n* second\n* third\n* last`; 112 + const initText = `* first\n* second`; 113 113 114 114 const response = await page.goto('/user2/repo1/issues/new'); 115 115 expect(response?.status()).toBe(200); ··· 119 119 const indent = page.locator('button[data-md-action="indent"]'); 120 120 await textarea.fill(initText); 121 121 122 - // Test continuation of '* ' prefix 123 - await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond'))); 124 - await textarea.press('End'); 125 - await textarea.press('Enter'); 126 - await textarea.pressSequentially('middle'); 127 - await expect(textarea).toHaveValue(`* first\n* second\n* middle\n* third\n* last`); 128 - 129 122 // Test continuation of ' * ' prefix 123 + await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('rst'), it.value.indexOf('rst'))); 130 124 await indent.click(); 125 + await textarea.press('End'); 131 126 await textarea.press('Enter'); 132 127 await textarea.pressSequentially('muddle'); 133 - await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* muddle\n* third\n* last`); 128 + await expect(textarea).toHaveValue(`${tab}* first\n${tab}* muddle\n* second`); 134 129 135 130 // Test breaking in the middle of a line 136 131 await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.lastIndexOf('ddle'), it.value.lastIndexOf('ddle'))); 137 132 await textarea.pressSequentially('tate'); 138 133 await textarea.press('Enter'); 139 134 await textarea.pressSequentially('me'); 140 - await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* mutate\n${tab}* meddle\n* third\n* last`); 135 + await expect(textarea).toHaveValue(`${tab}* first\n${tab}* mutate\n${tab}* meddle\n* second`); 141 136 142 137 // Test not triggering when Shift held 143 138 await textarea.fill(initText); ··· 145 140 await textarea.press('Shift+Enter'); 146 141 await textarea.press('Enter'); 147 142 await textarea.pressSequentially('...but not least'); 148 - await expect(textarea).toHaveValue(`* first\n* second\n* third\n* last\n\n...but not least`); 143 + await expect(textarea).toHaveValue(`* first\n* second\n\n...but not least`); 149 144 150 145 // Test continuation of ordered list 151 - await textarea.fill(`1. one\n2. two`); 146 + await textarea.fill(`1. one`); 152 147 await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); 153 148 await textarea.press('Enter'); 149 + await textarea.pressSequentially(' '); 150 + await textarea.press('Enter'); 154 151 await textarea.pressSequentially('three'); 155 - await expect(textarea).toHaveValue(`1. one\n2. two\n3. three`); 152 + await textarea.press('Enter'); 153 + await textarea.press('Enter'); 154 + await expect(textarea).toHaveValue(`1. one\n2. \n3. three\n\n`); 156 155 157 156 // Test continuation of alternative ordered list syntax 158 - await textarea.fill(`1) one\n2) two`); 157 + await textarea.fill(`1) one`); 159 158 await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); 159 + await textarea.press('Enter'); 160 + await textarea.pressSequentially(' '); 160 161 await textarea.press('Enter'); 161 162 await textarea.pressSequentially('three'); 162 - await expect(textarea).toHaveValue(`1) one\n2) two\n3) three`); 163 - 164 - // Test continuation of blockquote 165 - await textarea.fill(`> knowledge is power`); 166 - await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); 163 + await textarea.press('Enter'); 167 164 await textarea.press('Enter'); 168 - await textarea.pressSequentially('france is bacon'); 169 - await expect(textarea).toHaveValue(`> knowledge is power\n> france is bacon`); 165 + await expect(textarea).toHaveValue(`1) one\n2) \n3) three\n\n`); 170 166 171 167 // Test continuation of checklists 172 - await textarea.fill(`- [ ] have a problem\n- [x] create a solution`); 168 + await textarea.fill(`- [ ]have a problem\n- [x]create a solution`); 173 169 await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); 174 170 await textarea.press('Enter'); 175 171 await textarea.pressSequentially('write a test'); 176 - await expect(textarea).toHaveValue(`- [ ] have a problem\n- [x] create a solution\n- [ ] write a test`); 172 + await expect(textarea).toHaveValue(`- [ ]have a problem\n- [x]create a solution\n- [ ]write a test`); 177 173 178 174 // Test all conceivable syntax (except ordered lists) 179 175 const prefixes = [ ··· 189 185 '> ', 190 186 '> > ', 191 187 '- [ ] ', 192 - '- [ ]', // This does seem to render, so allow. 193 188 '* [ ] ', 194 189 '+ [ ] ', 195 190 ]; ··· 197 192 await textarea.fill(`${prefix}one`); 198 193 await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); 199 194 await textarea.press('Enter'); 195 + await textarea.pressSequentially(' '); 196 + await textarea.press('Enter'); 200 197 await textarea.pressSequentially('two'); 201 - await expect(textarea).toHaveValue(`${prefix}one\n${prefix}two`); 198 + await textarea.press('Enter'); 199 + await textarea.press('Enter'); 200 + await expect(textarea).toHaveValue(`${prefix}one\n${prefix} \n${prefix}two\n\n`); 202 201 } 203 202 }); 204 203 ··· 224 223 await expect(textarea).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n'); 225 224 await save_visual(page); 226 225 }); 226 + 227 + test('text expander has higher prio then prefix continuation', async ({page}) => { 228 + const response = await page.goto('/user2/repo1/issues/new'); 229 + expect(response?.status()).toBe(200); 230 + 231 + const textarea = page.locator('textarea[name=content]'); 232 + const initText = `* first`; 233 + await textarea.fill(initText); 234 + await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('rst'), it.value.indexOf('rst'))); 235 + await textarea.press('End'); 236 + 237 + // Test emoji completion 238 + await textarea.press('Enter'); 239 + await textarea.pressSequentially(':smile_c'); 240 + await textarea.press('Enter'); 241 + await expect(textarea).toHaveValue(`* first\n* 😸`); 242 + 243 + // Test username completion 244 + await textarea.press('Enter'); 245 + await textarea.pressSequentially('@user'); 246 + await textarea.press('Enter'); 247 + await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 `); 248 + 249 + await textarea.press('Enter'); 250 + await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 \n* `); 251 + });
+20 -4
web_src/js/features/comp/ComboMarkdownEditor.js
··· 99 99 e.target._shiftDown = true; 100 100 } 101 101 if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.altKey) { 102 + // Prevent special line break handling if currently a text expander popup is open 103 + if (this.textarea.hasAttribute('aria-expanded')) return; 102 104 if (!this.breakLine()) return; // Nothing changed, let the default handler work. 103 105 this.options?.onContentChanged?.(this, e); 104 106 e.preventDefault(); ··· 407 409 // Find the beginning of the current line. 408 410 const lineStart = Math.max(0, value.lastIndexOf('\n', start - 1) + 1); 409 411 // Find the end and extract the line. 410 - const lineEnd = value.indexOf('\n', start); 411 - const line = value.slice(lineStart, lineEnd === -1 ? value.length : lineEnd); 412 + const nextLF = value.indexOf('\n', start); 413 + const lineEnd = nextLF === -1 ? value.length : nextLF; 414 + const line = value.slice(lineStart, lineEnd); 412 415 // Match any whitespace at the start + any repeatable prefix + exactly one space after. 413 - const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s+(\[[ x]\]\s?)?|(>\s+)+)?/); 416 + const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s{1,4}\[[ x]\]\s?|[-*+]\s|(>\s?)+)?/); 414 417 415 418 // Defer to browser if we can't do anything more useful, or if the cursor is inside the prefix. 416 - if (!prefix || !prefix[0].length || lineStart + prefix[0].length > start) return false; 419 + if (!prefix) return false; 420 + const prefixLength = prefix[0].length; 421 + if (!prefixLength || lineStart + prefixLength > start) return false; 422 + // 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. 423 + // If this is the case do not leave the indentation and continue with the prefix. 424 + if ((prefixLength % 2 === 1 && /^ +$/.test(prefix[0])) || /^\t+ $/.test(prefix[0])) { 425 + prefix[0] = prefix[0].slice(0, prefixLength - 1); 426 + } else if (prefixLength === lineEnd - lineStart) { 427 + this.textarea.setSelectionRange(lineStart, lineEnd); 428 + if (!document.execCommand('insertText', false, '\n')) { 429 + this.textarea.setRangeText('\n'); 430 + } 431 + return true; 432 + } 417 433 418 434 // Insert newline + prefix. 419 435 let text = `\n${prefix[0]}`;