|
37 | 37 | }); |
38 | 38 | turndownService.escape = (string) => string; |
39 | 39 |
|
| 40 | + // Turndown serializes each <br> as a Markdown " \n" hard break, leaving |
| 41 | + // invisible trailing spaces at line breaks and on blank lines. Strip trailing |
| 42 | + // whitespace from every line, including inside fenced code blocks. markdownlint |
| 43 | + // (MD009) preserves it there for whitespace-sensitive languages, but a chat |
| 44 | + // message is not source code, so trimming uniformly is simpler and safe here. |
| 45 | + const stripTrailingWhitespace = (md: string): string => md.replace(/[ \t]+$/gm, ''); |
| 46 | +
|
40 | 47 | // Produce single newlines between paragraphs instead of double. |
41 | 48 | // TipTap wraps every line in <p> tags; the default Turndown rule emits |
42 | 49 | // \n\n around each paragraph which then required a destructive |
|
711 | 718 | try { |
712 | 719 | // Try parsing the value |
713 | 720 | return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), { |
714 | | - breaks: false |
| 721 | + breaks: true |
715 | 722 | }); |
716 | 723 | } catch (error) { |
717 | 724 | // If no attempts remain, fallback to plain text |
|
885 | 892 | jsonValue = editor.getJSON(); |
886 | 893 |
|
887 | 894 | if (richText) { |
888 | | - mdValue = turndownService |
889 | | - .turndown( |
890 | | - htmlValue |
891 | | - .replace(/<p><\/p>/g, '<br/>') |
892 | | - .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
893 | | - ) |
894 | | - .replace(/\u00a0/g, ' '); |
| 895 | + mdValue = stripTrailingWhitespace( |
| 896 | + turndownService |
| 897 | + .turndown( |
| 898 | + htmlValue |
| 899 | + .replace(/<p><\/p>/g, '<br/>') |
| 900 | + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
| 901 | + ) |
| 902 | + .replace(/\u00a0/g, ' ') |
| 903 | + ); |
895 | 904 | } else { |
896 | | - mdValue = turndownService |
897 | | - .turndown( |
898 | | - htmlValue |
899 | | - // Replace empty paragraphs with line breaks |
900 | | - .replace(/<p><\/p>/g, '<br/>') |
901 | | - // Replace multiple spaces with non-breaking spaces |
902 | | - .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
903 | | - // Replace tabs with non-breaking spaces (preserve indentation) |
904 | | - .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces |
905 | | - ) |
906 | | - // Convert non-breaking spaces back to regular spaces for markdown |
907 | | - .replace(/\u00a0/g, ' '); |
| 905 | + mdValue = stripTrailingWhitespace( |
| 906 | + turndownService |
| 907 | + .turndown( |
| 908 | + htmlValue |
| 909 | + // Replace empty paragraphs with line breaks |
| 910 | + .replace(/<p><\/p>/g, '<br/>') |
| 911 | + // Replace multiple spaces with non-breaking spaces |
| 912 | + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
| 913 | + // Replace tabs with non-breaking spaces (preserve indentation) |
| 914 | + .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces |
| 915 | + ) |
| 916 | + // Convert non-breaking spaces back to regular spaces for markdown |
| 917 | + .replace(/\u00a0/g, ' ') |
| 918 | + ); |
908 | 919 | } |
909 | 920 |
|
910 | 921 | onChange({ |
|
1265 | 1276 |
|
1266 | 1277 | const jsonValue = editor.getJSON(); |
1267 | 1278 | const htmlValue = editor.getHTML(); |
1268 | | - let mdValue = turndownService |
1269 | | - .turndown( |
1270 | | - (preserveBreaks ? htmlValue.replace(/<p><\/p>/g, '<br/>') : htmlValue).replace( |
1271 | | - / {2,}/g, |
1272 | | - (m) => m.replace(/ /g, '\u00a0') |
| 1279 | + let mdValue = stripTrailingWhitespace( |
| 1280 | + turndownService |
| 1281 | + .turndown( |
| 1282 | + (preserveBreaks ? htmlValue.replace(/<p><\/p>/g, '<br/>') : htmlValue).replace( |
| 1283 | + / {2,}/g, |
| 1284 | + (m) => m.replace(/ /g, '\u00a0') |
| 1285 | + ) |
1273 | 1286 | ) |
1274 | | - ) |
1275 | | - .replace(/\u00a0/g, ' '); |
| 1287 | + .replace(/\u00a0/g, ' ') |
| 1288 | + ); |
1276 | 1289 |
|
1277 | 1290 | if (value === '') { |
1278 | 1291 | editor.commands.clearContent(); // Clear content if value is empty |
|
1298 | 1311 | preserveBreaks |
1299 | 1312 | ? value |
1300 | 1313 | : marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), { |
1301 | | - breaks: false |
| 1314 | + breaks: true |
1302 | 1315 | }) |
1303 | 1316 | ); |
1304 | 1317 |
|
|
0 commit comments