Feat v0.6.5 renderer pipeline (#40)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #40.
This commit is contained in:
@@ -69,100 +69,13 @@
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// Markdown renderer (inline, no deps)
|
||||
// Markdown rendering via sw.markdown
|
||||
// ═══════════════════════════════════════════
|
||||
// Preload marked + DOMPurify so renderSync works in useMemo
|
||||
sw.markdown.preload();
|
||||
|
||||
function renderMarkdown(src) {
|
||||
if (!src) return '';
|
||||
var lines = src.split('\n');
|
||||
var out = [];
|
||||
var inCode = false;
|
||||
var inList = false;
|
||||
var listTag = '';
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i];
|
||||
|
||||
// fenced code blocks
|
||||
if (line.trim().startsWith('```')) {
|
||||
if (inCode) {
|
||||
out.push('</code></pre>');
|
||||
inCode = false;
|
||||
} else {
|
||||
if (inList) { out.push('</' + listTag + '>'); inList = false; }
|
||||
out.push('<pre><code>');
|
||||
inCode = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inCode) {
|
||||
out.push(escHtml(line) + '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
// blank line
|
||||
if (!line.trim()) {
|
||||
if (inList) { out.push('</' + listTag + '>'); inList = false; }
|
||||
continue;
|
||||
}
|
||||
|
||||
// headings
|
||||
if (line.startsWith('### ')) { closeList(); out.push('<h3>' + inline(line.slice(4)) + '</h3>'); continue; }
|
||||
if (line.startsWith('## ')) { closeList(); out.push('<h2>' + inline(line.slice(3)) + '</h2>'); continue; }
|
||||
if (line.startsWith('# ')) { closeList(); out.push('<h1>' + inline(line.slice(2)) + '</h1>'); continue; }
|
||||
|
||||
// hr
|
||||
if (/^[-*_]{3,}\s*$/.test(line)) { closeList(); out.push('<hr>'); continue; }
|
||||
|
||||
// blockquote
|
||||
if (line.startsWith('> ')) { closeList(); out.push('<blockquote><p>' + inline(line.slice(2)) + '</p></blockquote>'); continue; }
|
||||
|
||||
// unordered list
|
||||
if (/^[\-*+]\s/.test(line)) {
|
||||
if (!inList || listTag !== 'ul') { closeList(); out.push('<ul>'); inList = true; listTag = 'ul'; }
|
||||
out.push('<li>' + inline(line.replace(/^[\-*+]\s/, '')) + '</li>');
|
||||
continue;
|
||||
}
|
||||
|
||||
// ordered list
|
||||
if (/^\d+\.\s/.test(line)) {
|
||||
if (!inList || listTag !== 'ol') { closeList(); out.push('<ol>'); inList = true; listTag = 'ol'; }
|
||||
out.push('<li>' + inline(line.replace(/^\d+\.\s/, '')) + '</li>');
|
||||
continue;
|
||||
}
|
||||
|
||||
// paragraph
|
||||
closeList();
|
||||
out.push('<p>' + inline(line) + '</p>');
|
||||
}
|
||||
if (inCode) out.push('</code></pre>');
|
||||
if (inList) out.push('</' + listTag + '>');
|
||||
return out.join('\n');
|
||||
|
||||
function closeList() { if (inList) { out.push('</' + listTag + '>'); inList = false; } }
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function inline(s) {
|
||||
s = escHtml(s);
|
||||
// inline code
|
||||
s = s.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
// bold
|
||||
s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
s = s.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
||||
// italic
|
||||
s = s.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
||||
s = s.replace(/_(.+?)_/g, '<em>$1</em>');
|
||||
// links
|
||||
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||
// wikilinks: [[Note Title]] → clickable internal link
|
||||
s = s.replace(/\[\[([^\]]+)\]\]/g, function(match, linkText) {
|
||||
return '<a class="wikilink" data-wikilink="' + linkText.trim() + '" href="#">' + linkText.trim() + '</a>';
|
||||
});
|
||||
return s;
|
||||
function _escAttr(s) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
|
||||
@@ -854,13 +767,13 @@
|
||||
}, [note ? note.id : null, saveCount]);
|
||||
|
||||
var previewHtml = useMemo(function() {
|
||||
var h = renderMarkdown(body);
|
||||
var h = sw.markdown.renderSync(body, { sanitize: true });
|
||||
// mark unresolved wikilinks
|
||||
for (var i = 0; i < outgoingLinks.length; i++) {
|
||||
var link = outgoingLinks[i];
|
||||
if (!link.target_id) {
|
||||
var search = 'class="wikilink" data-wikilink="' + escHtml(link.link_text) + '"';
|
||||
var replace = 'class="wikilink wikilink--unresolved" data-wikilink="' + escHtml(link.link_text) + '"';
|
||||
var search = 'class="wikilink" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
var replace = 'class="wikilink wikilink--unresolved" data-wikilink="' + _escAttr(link.link_text) + '"';
|
||||
h = h.split(search).join(replace);
|
||||
}
|
||||
}
|
||||
@@ -884,6 +797,13 @@
|
||||
return function() { el.removeEventListener('click', handleClick); };
|
||||
}, [viewMode, onNavigateToTitle]);
|
||||
|
||||
// ── Run post-renderers (mermaid, katex, etc.) on preview ──
|
||||
useEffect(function() {
|
||||
var el = previewRef.current;
|
||||
if (!el || viewMode === 'edit') return;
|
||||
sw.renderers.runPostRenderers(el);
|
||||
}, [previewHtml, viewMode]);
|
||||
|
||||
// ── Synced scroll in split mode ──
|
||||
useEffect(function() {
|
||||
if (viewMode !== 'split' || !cmEditorRef.current) return;
|
||||
|
||||
Reference in New Issue
Block a user