Added markdown rendering
This commit is contained in:
parent
26ca510785
commit
5a0340c226
172 changed files with 12198 additions and 8338 deletions
298
static/js/marked_position.mjs
Normal file
298
static/js/marked_position.mjs
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
import { Marked } from './lib/node_modules/marked/lib/marked.esm.js'
|
||||
import markedTokenPosition from './lib/node_modules/marked-token-position/lib/index.esm.js'
|
||||
|
||||
const other = {// {{{
|
||||
codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm,
|
||||
outputLinkReplace: /\\([\[\]])/g,
|
||||
indentCodeCompensation: /^(\s+)(?:```)/,
|
||||
beginningSpace: /^\s+/,
|
||||
endingHash: /#$/,
|
||||
startingSpaceChar: /^ /,
|
||||
endingSpaceChar: / $/,
|
||||
nonSpaceChar: /[^ ]/,
|
||||
newLineCharGlobal: /\n/g,
|
||||
tabCharGlobal: /\t/g,
|
||||
multipleSpaceGlobal: /\s+/g,
|
||||
blankLine: /^[ \t]*$/,
|
||||
doubleBlankLine: /\n[ \t]*\n[ \t]*$/,
|
||||
blockquoteStart: /^ {0,3}>/,
|
||||
blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g,
|
||||
blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm,
|
||||
listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g,
|
||||
listIsTask: /^\[[ xX]\] +\S/,
|
||||
listReplaceTask: /^\[[ xX]\] +/,
|
||||
listTaskCheckbox: /\[[ xX]\]/,
|
||||
anyLine: /\n.*\n/,
|
||||
hrefBrackets: /^<(.*)>$/,
|
||||
tableDelimiter: /[:|]/,
|
||||
tableAlignChars: /^\||\| *$/g,
|
||||
tableRowBlankLine: /\n[ \t]*$/,
|
||||
tableAlignRight: /^ *-+: *$/,
|
||||
tableAlignCenter: /^ *:-+: *$/,
|
||||
tableAlignLeft: /^ *:-+ *$/,
|
||||
startATag: /^<a /i,
|
||||
endATag: /^<\/a>/i,
|
||||
startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i,
|
||||
endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i,
|
||||
startAngleBracket: /^</,
|
||||
endAngleBracket: />$/,
|
||||
pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/,
|
||||
unicodeAlphaNumeric: /[\p{L}\p{N}]/u,
|
||||
escapeTest: /[&<>"']/,
|
||||
escapeReplace: /[&<>"']/g,
|
||||
escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
|
||||
escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
|
||||
caret: /(^|[^\[])\^/g,
|
||||
percentDecode: /%25/g,
|
||||
findPipe: /\|/g,
|
||||
splitPipe: / \|/,
|
||||
slashPipe: /\\\|/g,
|
||||
carriageReturn: /\r\n|\r/g,
|
||||
spaceLine: /^ +$/gm,
|
||||
notSpaceStart: /^\S*/,
|
||||
endingNewline: /\n$/,
|
||||
listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`),
|
||||
nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),
|
||||
hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),
|
||||
fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`),
|
||||
headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`),
|
||||
htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, 'i'),
|
||||
blockquoteBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}>`),
|
||||
}// }}}
|
||||
const escapeReplacements = {// {{{
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}// }}}
|
||||
const getEscapeReplacement = (ch) => escapeReplacements[ch]
|
||||
|
||||
function cleanUrl(href) {// {{{
|
||||
try {
|
||||
href = encodeURI(href).replace(other.percentDecode, '%')
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
return href
|
||||
}// }}}
|
||||
function escapeHtmlEntities(html, encode) {// {{{
|
||||
if (encode) {
|
||||
if (other.escapeTest.test(html)) {
|
||||
return html.replace(other.escapeReplace, getEscapeReplacement)
|
||||
}
|
||||
} else {
|
||||
if (other.escapeTestNoEncode.test(html)) {
|
||||
return html.replace(other.escapeReplaceNoEncode, getEscapeReplacement)
|
||||
}
|
||||
}
|
||||
|
||||
return html
|
||||
}// }}}
|
||||
|
||||
export class MarkedPosition {
|
||||
constructor() {// {{{
|
||||
window.setpos = (event) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
_mbus.dispatch('MARKDOWN_EDIT', {
|
||||
position: {
|
||||
start: event.target.dataset.offsetStart,
|
||||
end: event.target.dataset.offsetEnd,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.marked = new Marked()
|
||||
this.marked.use(markedTokenPosition())
|
||||
this.marked.use({
|
||||
renderer: {
|
||||
heading(token) {
|
||||
const content = this.parser.parseInline(token.tokens)
|
||||
return `<h${token.depth} onclick="setpos(event)" onclick="setpos(this)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${content}</h${token.depth}>\n`
|
||||
},
|
||||
|
||||
paragraph(token) {
|
||||
const content = this.parser.parseInline(token.tokens)
|
||||
return `<p onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${content}</p>\n`
|
||||
},
|
||||
|
||||
list(token) {
|
||||
const ordered = token.ordered
|
||||
const start = token.start
|
||||
|
||||
let body = ''
|
||||
for (let j = 0; j < token.items.length; j++) {
|
||||
const item = token.items[j]
|
||||
body += this.listitem(item)
|
||||
}
|
||||
|
||||
const type = ordered ? 'ol' : 'ul'
|
||||
const startAttr = (ordered && start !== 1) ? (' start="' + start + '"') : ''
|
||||
return '<' + type + startAttr + '>\n' + body + '</' + type + '>\n'
|
||||
},
|
||||
|
||||
listitem(token) {
|
||||
return `<li onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${this.parser.parse(token.tokens)}</li>\n`
|
||||
},
|
||||
|
||||
code(token) {
|
||||
const langString = (token.lang || '').match(other.notSpaceStart)?.[0]
|
||||
|
||||
const code = token.text.replace(other.endingNewline, '') + '\n'
|
||||
|
||||
if (!langString) {
|
||||
return `<pre onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}"><code>`
|
||||
+ (token.escaped ? code : escapeHtmlEntities(code, true))
|
||||
+ '</code></pre>\n'
|
||||
}
|
||||
|
||||
return `<pre onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}"><code class="language-`
|
||||
+ escapeHtmlEntities(langString)
|
||||
+ '">'
|
||||
+ (token.escaped ? code : escapeHtmlEntities(code, true))
|
||||
+ '</code></pre>\n'
|
||||
},
|
||||
|
||||
blockquote(token) {
|
||||
const body = this.parser.parse(token.tokens)
|
||||
return `<blockquote onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">\n${body}</blockquote>\n`
|
||||
},
|
||||
|
||||
html(token) {
|
||||
return token.text
|
||||
},
|
||||
|
||||
def(token) {
|
||||
return ''
|
||||
},
|
||||
|
||||
hr(token) {
|
||||
return `<hr onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">\n`
|
||||
},
|
||||
|
||||
checkbox(token) {
|
||||
return `<input onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}"`
|
||||
+ (token.checked ? 'checked="" ' : '')
|
||||
+ 'disabled="" type="checkbox"> '
|
||||
},
|
||||
|
||||
table(token) {
|
||||
let header = '';
|
||||
|
||||
// header
|
||||
let cell = '';
|
||||
for (let j = 0; j < token.header.length; j++) {
|
||||
cell += this.tablecell(token.header[j]);
|
||||
}
|
||||
header += this.tablerow({ text: cell });
|
||||
|
||||
let body = '';
|
||||
for (let j = 0; j < token.rows.length; j++) {
|
||||
const row = token.rows[j];
|
||||
|
||||
cell = '';
|
||||
for (let k = 0; k < row.length; k++) {
|
||||
cell += this.tablecell(row[k]);
|
||||
}
|
||||
|
||||
body += this.tablerow({ text: cell });
|
||||
}
|
||||
if (body) body = `<tbody>${body}</tbody>`;
|
||||
|
||||
return '<table>\n'
|
||||
+ '<thead>\n'
|
||||
+ header
|
||||
+ '</thead>\n'
|
||||
+ body
|
||||
+ '</table>\n'
|
||||
},
|
||||
|
||||
tablerow(token) {
|
||||
return `<tr>\n${token.text}</tr>\n`
|
||||
},
|
||||
|
||||
tablecell(token) {
|
||||
let ofs = ''
|
||||
if (token.tokens.length > 0) {
|
||||
const start = token.tokens[0].position.start.offset
|
||||
const end = token.tokens[0].position.end.offset
|
||||
ofs = `onclick="setpos(event)" data-offset-start="${start}" data-offset-end="${end}"`
|
||||
}
|
||||
|
||||
const content = this.parser.parseInline(token.tokens);
|
||||
const type = token.header ? 'th' : 'td';
|
||||
const tag = token.align
|
||||
? `<${type} ${ofs} align="${token.align}">`
|
||||
: `<${type} ${ofs}>`;
|
||||
return tag + content + `</${type}>\n`
|
||||
},
|
||||
|
||||
strong(token) {
|
||||
return `<strong onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${this.parser.parseInline(token.tokens)}</strong>`
|
||||
},
|
||||
|
||||
em(token) {
|
||||
return `<em onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${this.parser.parseInline(token.tokens)}</em>`
|
||||
},
|
||||
|
||||
codespan(token) {
|
||||
return `<code onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${escapeHtmlEntities(token.text, true)}</code>`
|
||||
},
|
||||
|
||||
br(token) {
|
||||
return `<br onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">`
|
||||
},
|
||||
|
||||
del(token) {
|
||||
return `<del onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}">${this.parser.parseInline(token.tokens)}</del>`
|
||||
},
|
||||
|
||||
link(token) {
|
||||
const text = this.parser.parseInline(token.tokens)
|
||||
const cleanHref = cleanUrl(token.href)
|
||||
if (cleanHref === null) {
|
||||
return text
|
||||
}
|
||||
token.href = cleanHref
|
||||
let out = '<a onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}" href="' + token.href + '"'
|
||||
if (token.title) {
|
||||
out += ' title="' + (escapeHtmlEntities(token.title)) + '"'
|
||||
}
|
||||
out += '>' + text + '</a>'
|
||||
return out
|
||||
},
|
||||
|
||||
image(token) {
|
||||
|
||||
if (token.tokens) {
|
||||
token.text = this.parser.parseInline(token.tokens, this.parser.textRenderer)
|
||||
}
|
||||
const cleanHref = cleanUrl(token.href)
|
||||
if (cleanHref === null) {
|
||||
return escapeHtmlEntities(token.text)
|
||||
}
|
||||
token.href = cleanHref
|
||||
|
||||
let out = `<img onclick="setpos(event)" data-offset-start="${token.position.start.offset}" data-offset-end="${token.position.end.offset}" src="${token.href}" alt="${escapeHtmlEntities(token.text)}"`
|
||||
if (token.title) {
|
||||
out += ` title="${escapeHtmlEntities(token.title)}"`
|
||||
}
|
||||
out += '>'
|
||||
return out
|
||||
},
|
||||
|
||||
text(token) {
|
||||
return 'tokens' in token && token.tokens
|
||||
? this.parser.parseInline(token.tokens)
|
||||
: ('escaped' in token && token.escaped ? token.text : escapeHtmlEntities(token.text))
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
}// }}}
|
||||
parse(text) {// {{{
|
||||
return this.marked.parse(text)
|
||||
}// }}}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue