diff --git a/markup.css b/markup.css index d3d3350..8d4b990 100644 --- a/markup.css +++ b/markup.css @@ -119,6 +119,42 @@ L + ratio { } height: auto; } +.Markup .M-emote { + display: inline-block; + object-fit: contain; + width: calc(var(--size)*var(--emote-size, 1.125em)); + height: calc(var(--size)*var(--emote-size, 1.125em)); + vertical-align: middle; + max-height: unset; + border: none; +} + +.M-filter-h { + transform: scaleX(-1); +} +.M-filter-v { + transform: scaleY(-1); +} +.M-filter-h.M-image-filter-v { + transform: scaleX(-1) scaleY(-1); +} +.M-filter-r { + transform: rotate(90deg); +} +.M-filter-h.M-filter-r { + transform: scaleX(-1) rotate(90deg); +} +.M-filter-v.M-filter-r { + transform: scaleY(-1) rotate(90deg); +} +.M-filter-h.M-filter-v.M-filter-r { + transform: rotate(270deg); +} +.M-filter-p { + image-rendering: pixelated; + image-rendering: crisp-edges; +} + /* ruby text doesn't work if set to white-space: pre */ .Markup rt { white-space: pre-line; diff --git a/parse.js b/parse.js index 53764f7..f6ad2ba 100644 --- a/parse.js +++ b/parse.js @@ -422,7 +422,7 @@ class Markup_12y2 { constructor() { read_args() if (token==='\\link') { read_body(false) - } else { + } else if (token!=='\\e') { // Emote should not have body read_body(true) if (NO_ARGS===rargs && false===body) { NEVERMIND() @@ -504,6 +504,10 @@ class Markup_12y2 { constructor() { let [lang=""] = rargs OPEN('language', {lang}) word_maybe() + } break; case '\\e': { + let [id="",name="",role="emote",source=""] = rargs.reverse() + OPEN('emote', {source, name, id, role}) + CLOSE() }} } break; case 'STYLE': { let c = check_style(token, text.charAt(match.index-1)||"\n", text.charAt(REGEX.lastIndex)||"\n") diff --git a/render.js b/render.js index 95fa7a9..6682928 100644 --- a/render.js +++ b/render.js @@ -29,6 +29,15 @@ class Markup_Render_Dom { constructor() { ERROR: (href, thing)=> "about:blank#"+href, } + let EMOTE_SOURCES = { + __proto__: null, + "": (id, options) => options.pixel ? `sbs:image/${id}` : `sbs:image/${id}?size=128`, + "url": (id, options) => id, + "discordemote": (id, options) => `https://cdn.discordapp.com/emojis/${id}`, + "discordsticker": (id, options) => `https://media.discordapp.net/stickers/${id}`, + UNKNOWN: (id, options) => null + } + function filter_url(url, thing) { try { let u = new URL(url, "no-scheme:/") @@ -112,6 +121,68 @@ class Markup_Render_Dom { constructor() { }) return e }, + + emote: function({source, name, id, role, filter}, filter2) { + if (id == "") id = name + if (role == "") role = "emote" + let options + [id, options=""] = id.split("#") + options+=(filter || "") + (filter2 || "") + let opt = {} + // Duplicate filters cancel out + options.split('').forEach(c=>opt[c] = !opt[c]) + let url, unknown = false + if (id) + url = (EMOTE_SOURCES[source] || EMOTE_SOURCES.UNKNOWN)(id, {pixel: opt["p"]}) + let src = filter_url(url || "data:image/gif;base64,R0lGODlhAQABAIAAANDL5NDL5CH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", 'image') + let e = document.createElement('img') + e.classList.add('M-emote') + e.dataset.emotesource = source + if (url == null) e.dataset.emoteunknown = true + if (name!=null) + e.alt = e.title = name + e.tabIndex = 0 + const set_size = (state, width=e.naturalWidth, height=e.naturalHeight)=>{ + if (state=="size") { + e.width = width + e.height = height + } + e.style.setProperty('--width', width) + e.style.setProperty('--height', height) + e.dataset.state = state + } + let size = 2 + switch (role) { + case "icon": + size = 1 + break + case "emote": + size = 2 + break + case "medium": + size = 4 + break + case "sticker": + size = 8 + break + } + e.style.setProperty('--size', size) + set_size('size', size * 16, size * 16) + e.src = src + Object.keys(opt).forEach(x => x ? e.classList.add(`M-filter-${x}`) : undefined) + // 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-emote') + else // otherwise wait for load + e.decode().then(ok=>{ + set_size('loaded-emote') + }, no=>{ + e.dataset.state = 'error' + }) + return e + }, error: 𐀶`
🕯error🕯🕯message🕯
🕯stack🕯`,
 		
@@ -416,6 +487,7 @@ we should create our own fake bullet elements instead.*/
 		@member {Object}
 	**/
 	this.url_scheme = URL_SCHEME
+	this.emote_sources = EMOTE_SOURCES
 	this.filter_url = filter_url
 }}