(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('' + listStack.pop().type + '>');
}
}
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 = '' + 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('
' + 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);