1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2024-11-24 20:42:46 +01:00
Discord-History-Tracker/bld/viewer.html

171 lines
25 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Discord Offline History</title>
<script type="text/javascript">
var u=function(){function s(e){var t=e.url.lastIndexOf(".");return".png"===(t=-1===t?"":e.url.substring(t).toLowerCase())||".gif"===t||".jpg"===t||".jpeg"===t}function r(e){return(e=new Date(e)).toLocaleDateString()+", "+e.toLocaleTimeString()}function i(e){var a,t=C.i(e.replace(k,"$1"));return _.o.enableFormatting&&(a=(e,t)=>"&#"+t.charCodeAt(0)+";",t=t.replace(j,"&#96;").replace(w,(e,t,n)=>"<code class='block'>"+n.replace(M,a)+"</code>").replace(b,(e,t,n)=>"<code class='inline'>"+n.replace(M,a)+"</code>").replace(E,a).replace(S,e=>e.replace(/\\/g,"").replace(/(.)/g,a)).replace(f,"<b>$1</b>").replace(v,(e,t,n)=>"\\"===t?e:(t||"")+"<i>"+n+"</i>").replace(g,"<u>$1</u>").replace(h,"<s>$1</s>")),e=_.o.enableAnimatedEmoji?"gif":"png","<p>"+(t=t.replace(y,"<a href='$1' target='_blank' rel='noreferrer'>$1</a>").replace(R,(e,t)=>"<span class='link mention-chat'>#"+_.l(t)+"</span>").replace(O,(e,t)=>"<span class='link mention-user' title='#"+(_.p(t)||"????")+"'>@"+_.v(t)+"</span>").replace(x,"<img src='https://cdn.discordapp.com/emojis/$2.png' alt=':$1:' title=':$1:' class='emoji'>").replace(T,"<img src='https://cdn.discordapp.com/emojis/$2."+e+"' alt=':$1:' title=':$1:' class='emoji'>"))+"</p>"}var t,n,a,o,c,l,u,d,p,m,f=/\*\*([\s\S]+?)\*\*(?!\*)/g,v=/(.)?\*([\s\S]+?)\*(?!\*)/g,g=/__([\s\S]+?)__(?!_)/g,h=/~~([\s\S]+?)~~(?!~)/g,b=/(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/g,w=/```(?:([A-z0-9\-]+?)\n+)?\n*([^]+?)\n*```/g,y=/(\b(?:https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi,k=/<(\b(?:https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])>/gi,j=/\\`/g,E=/\\([*\\])/g,S=/\\__|_\\_|\\_\\_|\\~~|~\\~|\\~\\~/g,M=/([*_~\\])/g,O=/&lt;@!?(\d+?)&gt;/g,R=/&lt;#(\d+?)&gt;/g,x=/&lt;:([^:]+):(\d+?)&gt;/g,T=/&lt;a:([^:]+):(\d+?)&gt;/g;return{g:function(){t=new A(["<div data-channel='{id}'>","<div class='info' title='{topic}'><strong class='name'>#{name}</strong>{nsfw}<span class='tag'>{msgcount}</span></div>","<span class='server'>{server.name} ({server.type})</span>","</div>"].join("")),n=new A(["<div data-channel='{id}'>","<div class='info'><strong class='name'>{name}</strong><span class='tag'>{msgcount}</span></div>","<span class='server'>({server.type})</span>","</div>"].join("")),a=new A(["<div>","<div class='reply-message'>{reply}</div>","<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>","<div class='message'>{contents}{embeds}{attachments}</div>","<div class='reactions'>{reactions}</div>","</div>"].join("")),o=new A(["<div>","<div class='reply-message reply-message-with-avatar'>{reply}</div>","<div class='avatar-wrapper'>","<div class='avatar'>{avatar}</div>","<div>","<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>","<div class='message'>{contents}{embeds}{attachments}</div>","<div class='reactions'>{reactions}</div>","</div>","</div>","</div>"].join("")),c=new A(["<img src='https://cdn.discordapp.com/avatars/{id}/{path}.webp?size=128'>"].join("")),l=new A(["<a href='{url}' class='embed thumbnail'><img src='{url}' alt='(image attachment not found)'></a><br>"].join("")),u=new A(["<div class='embed download'><a href='{url}' class='title'>{t}</a><p class='desc'>{d}</p></div>"].join("")),d=new A(["<div class='embed download'><a href='{url}' class='title'>{t}</a></div>"].join("")),p=new A(["<div class='embed download'><p>(Formatted embeds are currently not supported)</p></div>"].join("")),m=new A(["<a href='{url}' class='embed download'>Download {filename}</a>"].join("")),templateReaction=new A(["<span class='reaction-wrapper'><span class='reaction-emoji'>{n}</span><span class='count'>{c}</span></span>"].join("")),templateReactionCustom=new A(["<span class='reaction-wrapper'><img src='https://cdn.discordapp.com/emojis/{id}.{ext}' alt=':{n}:' title=':{n}:' class='reaction-emoji-custom'><span class='count'>{c}</span></span>"].join(""))},h:s,k:function(e){return("SERVER"===e.server.type?t:n).apply(e,(e,t)=>{if("server.type"===e)switch(t){case"SERVER":return"server";case"GROUP":return"group";case"DM":return"user"}else if("nsfw"===e)return t?"<span class='tag'>NSFW</span>":""})},j:function(e){return(_.o.enableUserAvatars?o:a).apply(e,(e,t)=>{if("avatar"===e)return t?c.apply(t):"";if("user.tag"===e)return t||"????";if("timestamp"===e)return r(t);if("contents"===e)return null==t||0===t.length?"":i(t);if("embeds"===e)return t?t.map(e=>{switch(e.type){case"image":return _.o.enableImagePreviews?l.apply(e):"";case"rich":return(e.t?e.d?u:d:p).apply(e)}}).join(""):"";if("attachments"===e)return t?t.map(e=>{if(s(e)&&_.o.enableImagePreviews)return l.apply(e);var t=e.url.split("/");return m.apply({url:e.url,filename:t[t.length-1]})}).join(""):"";if("edit"===e)return t?"<span class='info edited'>Edited"+(1<t?" "+r(t):"")+"</span>":"";if("jump"===e)return _.S?"<span class='info jump' data-jump='"+t+"'>Jump to message</span>":"";if("reply"!==e)return"reactions"===e?null===t?"":t.map(e=>"id"in e?(e.ext=e.an&&_.o.enableAnimatedEmoji?"gif":"png",templateReactionCustom.apply(e)):templateReaction.apply(e)).join(""):void 0;if(null===t)return"";var n="<span class='reply-username' title='#"+(t.user.tag||"????")+"'>"+t.user.name+"</span>",a=_.o.enableUserAvatars&&t.avatar?"<span class='reply-avatar'>"+c.apply(t.avatar)+"</span>":"",e=t.contents?"<span class='reply-contents'>"+i(t.contents)+"</span>":"";return"<span class='jump' data-jump='"+t.id+"'>Jump to reply</span><span class='user'>"+a+n+"</span>"+e})}}}(),C=function(){var a=(e,t)=>{e=document.createElement(e);return t.appendChild(e),e},t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},n=/[&<>"']/g;return{id:(e,t)=>(t||document).getElementById(e),M:(e,t)=>Array.prototype.slice.call((t||document).getElementsByClassName(e)),tag:(e,t)=>Array.prototype.slice.call((t||document).getElementsByTagName(e)),O:(e,t)=>(t||document).getElementsByClassName(e)[0],createElement:(e,t)=>a(e,t),R:e=>e.parentNode.removeChild(e),i:e=>String(e).replace(n,e=>t[e]),T:(e,t)=>{var n=new Blob([t],{type:"octet/stream"});if("msSaveBlob"in window.navigator)return window.navigator.msSaveBlob(n,e);t=window.URL.createObjectURL(n),n=a("a",document.body);n.href=t,n.download=e,n.style.display="none",n.click(),document.body.removeChild(n),window.URL.revokeObjectURL(t)}}}(),s=function(){var t,n,a=!1;return{g:function(){a=!0,t="<!DOCTYPE html>\n"+document.documentElement.outerHTML,C.id("btn-upload-file").insertAdjacentHTML("afterend",'<button id="btn-embed-file" disabled>Embed File</button>'),C.id("btn-embed-file").addEventListener("click",()=>function(e,t){var n=new Blob([t],{type:"octet/stream"});if("msSaveBlob"in window.navigator)return window.navigator.msSaveBlob(n,e);t=window.URL.createObjectURL(n),n=C.createElement("a",document.body);n.href=t,n.download=e,n.style.display="none",n.click(),document.body.removeChild(n),window.URL.revokeObjectURL(t)}("embed.html",n))},C:function(e){a&&(C.id("btn-embed-file").disabled=!1,n=t.replace("</title>",`</title>\n<script type="text/javascript">window.DHT_EMBEDDED = "${e=e,window.btoa(unescape(encodeURIComponent(e)))}";<\/script>`).replace(`<${document.body.tagName.toLowerCase()}>`,'<body class="embedded">'))},_:function(){var e=window.DHT_EMBEDDED;return e?decodeURIComponent(escape(window.atob(e))):null}}}();document.addEventListener("DOMContentLoaded",()=>{var e=s._();"?embed"!==location.search||e||s.g(),u.g(),n.g(),n.A(()=>{_.$(n.I())}),_.$(n.I()),n.F(e=>{_.U(e)}),n.D(e=>{_.P(e)}),_.L(e=>{n.N(e)}),_.B((e,t)=>{n.J(e,t,_.H)}),_.Z(e=>{n.V(_.G(),_.q()),n.W(e),n.Y()});function a(e,t,n){var a;try{a=JSON.parse(e),s.C(e)}catch(e){return console.error(e),void alert(t)}r.K(a)?_.X(new r(a)):alert(n)}e?a(e,"Could not parse embedded file, see console for details.","Embedded file has an invalid format."):n.ee(e=>{var t,n;return 1===e.length?(t=e[0],n=new FileReader,_.ne(t.name),n.onload=()=>a(n.result,"Could not parse '"+t.name+"', see console for details.","File '"+t.name+"' has an invalid format."),n.readAsText(t,"UTF-8")):alert("Please, select only one file."),!0})});var n=function(){function n(){var e=C.O("active",C.id("opt-filter-list"));return e&&""!==e.value?{type:e.getAttribute("data-filter-type"),value:e.value}:null}function s(){var e=n();C.id("opt-save-filtered").classList.toggle("active",null!=e),t&&t(e)}function r(e,t){var n=C.id("dialog");return n.innerHTML=t,n.style.width=e+"px",n.style.marginLeft=-e/2+"px",C.id("modal").classList.add("visible"),n}function i(){function e(e,t){var n=C.id(e);n.checked=_.o[t],n.addEventListener("change",()=>_.o[t]=n.checked)}r(560,`
<label><input id='dht-cfg-imgpreviews' type='checkbox'> Image Previews</label><br>
<label><input id='dht-cfg-formatting' type='checkbox'> Message Formatting</label><br>
<label><input id='dht-cfg-useravatars' type='checkbox'> User Avatars</label><br>
<label><input id='dht-cfg-animemoji' type='checkbox'> Animated Emoji</label><br>`),e("dht-cfg-imgpreviews","enableImagePreviews"),e("dht-cfg-formatting","enableFormatting"),e("dht-cfg-useravatars","enableUserAvatars"),e("dht-cfg-animemoji","enableAnimatedEmoji")}var o,c,t,l;return{g:function(){function t(){n.value="",n.dispatchEvent(new Event("change")),C.id("opt-filter-contents").value="",C.id("opt-save-filtered").classList.remove("active")}var e=C.id("uploaded-file"),n=C.id("opt-messages-filter"),a=C.id("opt-filter-list");C.id("btn-upload-file").addEventListener("click",()=>{e.click()}),e.addEventListener("change",()=>{o&&o(e.files)&&(e.value=null,t())}),n.value="",n.addEventListener("change",()=>{C.M("active",a).forEach(e=>e.classList.remove("active")),n.value&&a.querySelector("[data-filter-type='"+n.value+"']").classList.add("active"),s()}),Array.prototype.forEach.call(a.children,e=>{e.addEventListener("SELECT"===e.tagName?"change":"input",e=>s())}),C.id("opt-messages-per-page").addEventListener("change",()=>{c&&c()}),C.id("btn-save-filtered").addEventListener("click",()=>{confirm("Filtering only removes messages, all users and servers will remain in the new archive. Continue?")&&_.ae()}),C.tag("button",C.O("nav")).forEach(e=>{e.disabled=!0,e.addEventListener("click",()=>{l&&l(e.getAttribute("data-nav"))})}),C.id("btn-settings").addEventListener("click",()=>{i()}),C.id("btn-about").addEventListener("click",()=>{var e;r(560,`
<p>Discord History Tracker is developed by <a href='https://chylex.com'>chylex</a> as an <a href='${e="https://github.com/chylex/Discord-History-Tracker"}/blob/master/LICENSE.md'>open source</a> project.</p>
<sub>v.31a, released 12 Feb 2022</sub>
<p>Please, report any issues and suggestions to the <a href='${e}/issues'>tracker</a>. If you want to support the development, please spread the word and consider <a href='https://www.patreon.com/chylex'>becoming a patron</a> or <a href='https://ko-fi.com/chylex'>buying me a coffee</a>. Any support is appreciated!</p>
<p><a href='${e}/issues'>Issue Tracker</a> &nbsp;&mdash;&nbsp; <a href='${e}'>GitHub Repository</a> &nbsp;&mdash;&nbsp; <a href='https://twitter.com/chylexmc'>Developer's Twitter</a></p>`)}),C.id("messages").addEventListener("click",e=>{e=e.target.getAttribute("data-jump");e&&(t(),e=_.se(e),C.id("messages").children[e].scrollIntoView())}),C.id("overlay").addEventListener("click",()=>{C.id("modal").classList.remove("visible"),C.id("dialog").innerHTML=""})},ee:function(e){o=e},A:function(e){c=e},F:function(e){t=e},D:function(e){l=e},I:function(){return parseInt(C.id("opt-messages-per-page").value,10)},V:function(e,t){C.id("nav-page-current").innerHTML=e,C.id("nav-page-total").innerHTML=t||"?",C.id("nav-first").disabled=1===e,C.id("nav-prev").disabled=1===e,C.id("nav-pick").disabled=(t||0)<=1,C.id("nav-next").disabled=e===(t||1),C.id("nav-last").disabled=e===(t||1)},J:function(e,t,a){var s=C.id("channels");e?(null!=n()&&(e=e.filter(e=>0<e.msgcount)),s.innerHTML=e.map(e=>u.k(e)).join(""),Array.prototype.forEach.call(s.children,n=>{n.addEventListener("click",e=>{var t=C.O("active",s);t&&t.classList.remove("active"),n.classList.add("active"),a(n.getAttribute("data-channel"))})}),!t||(t=s.querySelector("[data-channel='"+t+"']"))&&t.classList.add("active")):s.innerHTML=""},W:function(e){C.id("messages").innerHTML=e?e.map(e=>u.j(e)).join(""):""},N:function(e){for(var t=C.id("opt-filter-user");1<t.length;)t.remove(1);var n,a=[];for(n of Object.keys(e)){var s=document.createElement("option");s.value=n,s.text=e[n].name,a.push(s)}a.sort((e,t)=>e.text.toLocaleLowerCase().localeCompare(t.text.toLocaleLowerCase())),a.forEach(e=>t.add(e))},Y:function(){C.id("messages").scrollTop=0}}}(),g={ie:{oe:t=>e=>e.u===t,ce:(t,n)=>e=>e.t>=t&&e.t<=n,le:t=>e=>-1!==("m"in e?e.m:"").indexOf(t),ue:t=>e=>t.test("m"in e?e.m:""),de:()=>e=>e.e&&e.e.some(e=>"image"===e.type)||e.a&&e.a.some(u.h),pe:()=>e=>e.a&&e.a.some(e=>!u.h(e)),me:()=>e=>e.e&&0<e.e.length,fe:()=>e=>e.a&&0<e.a.length,ve:()=>e=>"te"in e?e.te:1==(1&e.f)},ge:{he:(e,t)=>e.length===t.length?t<e?1:e<t?-1:0:e.length>t.length?1:-1,be:(e,t)=>e.length===t.length?t<e?-1:e<t?1:0:e.length>t.length?-1:1}};class r{constructor(e){var t=this;t.meta=e.meta,t.data=e.data,t.meta.users=t.meta.users||{},t.meta.userindex=t.meta.userindex||[],t.meta.servers=t.meta.servers||[],t.meta.channels=t.meta.channels||{}}static K(e){return e&&"object"==typeof e.meta&&"object"==typeof e.data}we(e){return this.meta.servers[e]||{name:"&lt;unknown&gt;",type:"ERROR"}}ye(){return this.meta.channels}ke(e){return this.meta.channels[e]||{id:e,name:e}}je(){return this.meta.users}Ee(e){return this.meta.users[this.meta.userindex[e]]||{name:"&lt;unknown&gt;"}}Se(e){return this.meta.userindex[e]}Me(e){return this.meta.users[e]||{name:e}}Oe(e){return this.meta.userindex.indexOf(e)}Re(e){return this.data[e]||{}}xe(e){var t,n=JSON.parse(JSON.stringify(this.meta)),a={};for(t of Object.keys(this.ye())){var s,r=this.Re(t),i={};for(s of Object.keys(r)){var o=r[s];e(o)&&(i[s]=o)}0<Object.keys(i).length?a[t]=i:delete n.channels[t]}return JSON.stringify({meta:n,data:a})}}var _=function(){function t(e){u&&u(m.Te(),e)}function r(){d&&d(m.Ce())}var c,n,a,s,i,o,l,u,d,p,m={};m.B=function(e){u=e},m.Z=function(e){d=e},m.L=function(e){p=e},m.X=function(e){c=e,i=n=null,o=1,p&&p(m._e()),t(),r()},m.ne=function(e){a=e},m.l=function(e){return c.ke(e).name},m.v=function(e){return c.Me(e).name},m.p=function(e){return c.Me(e).tag};function f(e){var t=c.Re(e),e=Object.keys(t);return e=s?e.filter(e=>s(t[e])):e}m.Te=function(){if(!c)return[];var t=c.ye();return Object.keys(t).map(e=>({id:e,name:t[e].name,server:c.we(t[e].server),msgcount:f(e).length,topic:t[e].topic||"",nsfw:t[e].nsfw||!1,position:t[e].position||-1})).sort((e,t)=>{var n=e.server,a=t.server;return n.type.localeCompare(a.type,"en")||n.name.toLocaleLowerCase().localeCompare(a.name.toLocaleLowerCase(),void 0,{numeric:!0})||e.position-t.position||e.name.toLocaleLowerCase().localeCompare(t.name.toLocaleLowerCase(),void 0,{numeric:!0})})},m.H=function(e){o=1,n=f(i=e).sort(g.ge.he),r()},m.Ae=function(){return i},m.Ce=function(){if(!n)return[];var o=c.Re(i),e=l*(m.G()-1);return n.slice(e,l?e+l:void 0).map(e=>{var t=o[e],n=c.Ee(t.u),a=n.avatar?{id:c.Se(t.u),path:n.avatar}:null,s="r"in t&&t.r in o?o[t.r]:null,r=s?c.Ee(s.u):null,i=r&&r.avatar?{id:c.Se(s.u),path:r.avatar}:null,s=s?{id:t.r,user:r,avatar:i,contents:s.m}:null;return{user:n,avatar:a,timestamp:t.t,contents:"m"in t?t.m:null,embeds:t.e,attachments:t.a,edit:"te"in t?t.te:1==(1&t.f),jump:e,reply:s,reactions:"re"in t?t.re:null}})},m.se=function(e){if(!n)return 0;e=n.indexOf(e);return-1==e?0:(o=Math.max(1,Math.min(m.q(),1+Math.floor(e/l))),r(),e%l)},m.S=!1,m.U=function(e){switch(e?e.type:""){case"user":s=g.ie.oe(c.Oe(e.value));break;case"contents":s=g.ie.le(e.value);break;case"withimages":s=g.ie.de();break;case"withdownloads":s=g.ie.pe();break;case"edited":s=g.ie.ve();break;default:s=null}m.S=null!=s,t(i),i&&m.H(i)},m.ae=function(){var e="dht-filtered.txt";a&&(e=a.includes("filtered")?a:a.replace(".","-filtered.")),C.T(e,c.xe(s))},m._e=function(){return c?c.je():[]},m.$=function(e){l=e,r()},m.P=function(e){switch(e){case"first":o=1;break;case"prev":o=Math.max(1,o-1);break;case"next":o=Math.min(m.q(),o+1);break;case"last":o=m.q();break;case"pick":var t=parseInt(prompt("Select page:",o),10);if(!t&&0!==t)return;o=Math.max(1,Math.min(m.q(),t))}r()},m.G=function(){var e=m.q();return(o=e<o&&0<e?e:o)||1},m.q=function(){return n?l?Math.ceil(n.length/l):1:0},m.o={};var e=(t,e,n)=>{var a="_"+t;Object.defineProperty(m.o,t,{get:()=>m.o[a],set:e=>{m.o[a]=e,r(),((e,t)=>{try{localStorage.setItem(e,t)}catch(e){console.error(e)}})(t,e)}});var s=(e=>{try{return localStorage.getItem(e)}catch(e){return console.error(e),null}})(t);null!==s&&(s=n(s)),m.o[a]=null===s?e:s},v=e=>"true"===e||"false"!==e&&null;return e("enableImagePreviews",!0,v),e("enableFormatting",!0,v),e("enableUserAvatars",!0,v),e("enableAnimatedEmoji",!0,v),m}(),e=/{([^{}]+?)}/g;class A{constructor(e){this.contents=e}apply(a,s){return this.contents.replace(e,(e,t)=>{var n=t.split(".").reduce((e,t)=>e[t],a);if(s){t=s(t,n);return void 0===t?C.i(n):t}return C.i(n)})}} </script>
<style type="text/css">
#channels {width:15vw;min-width:215px;max-width:300px;overflow-y:auto;background-color:#1C1E22}
#channels > div {cursor:pointer;padding:10px 12px;color:#eee;font-size:15px;border-bottom:1px solid #333333}
#channels > div:hover, #channels > div.active {background-color:#282B30}
#channels .info {display:flex;height:16px;margin-bottom:4px}
#channels .name {flex-grow:1;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}
#channels .tag {flex-shrink:1;background-color:rgba(255,255,255,0.08);border-radius:4px;margin-left:4px;margin-top:1px;padding:2px 5px;font-size:11px}
body {font-family:Whitney, "Helvetica Neue", Helvetica, Verdana, "Lucida Grande", sans-serif;line-height:1;margin:0;padding:0;overflow:hidden}
body.embedded .hide-embedded {display:none}
#menu {width:100%;height:48px;display:flex;flex-direction:row}
#app {height:calc(100vh - 48px);display:flex;flex-direction:row}
#menu {background-color:#17181C;border-bottom:1px dotted #5D626B}
#menu .splitter {width:1px;margin:9px 4px;background-color:#5D626B}
#menu .separator {flex:1 1 0}
#menu :disabled {background-color:#555;cursor:default}
#menu button, #menu select, #menu input[type="text"] {margin:8px;background-color:#7289DA;color:#FFF;text-shadow:1px 1px 2px rgba(0,0,0,0.75)}
#menu button {font-size:17px;padding:0 12px;border:0;cursor:pointer}
#menu select {font-size:14px;padding:6px;border:0;cursor:pointer}
#menu input[type="text"] {font-size:14px;padding:7px 12px;border:0}
#menu .nav {display:flex;flex-direction:row;margin:0 8px}
#menu .nav > button {font-size:14px}
#menu .nav > button.icon {font-family:Lucida Console, monospace;font-size:17px;padding:0 8px}
#menu .nav > button, #menu .nav > p {margin:8px 1px}
#opt-filter-list > select, #opt-filter-list > input {display:none}
#opt-filter-list > .active {display:block}
#opt-save-filtered {display:flex}
#opt-save-filtered:not(.active) {display:none}
#messages {flex:1 1 0;overflow-y:auto;background-color:#36393E}
#messages > div {margin:0 24px;padding:4px 0 12px;border-bottom:1px solid rgba(255,255,255,0.04)}
#messages h2 {margin:0;padding:0;display:block}
#messages .avatar-wrapper {display:flex;flex-direction:row;align-items:flex-start;align-content:flex-start}
#messages .avatar-wrapper > div {flex:1 1 auto}
#messages .avatar {flex:0 0 38px!important;margin:8px 14px 0 0}
#messages .avatar img {width:38px;border-radius:50%}
#messages .username {color:#FFF;font-size:15px;font-weight:600;margin-right:3px;letter-spacing:0}
#messages .info {color:rgba(255,255,255,0.4);font-size:12px;font-weight:500;letter-spacing:0}
#messages .info::before {content:"\2022";text-align:center;display:inline-block;width:14px}
#messages .jump {cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.message {margin-top:6px;color:rgba(255,255,255,0.7);font-size:15px;line-height:1.1em;white-space:pre-wrap;word-wrap:break-word}
.message .link, .reply-message .link {color:#7289DA;background-color:rgba(115,139,215,0.1)}
.message a, .reply-message a {color:#0096CF;text-decoration:none}
.message a:hover {text-decoration:underline}
.message p {margin:0}
.message .embed {display:inline-block;margin-top:8px}
.message .embed .title {font-weight:bold;display:inline-block}
.message .embed .desc {margin-top:4px}
.message .thumbnail {max-width:calc(100% - 20px);max-height:320px}
.message .thumbnail img {max-width:100%;max-height:320px;border-radius:3px}
.message .download {margin-right:8px;padding:8px 9px;border:1px solid rgba(255,255,255,0.5);border-radius:3px}
.message .embed:first-child, .message .download + .download {margin-top:0}
.message code {background-color:#2E3136;border-radius:5px;font-family:Menlo, Consolas, Monaco, monospace;font-size:14px}
.message code.inline {display:inline;padding:2px}
.message code.block {display:block;border:2px solid #282B30;margin-top:6px;padding:7px}
.message .emoji {width:22px;height:22px;margin:0 1px;vertical-align:-30%;object-fit:contain}
.reply-message {display:flex;align-items:baseline;flex-wrap:wrap;line-height:120%;white-space:nowrap}
.reply-message-with-avatar {margin:0 0 -2px 52px}
.reply-message .jump {color:rgba(255,255,255,0.4);font-size:12px;text-underline-offset:1px;margin-right:7px}
.reply-message .emoji {width:16px;height:16px;vertical-align:-20%;object-fit:contain}
.reply-message .user {margin-right:5px}
.reply-avatar {margin-right:4px}
.reply-avatar img {width:16px;border-radius:50%;vertical-align:middle}
.reply-username {color:#FFF;font-size:12px;font-weight:600;letter-spacing:0}
.reply-contents {display:inline-block;color:rgba(255,255,255,0.7);font-size:12px;max-width:calc(80%)}
.reply-contents p {margin:0;overflow:hidden;text-overflow:ellipsis}
.reply-contents code {background-color:#2E3136;font-family:Menlo, Consolas, Monaco, monospace;padding:1px 2px}
.reactions {margin-top:4px}
.reactions .reaction-wrapper {display:inline-block;border-radius:4px;margin:3px 2px 0 0;padding:3px 6px;background:#42454a;cursor:default}
.reactions .reaction-emoji {margin-right:5px;font-size:16px;display:inline-block;text-align:center;vertical-align:-5%}
.reactions .reaction-emoji-custom {height:15px;margin-right:5px;vertical-align:-10%}
.reactions .count {color:rgba(255,255,255,0.45);font-size:14px}
#modal div {position:absolute;display:none}
#modal.visible div {display:block}
#modal #overlay {left:0;top:0;width:100%;height:100%;background-color:#000}
#modal.visible #overlay {opacity:0.5}
#dialog {left:50%;top:50%;padding:16px;background-color:#fff;transform:translateY(-50%)}
#dialog p {line-height:1.2}
#dialog p:first-child, #dialog p:last-child {margin-top:1px;margin-bottom:1px}
#dialog sub {color:#999;font-size:12px}
#dialog a {color:#0096CF;text-decoration:none}
#dialog a:hover {text-decoration:underline}
</style>
</head>
<body>
<div id="menu">
<input id="uploaded-file" type="file" style="display:none">
<button id="btn-upload-file" class="hide-embedded">Load File</button>
<div class="splitter hide-embedded"></div>
<button id="btn-settings">Settings</button>
<div> <!-- needed to stop the select from messing up -->
<select id="opt-messages-per-page">
<option value="50">50 messages per page&nbsp;</option>
<option value="100">100 messages per page&nbsp;</option>
<option value="250">250 messages per page&nbsp;</option>
<option value="500">500 messages per page&nbsp;</option>
<option value="1000">1000 messages per page&nbsp;</option>
<option value="0">All messages&nbsp;</option>
</select>
</div>
<div class="nav">
<button id="nav-first" data-nav="first" class="icon">&laquo;</button>
<button id="nav-prev" data-nav="prev" class="icon">&lsaquo;</button>
<button id="nav-pick" data-nav="pick">Page <span id="nav-page-current">1</span>/<span id="nav-page-total">?</span></button>
<button id="nav-next" data-nav="next" class="icon">&rsaquo;</button>
<button id="nav-last" data-nav="last" class="icon">&raquo;</button>
</div>
<div class="splitter"></div>
<div> <!-- needed to stop the select from messing up -->
<select id="opt-messages-filter">
<option value="">No filter&nbsp;</option>
<option value="user">Filter messages by user&nbsp;</option>
<option value="contents">Filter messages by contents&nbsp;</option>
<option value="withimages">Only messages with images&nbsp;</option>
<option value="withdownloads">Only messages with downloads&nbsp;</option>
<option value="edited">Only edited messages&nbsp;</option>
</select>
</div>
<div id="opt-filter-list">
<select id="opt-filter-user" data-filter-type="user">
<option value="">Select user...</option>
</select>
<input id="opt-filter-contents" type="text" data-filter-type="contents" placeholder="Messages containing...">
<input type="hidden" data-filter-type="withimages" value="1">
<input type="hidden" data-filter-type="withdownloads" value="1">
<input type="hidden" data-filter-type="edited" value="1">
</div>
<div id="opt-save-filtered">
<div class="splitter"></div>
<button id="btn-save-filtered">Save Filtered Messages</button>
</div>
<div class="separator"></div>
<button id="btn-about">About</button>
</div>
<div id="app">
<div id="channels"></div>
<div id="messages"></div>
</div>
<div id="modal">
<div id="overlay"></div>
<div id="dialog"></div>
</div>
</body>
</html>