|
|
|
|
@ -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); |
|
|
|
|
|