(function (app) { 'use strict'; const markdown = app.modules.markdown = {}; const dom = app.dom; var escapeHtml = app.util.escapeHtml; function renderInline(text) { if (!text) { return ''; } let rendered = escapeHtml(text); const codePlaceholders = []; rendered = rendered.replace(/`([^`]+)`/g, function (_, code) { codePlaceholders.push('' + escapeHtml(code) + ''); return '\u0000C' + (codePlaceholders.length - 1) + '\u0000'; }); rendered = rendered.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function (_, label, url) { const trimmedUrl = url.trim(); const allowed = /^(https?:\/\/|mailto:|tel:|\/|\.{1,2}\/)/i.test(trimmedUrl); if (!allowed) { return '[' + label + '](' + url + ')'; } const safeUrl = trimmedUrl.replace(/\s+/g, '%20').replace(/"/g, '%22'); return '' + escapeHtml(label) + ''; }); rendered = rendered.replace(/(^|\s)((https?:\/\/)[^\s<]+)/gi, function (_, prefix, url) { const safe = url.replace(/"/g, '%22'); return prefix + '' + url + ''; }); rendered = rendered .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/__([^_]+)__/g, '$1') .replace(/(^|\W)\*([^*]+)\*(?=\W|$)/g, '$1$2') .replace(/(^|\W)_([^_]+)_(?=\W|$)/g, '$1$2') .replace(/~~([^~]+)~~/g, '$1'); rendered = rendered.replace(/\u0000C(\d+)\u0000/g, function (_, index) { return codePlaceholders[Number(index)] || ''; }); return rendered; } function leadingIndent(text) { const expanded = text.replace(/\t/g, ' '); const match = expanded.match(/^(\s*)/); return match ? match[1].length : 0; } markdown.render = function renderMarkdownBasic(markdownText) { const lines = (markdownText || '').replace(/\r\n?/g, '\n').split('\n'); const output = []; let inCode = false; let codeBuffer = []; const listStack = []; const tableSeparator = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/; function flushCode() { if (!inCode) { return; } output.push('
' + escapeHtml(codeBuffer.join('\n')) + '
'); inCode = false; codeBuffer = []; } function closeListsTo(indent) { while (listStack.length && listStack[listStack.length - 1].indent > indent) { output.push(''); } } function closeAllLists() { closeListsTo(-1); } function openList(type, indent) { listStack.push({ type, indent }); output.push('<' + type + '>'); } function splitCells(row) { const trimmed = row.trim().replace(/^\|/, '').replace(/\|$/, ''); return trimmed.split('|').map(function (cell) { return cell.trim(); }); } for (let index = 0; index < lines.length; index += 1) { const raw = lines[index]; const line = raw; if (/^```/.test(line)) { if (inCode) { flushCode(); } else { inCode = true; codeBuffer = []; } continue; } if (inCode) { codeBuffer.push(raw); continue; } if (!line.trim()) { closeAllLists(); continue; } if (/\|/.test(line) && index + 1 < lines.length && tableSeparator.test(lines[index + 1])) { closeAllLists(); const headerCells = splitCells(line); index += 2; const body = []; while (index < lines.length && /\|/.test(lines[index]) && lines[index].trim()) { body.push(splitCells(lines[index])); index += 1; } index -= 1; const thead = '' + headerCells.map(function (cell) { return '' + renderInline(cell) + ''; }).join('') + ''; const tbody = '' + body.map(function (rowCells) { return '' + rowCells.map(function (cell) { return '' + renderInline(cell) + ''; }).join('') + ''; }).join('') + ''; output.push('' + thead + tbody + '
'); continue; } if (/^\s*(?:---|\*\*\*|___)\s*$/.test(line)) { closeAllLists(); output.push('
'); continue; } if (/^\s*>\s?/.test(line)) { closeAllLists(); const quoteLines = []; while (index < lines.length && /^\s*>\s?/.test(lines[index])) { quoteLines.push(lines[index].replace(/^\s*>\s?/, '')); index += 1; } index -= 1; const html = quoteLines.map(function (text) { return '

' + renderInline(text) + '

'; }).join('\n'); output.push('
' + html + '
'); continue; } let match = line.match(/^(#{1,6})\s+(.*)$/); if (match) { closeAllLists(); const level = match[1].length; const text = renderInline(match[2].trim()); output.push('' + text + ''); continue; } match = line.match(/^(\s*)[-*+]\s+(.*)$/); if (match) { const indent = leadingIndent(match[1]); const type = 'ul'; if (!listStack.length || listStack[listStack.length - 1].indent < indent) { openList(type, indent); } else { closeListsTo(indent); if (!listStack.length || listStack[listStack.length - 1].type !== type || listStack[listStack.length - 1].indent !== indent) { openList(type, indent); } } output.push('
  • ' + renderInline(match[2].trim()) + '
  • '); continue; } match = line.match(/^(\s*)\d+\.\s+(.*)$/); if (match) { const indent = leadingIndent(match[1]); const type = 'ol'; if (!listStack.length || listStack[listStack.length - 1].indent < indent) { openList(type, indent); } else { closeListsTo(indent); if (!listStack.length || listStack[listStack.length - 1].type !== type || listStack[listStack.length - 1].indent !== indent) { openList(type, indent); } } output.push('
  • ' + renderInline(match[2].trim()) + '
  • '); continue; } closeAllLists(); output.push('

    ' + renderInline(line) + '

    '); } closeAllLists(); flushCode(); return output.join('\n'); }; markdown.refresh = function refreshPreview() { const textarea = dom.qs('#remarks'); const target = dom.qs('#remarks-render'); if (!textarea || !target) { return; } target.innerHTML = markdown.render(textarea.value || ''); }; markdown.bindRemarksLivePreview = function bindRemarksLivePreview() { const textarea = dom.qs('#remarks'); if (!textarea) { return; } textarea.addEventListener('input', function () { if (app.state.mode === 'edit') { markdown.refresh(); } }); markdown.refresh(); }; app.registerInit(function () { markdown.bindRemarksLivePreview(); }); })(window.transmittalApp);