Browse Source

Editor: stop runaway escaping

imwald
Nuša Pukšič 4 days ago
parent
commit
a511cdda1f
  1. 99
      assets/controllers/editor/conversion.js

99
assets/controllers/editor/conversion.js

@ -72,13 +72,55 @@ export function deltaToMarkdown(delta, opts = {}) { @@ -72,13 +72,55 @@ export function deltaToMarkdown(delta, opts = {}) {
}
};
const escapeText = (s) =>
String(s)
.replace(/\\/g, '\\\\')
.replace(/([*_`[\]~])/g, '\\$1');
const escapeText = (s) => {
// Escape special markdown characters, but don't double-escape already escaped ones
// We process character by character to handle existing escapes properly
let result = '';
const str = String(s);
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === '\\') {
// Check if next char is something that could be escaped
const next = str[i + 1];
if (next && '\\*_`[]~'.includes(next)) {
// Already escaped, keep both backslash and next char
result += '\\' + next;
i++; // skip next char
} else {
// Lone backslash, escape it
result += '\\\\';
}
} else if ('*_`[]~'.includes(char)) {
// Special char that needs escaping
result += '\\' + char;
} else {
result += char;
}
}
return result;
};
const escapeLinkText = (s) =>
String(s).replace(/\\/g, '\\\\').replace(/([\[\]])/g, '\\$1');
const escapeLinkText = (s) => {
let result = '';
const str = String(s);
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === '\\') {
const next = str[i + 1];
if (next && '\\[]'.includes(next)) {
result += '\\' + next;
i++;
} else {
result += '\\\\';
}
} else if ('[]'.includes(char)) {
result += '\\' + char;
} else {
result += char;
}
}
return result;
};
const escapeLinkUrl = (s) => String(s).replace(/\s/g, '%20');
@ -86,7 +128,22 @@ export function deltaToMarkdown(delta, opts = {}) { @@ -86,7 +128,22 @@ export function deltaToMarkdown(delta, opts = {}) {
if (!text) return '';
if (attrs.code) {
return `\`${String(text).replace(/`/g, '\\`')}\``;
let codeText = '';
const str = String(text);
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === '\\' && i + 1 < str.length && str[i + 1] === '`') {
codeText += '\\`';
i++;
} else if (char === '`') {
codeText += '\\`';
} else if (char === '\\') {
codeText += '\\\\';
} else {
codeText += char;
}
}
return `\`${codeText}\``;
}
let out = escapeText(text);
@ -339,7 +396,23 @@ function inlineMarkdownToOps(text) { @@ -339,7 +396,23 @@ function inlineMarkdownToOps(text) {
const pushText = (t) => { if (t) ops.push({ insert: t }); };
const unescapeText = (t) => t.replace(/\\([\\*_`[\]~])/g, '$1');
while (i < text.length) {
// escaped character: \X
if (text[i] === '\\' && i + 1 < text.length) {
const next = text[i + 1];
if ('\\*_`[]~'.includes(next)) {
pushText(next);
i += 2;
continue;
}
// not a recognized escape, treat backslash literally
pushText('\\');
i += 1;
continue;
}
// inline code: `...`
if (text[i] === '`') {
const end = text.indexOf('`', i + 1);
@ -358,8 +431,8 @@ function inlineMarkdownToOps(text) { @@ -358,8 +431,8 @@ function inlineMarkdownToOps(text) {
if (closeBracket !== -1 && text[closeBracket + 1] === '(') {
const closeParen = text.indexOf(')', closeBracket + 2);
if (closeParen !== -1) {
const label = text.slice(i + 1, closeBracket);
const url = text.slice(closeBracket + 2, closeParen);
const label = unescapeText(text.slice(i + 1, closeBracket));
const url = text.slice(closeBracket + 2, closeParen).replace(/%20/g, ' ');
if (label) ops.push({ insert: label, attributes: { link: url } });
i = closeParen + 1;
continue;
@ -372,7 +445,7 @@ function inlineMarkdownToOps(text) { @@ -372,7 +445,7 @@ function inlineMarkdownToOps(text) {
if (text.startsWith('**', i)) {
const end = text.indexOf('**', i + 2);
if (end !== -1) {
const content = text.slice(i + 2, end);
const content = unescapeText(text.slice(i + 2, end));
if (content) ops.push({ insert: content, attributes: { bold: true } });
i = end + 2;
continue;
@ -384,7 +457,7 @@ function inlineMarkdownToOps(text) { @@ -384,7 +457,7 @@ function inlineMarkdownToOps(text) {
if (text.startsWith('~~', i)) {
const end = text.indexOf('~~', i + 2);
if (end !== -1) {
const content = text.slice(i + 2, end);
const content = unescapeText(text.slice(i + 2, end));
if (content) ops.push({ insert: content, attributes: { strike: true } });
i = end + 2;
continue;
@ -396,7 +469,7 @@ function inlineMarkdownToOps(text) { @@ -396,7 +469,7 @@ function inlineMarkdownToOps(text) {
if (text[i] === '*') {
const end = text.indexOf('*', i + 1);
if (end !== -1) {
const content = text.slice(i + 1, end);
const content = unescapeText(text.slice(i + 1, end));
if (content) ops.push({ insert: content, attributes: { italic: true } });
i = end + 1;
continue;
@ -414,7 +487,7 @@ function inlineMarkdownToOps(text) { @@ -414,7 +487,7 @@ function inlineMarkdownToOps(text) {
}
function nextSpecialIndex(text, start) {
const specials = ['`', '[', '*', '~'];
const specials = ['\\', '`', '[', '*', '~'];
let min = text.length;
for (const ch of specials) {
const idx = text.indexOf(ch, start);

Loading…
Cancel
Save