This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423"use strict"12||+typeof await/2//2; export default/** DOM node renderer (for use in browsers) factory class**/class Markup_Render_Dom { constructor() { // This tag-function parses an HTML string, and returns a function // which creates a copy of that HTML DOM tree when called. // ex: let create = ๐ถ`<div></div>` // - create() acts like document.createElement('div') let temp = document.createElement('template') function ๐ถ([html]) { temp.innerHTML = html.replace(/\s*?\n\s*/g, "") return document.importNode.bind(document, temp.content.firstChild, true) } // todo: this needs to be more powerful. i.e. returning entire elements in some cases etc. gosh idk.. need to handle like, sbs emotes ? how uh nno that should be the parser's job.. oh and also this should, like, // for embeds, need separate handlers for normal urls and embeds and let URL_SCHEME = { __proto__: null, "sbs:": (url, thing)=> "#"+url.pathname+url.search+url.hash, "https:": (url, thing)=> url.href, "http:": (url, thing)=> url.href, "data:": (url, thing)=> url.href, DEFAULT: (url, thing)=> "about:blank#"+url.href, // these take a url string instead of URL RELATIVE: (href, thing)=> href.replace(/^[/]{0,2}/, "https://"), ERROR: (href, thing)=> "about:blank#"+href, } function filter_url(url, thing) { try { let u = new URL(url, "no-scheme:/") if ('no-scheme:'==u.protocol) return URL_SCHEME.RELATIVE(url, thing) else return (URL_SCHEME[u.protocol] || URL_SCHEME.DEFAULT)(u, thing) } catch (e) { return URL_SCHEME.ERROR(url, thing) } } let preview let CREATE = { __proto__: null, newline: ๐ถ`<br>`, divider: ๐ถ`<hr>`, code: function({text, lang}) { // <tt>? let e = this() e.textContent = text return e }.bind(๐ถ`<pre>`), // .bind(value) makes that value accessible as `this` inside the function, when it's called. (note that the value is only evaluated once) // I'm just using this as a simple trick to store the html templates with their init functions, but there's no special reason to do it this way icode: function({text}) { let e = this() e.textContent = text.replace(/ /g, "ย ") // non breaking space.. return e }.bind(๐ถ`<code>`), simple_link: function({url, text}) { let e = this() if (text==null) { e.textContent = url } else { e.textContent = text e.className += ' M-link-custom' } if (!url.startsWith("#")) { url = filter_url(url, 'link') e.target = '_blank' } else e.target = '_self' //hack e.href = url return e }.bind(๐ถ`<a href="" class='M-link'>`), image: function({url, alt, width, height}) { let src = filter_url(url, 'image') let e = document.createElement('img') e.classList.add('M-image') e.dataset.shrink = "" if (alt!=null) e.alt = e.title = alt e.tabIndex = 0 const set_size = (state, width=e.naturalWidth, height=e.naturalHeight)=>{ e.width = width e.height = height e.style.setProperty('--width', width) e.style.setProperty('--height', height) e.dataset.state = state } if (height) set_size('size', width, height) e.src = src // check whether the image is "available" (i.e. size is known) by looking at naturalHeight // https://html.spec.whatwg.org/multipage/images.html#img-available // this will happen here if the image is VERY cached, i guess if (e.naturalHeight) set_size('loaded') else // otherwise wait for load e.decode().then(ok=>{ set_size('loaded') }, no=>{ e.dataset.state = 'error' }) return e }, error: ๐ถ`<div class='error'><code>๐ฏerror๐ฏ</code>๐ฏmessage๐ฏ<pre>๐ฏstack๐ฏ`, audio: function({url}) { url = filter_url(url, 'audio') let e = this() e.dataset.src = url e.onclick = ev=>{ ev.preventDefault() let e = ev.currentTarget let audio = document.createElement('audio') audio.controls = true audio.autoplay = true audio.src = e.dataset.src e.replaceChildren(audio) e.onclick = null } let link = e.firstChild link.href = url link.title = url link.lastChild.textContent = url.replace(/.*[/]/, "โฆ/").replace(/[?].*$/, "?โฆ") return e }.bind(๐ถ`<y12-audio><a>๐ต๏ธ<span></span></a></y12-audio>`), video: function({url}) { let e = this() let media = document.createElement('video') media.setAttribute('tabindex', 0) media.preload = 'none' media.dataset.shrink = "video" media.src = filter_url(url, 'video') e.firstChild.append(media) let cl = e.lastChild let [play, progress, time] = cl.childNodes play.onclick = e=>{ if (media.paused) media.play() else media.pause() e.stopPropagation() } media.onpause = e=>{ play.textContent = "โถ๏ธ" } media.onplay = e=>{ play.textContent = "โธ๏ธ" } media.onresize = ev=>{ media.onresize = null media.parentNode.style.aspectRatio = media.videoWidth+"/"+media.videoHeight media.parentNode.style.height = media.videoHeight+"px" media.parentNode.style.width = media.videoWidth+"px" } media.onerror = ev=>{ time.textContent = 'Error' } media.ondurationchange = e=>{ let s = media.duration progress.disabled = false progress.max = s let m = Math.floor(s / 60) s = s % 60 time.textContent = m+":"+(s+100).toFixed(2).substring(1) } media.ontimeupdate = e=>{ progress.value = media.currentTime } progress.onchange = e=>{ media.currentTime = progress.value } return e }.bind(๐ถ`<y12-video> <figure class='M-image-wrapper'></figure> <div class='M-media-controls'> <button>โถ๏ธ</button> <input type=range min=0 max=1 step=any value=0 disabled> <span>not loaded</span> </div></y12-video>`), italic: ๐ถ`<i>`, bold: ๐ถ`<b>`, strikethrough: ๐ถ`<s>`, underline: ๐ถ`<u>`, heading: function({level, id}) { let e = document.createElement("h"+(level- -1)) if (id) { let e2 = this() e2.name = id e2.appendChild(e) } return e }.bind(๐ถ`<a name="" class=M-anchor></a>`), // what if instead of the \a tag, we just supported // an [id=...] attribute on every tag? just need to set id, so... // well except <a name=...> is safer than id... anchor: function({id}) { let e = this() if (id) e.name = id return e }.bind(๐ถ`<a name="" class=M-anchor></a>`), quote: function({cite}) { if (cite==null) return this[0]() let e = this[1]() e.firstChild.textContent = cite return e.lastChild }.bind([ ๐ถ`<blockquote class='M-quote'>`, ๐ถ`<blockquote class='M-quote'><cite class='M-quote-label'></cite>:<div class='M-quote-inner'></div></blockquote>`, // should we have -outer class? ]), table: function() { let e = this() return e.firstChild.firstChild }.bind(๐ถ`<div class='M-table-outer'><table><tbody>`), table_row: ๐ถ`<tr>`, table_cell: function({header, color, truecolor, colspan, rowspan, align, div}, row_args) { let e = this[header||row_args.header ? 1 : 0]() if (color) e.dataset.bgcolor = color if (truecolor) e.style.backgroundColor = truecolor if (colspan) e.colSpan = colspan if (rowspan) e.rowSpan = rowspan if (align) e.style.textAlign = align // todo: better way of representing this? if (div) e.classList.add('M-wall-right') if (row_args.divider) e.classList.add('M-wall-top') return e }.bind([๐ถ`<td>`, ๐ถ`<th>`]), youtube: function({url}) { let e = this() e.firstChild.textContent = url e.firstChild.href = url e.dataset.href = url return e }.bind(๐ถ`<youtube-embed><a target=_blank></a></youtube-embed>`), link: function({url}) { let e = this() if (!url.startsWith("#")) { url = filter_url(url, 'link') e.target = '_blank' } else e.target = '_self' e.href = url return e }.bind(๐ถ`<a class='M-link M-link-custom' href="">`), list: function({style}) { if (style==null) return this[0]() let e = this[1]() //e.style.listStyleType = style // this was only supported by old bbcode so i can probably secretly remove it. return e }.bind([๐ถ`<ul>`, ๐ถ`<ol>`]), /* todo: list bullets suck, because you can't select/copy themwe should create our own fake bullet elements instead.*/ list_item: ๐ถ`<li>`, align: function({align}) { let e = this() e.style.textAlign = align return e }.bind(๐ถ`<div>`), subscript: ๐ถ`<sub>`, superscript: ๐ถ`<sup>`, small: ๐ถ`<small>`, small_caps: ๐ถ`<span class='M-small-caps'>`, overline: ๐ถ`<span class='M-overline'>`, /*anchor: function({name}) { let e = this() e.id = "Markup-anchor-"+name return e }.bind(๐ถ`<span id="" class='M-anchor'>`),*/ ruby: function({text}) { let e = this() e.lastChild.textContent = text return e.firstChild }.bind(๐ถ`<ruby><span></span><rt>`), // I don't think we need <rp> since we're rendering for modern browsers... spoiler: function({label, cw}) { let e = this() if (cw) e.classList.add('M-content-warning') e.firstChild.textContent = label//.replace(/_/g, " ") //todo: [12y1] maybe replace all underscores in args with spaces, during parsing? return e.lastChild }.bind(๐ถ`<details class='M-spoiler'> <summary class='M-spoiler-label'></summary> <div class='M-spoiler-inner'></div></details>`), background_color: function({color}) { let e = this() if (color) e.dataset.bgcolor = color return e }.bind(๐ถ`<span class='M-background'>`), language: function({lang}) { let e = this() e.lang = lang return e }.bind(๐ถ`<span>`), invalid: function({text, reason}) { let e = this() e.title = reason e.textContent = text return e }.bind(๐ถ`<span class='M-invalid'>`), key: ๐ถ`<kbd>`, preview: function(node) { let e = this() e.textContent = node.type return e }.bind(๐ถ`<div class='M-preview'>`), } function fill_branch(branch, leaves) { for (let leaf of leaves) { if ('string'==typeof leaf) { branch.append(leaf) } else { let node if (preview && (leaf.type=='audio' || leaf.type=='video' || leaf.type=='youtube')) { node = CREATE.preview(leaf) } else { let creator = CREATE[leaf.type] if (!creator) { if ('object'==typeof leaf && leaf) throw new RangeError("unknown node .type: โ"+leaf.type+"โ") else throw new TypeError("unknown node type: "+typeof leaf) } node = creator(leaf.args) } if (leaf.content) { if ('table_row'===leaf.type) { for (let cell of leaf.content) { if ('table_cell'!==cell.type) continue let c = CREATE.table_cell(cell.args, leaf.args||{}) if (cell.content) fill_branch(c, cell.content) node.append(c) } } else { fill_branch(node, leaf.content) } } branch.append(node.getRootNode()) // recursion order? } } } /** Render function (closure method) @param {Tree} ast - input ast @param {ParentNode} [node=document.createDocumentFragment()] - destination node @param {?object} options - render options @return {ParentNode} - node with rendered contents. same as `node` if passed, otherwise is a new DocumentFragment. **/ this.render = function({args, content}, node=document.createDocumentFragment(), options) { preview = options && options.preview node.textContent = "" //mmnn fill_branch(node, content) return node } /** block rendering functions @member {Object<string,function>} **/ this.create = CREATE /** URL processing functions @member {Object<string,function>} **/ this.url_scheme = URL_SCHEME this.filter_url = filter_url}}
export default Markup_Render_Dom