147 lines
4.4 KiB
JavaScript
147 lines
4.4 KiB
JavaScript
/*!
|
|
* marked-mini - minimal Markdown renderer for Memos Clipper
|
|
* Supports: headings, bold, italic, strike, code, pre, blockquote,
|
|
* links, images, ul, ol, hr, tables, line breaks
|
|
*/
|
|
(function(global){
|
|
"use strict";
|
|
function escape(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");}
|
|
|
|
function parseInline(s){
|
|
// code spans
|
|
s = s.replace(/`([^`]+)`/g,(_,c)=>`<code>${escape(c)}</code>`);
|
|
// bold+italic
|
|
s = s.replace(/\*\*\*(.+?)\*\*\*/g,"<strong><em>$1</em></strong>");
|
|
// 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>");
|
|
// strikethrough
|
|
s = s.replace(/~~(.+?)~~/g,"<del>$1</del>");
|
|
// images
|
|
s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(_,alt,src)=>`<img src="${escape(src)}" alt="${escape(alt)}">`);
|
|
// links
|
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(_,txt,href)=>`<a href="${escape(href)}" target="_blank">${txt}</a>`);
|
|
// auto-link
|
|
s = s.replace(/(?<!["\(])(https?:\/\/[^\s<>)]+)/g,url=>`<a href="${escape(url)}" target="_blank">${escape(url)}</a>`);
|
|
// hard break
|
|
s = s.replace(/ \n/g,"<br>");
|
|
return s;
|
|
}
|
|
|
|
function parse(src){
|
|
const lines = src.split("\n");
|
|
const out = [];
|
|
let i = 0;
|
|
|
|
while(i < lines.length){
|
|
const line = lines[i];
|
|
|
|
// Fenced code block
|
|
const fenceM = line.match(/^```(\w*)/);
|
|
if(fenceM){
|
|
const lang = fenceM[1]||"";
|
|
const code = [];
|
|
i++;
|
|
while(i<lines.length && !lines[i].startsWith("```")){
|
|
code.push(lines[i]);
|
|
i++;
|
|
}
|
|
i++; // skip closing ```
|
|
out.push(`<pre><code class="language-${escape(lang)}">${escape(code.join("\n"))}</code></pre>`);
|
|
continue;
|
|
}
|
|
|
|
// Headings
|
|
const hM = line.match(/^(#{1,6})\s+(.*)/);
|
|
if(hM){
|
|
const lvl = hM[1].length;
|
|
out.push(`<h${lvl}>${parseInline(hM[2])}</h${lvl}>`);
|
|
i++; continue;
|
|
}
|
|
|
|
// HR
|
|
if(/^[-*_]{3,}\s*$/.test(line)){
|
|
out.push("<hr>");
|
|
i++; continue;
|
|
}
|
|
|
|
// Blockquote
|
|
if(line.startsWith("> ")){
|
|
const bq = [];
|
|
while(i<lines.length && lines[i].startsWith("> ")){
|
|
bq.push(lines[i].slice(2));
|
|
i++;
|
|
}
|
|
out.push(`<blockquote>${parse(bq.join("\n"))}</blockquote>`);
|
|
continue;
|
|
}
|
|
|
|
// Unordered list
|
|
if(/^[-*+]\s/.test(line)){
|
|
const items = [];
|
|
while(i<lines.length && /^[-*+]\s/.test(lines[i])){
|
|
items.push(`<li>${parseInline(lines[i].replace(/^[-*+]\s/,""))}</li>`);
|
|
i++;
|
|
}
|
|
out.push(`<ul>${items.join("")}</ul>`);
|
|
continue;
|
|
}
|
|
|
|
// Ordered list
|
|
if(/^\d+\.\s/.test(line)){
|
|
const items = [];
|
|
while(i<lines.length && /^\d+\.\s/.test(lines[i])){
|
|
items.push(`<li>${parseInline(lines[i].replace(/^\d+\.\s/,""))}</li>`);
|
|
i++;
|
|
}
|
|
out.push(`<ol>${items.join("")}</ol>`);
|
|
continue;
|
|
}
|
|
|
|
// Table (| col | col |)
|
|
if(line.startsWith("|") && i+1<lines.length && /^\|[-| :]+\|/.test(lines[i+1])){
|
|
const headers = line.split("|").filter((_,idx,a)=>idx>0&&idx<a.length-1).map(h=>`<th>${parseInline(h.trim())}</th>`);
|
|
i+=2; // skip header + separator
|
|
const rows = [];
|
|
while(i<lines.length && lines[i].startsWith("|")){
|
|
const cells = lines[i].split("|").filter((_,idx,a)=>idx>0&&idx<a.length-1).map(c=>`<td>${parseInline(c.trim())}</td>`);
|
|
rows.push(`<tr>${cells.join("")}</tr>`);
|
|
i++;
|
|
}
|
|
out.push(`<table><thead><tr>${headers.join("")}</tr></thead><tbody>${rows.join("")}</tbody></table>`);
|
|
continue;
|
|
}
|
|
|
|
// Empty line
|
|
if(line.trim()===""){
|
|
i++; continue;
|
|
}
|
|
|
|
// Paragraph — collect until blank line
|
|
const para = [];
|
|
while(i<lines.length && lines[i].trim()!==""){
|
|
// stop on block elements
|
|
if(/^(#{1,6}\s|```|>|[-*+]\s|\d+\.\s|[-*_]{3,}\s*$)/.test(lines[i])) break;
|
|
para.push(lines[i]);
|
|
i++;
|
|
}
|
|
if(para.length) out.push(`<p>${parseInline(para.join(" "))}</p>`);
|
|
}
|
|
return out.join("\n");
|
|
}
|
|
|
|
global.marked = {
|
|
parse: function(src, opts) {
|
|
const breaks = opts && opts.breaks;
|
|
// With breaks:true, single newlines in paragraphs become <br>
|
|
if (breaks) {
|
|
// Pre-process: single \n inside paragraph text → two spaces + \n (markdown hard break)
|
|
src = src.replace(/([^\n])\n([^\n])/g, "$1 \n$2");
|
|
}
|
|
return parse(src);
|
|
}
|
|
};})(window);
|