/***************************************************************************** * casual-markdown - a lightweight regexp-base markdown parser with TOC support * 2022/07/31, v0.90, refine frontmatter (simple yaml) * 2023/04/12, v0.92, addCopyButton for code-block * * Copyright (c) 2022-2023, Casualwriter (MIT Licensed) * https://github.com/casualwriter/casual-markdown *****************************************************************************/ ;(function(){ // define md object, and extent function (which is a dummy function) var md = { yaml:{}, before: function (str) {return str}, after: function (str) {return str} } // function for REGEXP to convert html tag. ie. => <TAG*gt; md.formatTag = function (html) { return html.replace(//g,'>'); } // frontmatter for simple YAML (support multi-level, but string value only) md.formatYAML = function (front, matter) { var level = {}, latest = md.yaml; matter.replace( /^\s*#(.*)$/gm, '' ).replace( /^( *)([^:^\n]+):(.*)$/gm, function(m, sp, key,val) { level[sp] = level[sp] || latest latest = level[sp][key.trim()] = val.trim() || {} for (e in level) if(e>sp) level[e]=null; } ); return '' } //===== format code-block, highlight remarks/keywords for code/sql md.formatCode = function (match, title, block) { // convert tag <> to < > tab to 3 space, support marker using ^^^ block = block.replace(//g,'>') block = block.replace(/\t/g,' ').replace(/\^\^\^(.+?)\^\^\^/g, '$1') // highlight comment and keyword based on title := none | sql | code if (title.toLowerCase(title) == 'sql') { block = block.replace(/^\-\-(.*)/gm,'--$1').replace(/\s\-\-(.*)/gm,' --$1') block = block.replace(/(\s?)(function|procedure|return|if|then|else|end|loop|while|or|and|case|when)(\s)/gim,'$1$2$3') block = block.replace(/(\s?)(select|update|delete|insert|create|from|where|group by|having|set)(\s)/gim,'$1$2$3') } else if ((title||'none')!=='none') { block = block.replace(/^\/\/(.*)/gm,'//$1').replace(/\s\/\/(.*)/gm,' //$1') block = block.replace(/(\s?)(function|procedure|return|exit|if|then|else|end|loop|while|or|and|case|when)(\s)/gim,'$1$2$3') block = block.replace(/(\s?)(var|let|const|=>|for|next|do|while|loop|continue|break|switch|try|catch|finally)(\s)/gim,'$1$2$3') } return '
'  + block + '
' } // copy to clipboard for code-block md.clipboard = function (e) { navigator.clipboard.writeText( e.parentNode.innerText.replace('copy\n','') ) e.innerText = 'copied' } //===== parse markdown string into HTML string (exclude code-block) md.parser = function( mdstr ) { // apply yaml variables for (var name in this.yaml) mdstr = mdstr.replace( new RegExp('\{\{\\s*'+name+'\\s*\}\}', 'gm'), this.yaml[name] ) // table syntax mdstr = mdstr.replace(/\n(.+?)\n.*?\-\-\s?\|\s?\-\-.*?\n([\s\S]*?)\n\s*?\n/g, function (m,p1,p2) { var thead = p1.replace(/^\|(.+)/gm,'$1').replace(/(.+)\|$/gm,'$1').replace(/\|/g,'') var tbody = p2.replace(/^\|(.+)/gm,'$1').replace(/(.+)\|$/gm,'$1') tbody = tbody.replace(/(.+)/gm,'$1').replace(/\|/g,'') return '\n\n\n\n' + tbody + '\n
' + thead + '\n
\n\n' } ) // horizontal rule =>
mdstr = mdstr.replace(/^-{3,}|^\_{3,}|^\*{3,}$/gm, '
').replace(/\n\n/g, '\n

') // header =>

..

mdstr = mdstr.replace(/^##### (.*?)\s*#*$/gm, '
$1
') .replace(/^#### (.*?)\s*#*$/gm, '

$1

') .replace(/^### (.*?)\s*#*$/gm, '

$1

') .replace(/^## (.*?)\s*#*$/gm, '

$1

') .replace(/^# (.*?)\s*#*$/gm, '

$1

') .replace(/^(.*?)\s*{(.*)}\s*<\/h\d\>$/gm, '$2') // inline code-block: `code-block` => code-block mdstr = mdstr.replace(/``(.*?)``/gm, function(m,p){ return '' + md.formatTag(p).replace(/`/g,'`') + ''} ) mdstr = mdstr.replace(/`(.*?)`/gm, '$1' ) // blockquote, max 2 levels =>
{text}
mdstr = mdstr.replace(/^\>\> (.*$)/gm, '
$1
') mdstr = mdstr.replace(/^\> (.*$)/gm, '
$1
') mdstr = mdstr.replace(/<\/blockquote\>\n/g, '\n
' ) mdstr = mdstr.replace(/<\/blockquote\>\n/g, '\n
' ) // image syntax: ![title](url) => title mdstr = mdstr.replace(/!\[(.*?)\]\((.*?) "(.*?)"\)/gm, '$1') mdstr = mdstr.replace(/!\[(.*?)\]\((.*?)\)/gm, '$1') // links syntax: [title "title"](url) => text mdstr = mdstr.replace(/\[(.*?)\]\((.*?) "new"\)/gm, '$1') mdstr = mdstr.replace(/\[(.*?)\]\((.*?) "(.*?)"\)/gm, '$1') mdstr = mdstr.replace(/([<\s])(https?\:\/\/.*?)([\s\>])/gm, '$1$2$3') mdstr = mdstr.replace(/\[(.*?)\]\(\)/gm, '$1') mdstr = mdstr.replace(/\[(.*?)\]\((.*?)\)/gm, '$1') // unordered/ordered list, max 2 levels =>
  • ..
,
  1. ..
mdstr = mdstr.replace(/^[\*+-][ .](.*)/gm, '
  • $1
' ) mdstr = mdstr.replace(/^\d\d?[ .](.*)/gm, '
  1. $1
' ) mdstr = mdstr.replace(/^\s{2,6}[\*+-][ .](.*)/gm, '
    • $1
' ) mdstr = mdstr.replace(/^\s{2,6}\d[ .](.*)/gm, '
    1. $1
' ) mdstr = mdstr.replace(/<\/[ou]l\>\n\n?<[ou]l\>/g, '\n' ) mdstr = mdstr.replace(/<\/[ou]l\>\n<[ou]l\>/g, '\n' ) // text decoration: bold, italic, underline, strikethrough, highlight mdstr = mdstr.replace(/\*\*\*(\w.*?[^\\])\*\*\*/gm, '$1') mdstr = mdstr.replace(/\*\*(\w.*?[^\\])\*\*/gm, '$1') mdstr = mdstr.replace(/\*(\w.*?[^\\])\*/gm, '$1') mdstr = mdstr.replace(/___(\w.*?[^\\])___/gm, '$1') mdstr = mdstr.replace(/__(\w.*?[^\\])__/gm, '$1') // mdstr = mdstr.replace(/_(\w.*?[^\\])_/gm, '$1') // NOT support!! mdstr = mdstr.replace(/\^\^\^(.+?)\^\^\^/gm, '$1') mdstr = mdstr.replace(/\^\^(\w.*?)\^\^/gm, '$1') mdstr = mdstr.replace(/~~(\w.*?)~~/gm, '$1') // line break and paragraph =>

mdstr = mdstr.replace(/ \n/g, '\n
').replace(/\n\s*\n/g, '\n

\n') // indent as code-block mdstr = mdstr.replace(/^ {4,10}(.*)/gm, function(m,p) { return '

' + md.formatTag(p) + '
'} ) mdstr = mdstr.replace(/^\t(.*)/gm, function(m,p) { return '
' + md.formatTag(p) + '
'} ) mdstr = mdstr.replace(/<\/code\><\/pre\>\n/g, '\n' ) // Escaping Characters return mdstr.replace(/\\([`_~\*\+\-\.\^\\\<\>\(\)\[\]])/gm, '$1' ) } //===== parse markdown string into HTML content (cater code-block) md.html = function (mdText) { // replace \r\n to \n, and handle front matter for simple YAML mdText = mdText.replace(/\r\n/g, '\n').replace( /^---+\s*\n([\s\S]*?)\n---+\s*\n/, md.formatYAML ) // handle code-block. mdText = mdText.replace(/\n~~~/g,'\n```').replace(/\n``` *(.*?)\n([\s\S]*?)\n``` *\n/g, md.formatCode) // split by "", skip for code-block and process normal text var pos1=0, pos2=0, mdHTML = '' while ( (pos1 = mdText.indexOf('')) >= 0 ) { pos2 = mdText.indexOf('', pos1 ) mdHTML += md.after( md.parser( md.before( mdText.substr(0,pos1) ) ) ) mdHTML += mdText.substr(pos1, (pos2>0? pos2-pos1+7 : mdtext.length) ) mdText = mdText.substr( pos2 + 7 ) } return '
' + mdHTML + md.after( md.parser( md.before(mdText) ) ) + '
' } //===== TOC support md.toc = function (srcDiv, tocDiv, options ) { // select elements, set title var tocSelector = (options&&options.css) || 'h1,h2,h3,h4' var tocTitle = (options&&options.title) || 'Table of Contents' var toc = document.getElementById(srcDiv).querySelectorAll( tocSelector ) var html = '
    ' + (tocTitle=='none'? '' : '

    ' + tocTitle + '

    '); // loop for each element,add
  • element with class in TAG name. for (var i=0; i' html += toc[i].textContent + '
  • '; } document.getElementById(tocDiv).innerHTML = html + "
"; //===== scrollspy support (ps: add to document.body if element(scrollspy) not found) if ( options && options.scrollspy ) { (document.getElementById(options.scrollspy)||document).onscroll = function () { // get TOC elements, and viewport position var list = document.getElementById(tocDiv).querySelectorAll('li') var divScroll = document.getElementById(options.scrollspy) || document.documentElement var divHeight = divScroll.clientHeight || divScroll.offsetHeight // loop for each TOC element, add/remove scrollspy class for (var i=0; i0 && pos