refactor: renderer.js拆分为6个模块(core/tabs/editor/search/sidebar/markdown)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// MarkLite — Markdown Module: marked.js init, sanitizer, rendering, preview links
|
||||
(function () {
|
||||
'use strict';
|
||||
var ML = window.MarkLite;
|
||||
|
||||
// ===== HTML Sanitizer =====
|
||||
var DANGEROUS_TAGS = new Set(['script', 'iframe', 'object', 'embed', 'applet', 'form', 'base', 'meta', 'link', 'style']);
|
||||
var DANGEROUS_ATTRS = new Set([
|
||||
'onabort', 'onanimationend', 'onanimationiteration', 'onanimationstart', 'onauxclick', 'onbeforecopy', 'onbeforecut',
|
||||
'onbeforepaste', 'onblur', 'oncancel', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'onclose', 'oncontextmenu',
|
||||
'oncopy', 'oncuechange', 'oncut', 'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover',
|
||||
'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus', 'onfocusin', 'onfocusout',
|
||||
'onfullscreenchange', 'onfullscreenerror', 'ongotpointercapture', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress',
|
||||
'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onlostpointercapture', 'onmousedown',
|
||||
'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onpaste',
|
||||
'onpause', 'onplay', 'onplaying', 'onpointercancel', 'onpointerdown', 'onpointerenter', 'onpointerleave', 'onpointermove',
|
||||
'onpointerout', 'onpointerover', 'onpointerup', 'onprogress', 'onratechange', 'onreset', 'onresize', 'onscroll',
|
||||
'onsearch', 'onseeked', 'onseeking', 'onselect', 'onselectionchange', 'onselectstart', 'onshow', 'onstalled', 'onsubmit',
|
||||
'onsuspend', 'ontimeupdate', 'ontoggle', 'ontouchcancel', 'ontouchend', 'ontouchmove', 'ontouchstart', 'ontransitionend',
|
||||
'onvolumechange', 'onwaiting', 'onwheel', 'onanimationend', 'oncontentvisibilityautostatechange', 'onscrollend',
|
||||
'onscrollsnapchange', 'onscrollsnapchanging'
|
||||
]);
|
||||
|
||||
function sanitizeHTML(html) {
|
||||
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
var walker = document.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
|
||||
var toRemove = [];
|
||||
while (walker.nextNode()) {
|
||||
var el = walker.currentNode;
|
||||
var tag = el.tagName.toLowerCase();
|
||||
if (DANGEROUS_TAGS.has(tag)) { toRemove.push(el); continue; }
|
||||
var attrs = Array.from(el.attributes);
|
||||
for (var i = 0; i < attrs.length; i++) {
|
||||
var name = attrs[i].name.toLowerCase();
|
||||
if (DANGEROUS_ATTRS.has(name) || name.startsWith('on')) el.removeAttribute(attrs[i].name);
|
||||
else if ((name === 'href' || name === 'action' || name === 'formaction' || name === 'xlink:href') && /^\s*javascript:/i.test(attrs[i].value)) el.removeAttribute(attrs[i].name);
|
||||
else if (name === 'style' && /expression\s*\(|url\s*\(\s*['"]?\s*javascript:/i.test(attrs[i].value)) el.removeAttribute(attrs[i].name);
|
||||
}
|
||||
}
|
||||
for (var _i = 0; _i < toRemove.length; _i++) toRemove[_i].parentNode.removeChild(toRemove[_i]);
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
|
||||
// ===== Rendering =====
|
||||
ML.renderMarkdown = function (text) {
|
||||
if (typeof marked === 'undefined') { ML.preview.textContent = '错误: Markdown 解析库未加载'; ML.preview.style.color = 'red'; return; }
|
||||
try {
|
||||
var raw = marked.parse(text);
|
||||
ML.preview.innerHTML = sanitizeHTML(raw);
|
||||
ML.preview.style.color = '';
|
||||
} catch (e) { ML.preview.textContent = '渲染错误: ' + e.message; ML.preview.style.color = 'red'; }
|
||||
};
|
||||
|
||||
// ===== Init marked.js =====
|
||||
ML.initMarked = function () {
|
||||
if (typeof marked === 'undefined') return;
|
||||
var renderer = new marked.Renderer();
|
||||
renderer.code = function (codeOrToken, legacyLang) {
|
||||
var text = (typeof codeOrToken === 'object' && codeOrToken !== null) ? codeOrToken.text : codeOrToken;
|
||||
var lang = (typeof codeOrToken === 'object' && codeOrToken !== null) ? codeOrToken.lang : legacyLang;
|
||||
var highlighted = text;
|
||||
if (typeof hljs !== 'undefined') {
|
||||
if (lang && hljs.getLanguage(lang)) { try { highlighted = hljs.highlight(text, { language: lang }).value; } catch (e) { /* */ } }
|
||||
else { try { highlighted = hljs.highlightAuto(text).value; } catch (e) { /* */ } }
|
||||
}
|
||||
var langClass = lang ? ' language-' + lang : '';
|
||||
return '<pre><code class="hljs' + langClass + '">' + highlighted + '</code></pre>';
|
||||
};
|
||||
renderer.image = function (hrefOrToken, title, text) {
|
||||
var href = (typeof hrefOrToken === 'object' && hrefOrToken !== null) ? hrefOrToken.href : hrefOrToken;
|
||||
var alt = (typeof hrefOrToken === 'object' && hrefOrToken !== null) ? hrefOrToken.text : text;
|
||||
var titleAttr = (typeof hrefOrToken === 'object' && hrefOrToken !== null) ? hrefOrToken.title : title;
|
||||
var src = href;
|
||||
if (src && !src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('data:') && !src.startsWith('file://')) {
|
||||
var tab = ML.getActiveTab();
|
||||
if (tab && tab.filePath) {
|
||||
var dir = tab.filePath.replace(/[/\\][^/\\]+$/, '');
|
||||
var sep = dir.includes('\\') ? '\\' : '/';
|
||||
src = 'file://' + (dir + sep + src).replace(/\\/g, '/');
|
||||
}
|
||||
}
|
||||
var titleStr = titleAttr ? ' title="' + titleAttr + '"' : '';
|
||||
return '<img src="' + src + '" alt="' + alt + '"' + titleStr + '>';
|
||||
};
|
||||
marked.setOptions({ gfm: true, breaks: true, pedantic: false });
|
||||
marked.use({ renderer: renderer });
|
||||
};
|
||||
|
||||
// ===== Preview Links =====
|
||||
ML.initPreviewLinks = function () {
|
||||
ML.preview.addEventListener('click', function (e) {
|
||||
var link = e.target.closest('a');
|
||||
if (!link) return;
|
||||
e.preventDefault();
|
||||
var href = link.getAttribute('href');
|
||||
if (!href) return;
|
||||
if (href.startsWith('#')) { var target = ML.preview.querySelector(href); if (target) target.scrollIntoView({ behavior: 'smooth' }); return; }
|
||||
try { var url = new URL(href); if (url.protocol !== 'http:' && url.protocol !== 'https:') return; } catch (err) { return; }
|
||||
if (typeof window.electronAPI !== 'undefined' && window.electronAPI.openExternal) window.electronAPI.openExternal(href);
|
||||
else window.open(href, '_blank', 'noopener');
|
||||
});
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user