|
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 that |
| 42 | + // trailing whitespace, except inside fenced code blocks where it can be significant. |
| 43 | + const stripTrailingWhitespace = (md: string): string => |
| 44 | + md |
| 45 | + .split(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g) |
| 46 | + .map((part, i) => (i % 2 ? part : part.replace(/[ \t]+$/gm, ''))) |
| 47 | + .join(''); |
| 48 | +
|
40 | 49 | // Produce single newlines between paragraphs instead of double. |
41 | 50 | // TipTap wraps every line in <p> tags; the default Turndown rule emits |
42 | 51 | // \n\n around each paragraph which then required a destructive |
|
711 | 720 | try { |
712 | 721 | // Try parsing the value |
713 | 722 | return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), { |
714 | | - breaks: false |
| 723 | + breaks: true |
715 | 724 | }); |
716 | 725 | } catch (error) { |
717 | 726 | // If no attempts remain, fallback to plain text |
|
885 | 894 | jsonValue = editor.getJSON(); |
886 | 895 |
|
887 | 896 | 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, ' '); |
| 897 | + mdValue = stripTrailingWhitespace( |
| 898 | + turndownService |
| 899 | + .turndown( |
| 900 | + htmlValue |
| 901 | + .replace(/<p><\/p>/g, '<br/>') |
| 902 | + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
| 903 | + ) |
| 904 | + .replace(/\u00a0/g, ' ') |
| 905 | + ); |
895 | 906 | } 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, ' '); |
| 907 | + mdValue = stripTrailingWhitespace( |
| 908 | + turndownService |
| 909 | + .turndown( |
| 910 | + htmlValue |
| 911 | + // Replace empty paragraphs with line breaks |
| 912 | + .replace(/<p><\/p>/g, '<br/>') |
| 913 | + // Replace multiple spaces with non-breaking spaces |
| 914 | + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) |
| 915 | + // Replace tabs with non-breaking spaces (preserve indentation) |
| 916 | + .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces |
| 917 | + ) |
| 918 | + // Convert non-breaking spaces back to regular spaces for markdown |
| 919 | + .replace(/\u00a0/g, ' ') |
| 920 | + ); |
908 | 921 | } |
909 | 922 |
|
910 | 923 | onChange({ |
|
1267 | 1280 |
|
1268 | 1281 | const jsonValue = editor.getJSON(); |
1269 | 1282 | const htmlValue = editor.getHTML(); |
1270 | | - let mdValue = turndownService |
1271 | | - .turndown( |
1272 | | - (preserveBreaks ? htmlValue.replace(/<p><\/p>/g, '<br/>') : htmlValue).replace( |
1273 | | - / {2,}/g, |
1274 | | - (m) => m.replace(/ /g, '\u00a0') |
| 1283 | + let mdValue = stripTrailingWhitespace( |
| 1284 | + turndownService |
| 1285 | + .turndown( |
| 1286 | + (preserveBreaks ? htmlValue.replace(/<p><\/p>/g, '<br/>') : htmlValue).replace( |
| 1287 | + / {2,}/g, |
| 1288 | + (m) => m.replace(/ /g, '\u00a0') |
| 1289 | + ) |
1275 | 1290 | ) |
1276 | | - ) |
1277 | | - .replace(/\u00a0/g, ' '); |
| 1291 | + .replace(/\u00a0/g, ' ') |
| 1292 | + ); |
1278 | 1293 |
|
1279 | 1294 | if (value === '') { |
1280 | 1295 | editor.commands.clearContent(); // Clear content if value is empty |
|
1300 | 1315 | preserveBreaks |
1301 | 1316 | ? value |
1302 | 1317 | : marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), { |
1303 | | - breaks: false |
| 1318 | + breaks: true |
1304 | 1319 | }) |
1305 | 1320 | ); |
1306 | 1321 |
|
|
0 commit comments