UNPKG

discord-html-transcripts-fix

Version:

A nicely formatted html transcript generator for discord.js. Bugfix fork with support for the latest discord.js and Components v2.

636 lines (613 loc) 69.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ggSansFont = exports.revealSpoiler = exports.scrollToMessage = exports.mentionPopup = exports.mentionPopupStyles = exports.backToTop = exports.transcriptUtils = exports.searchBar = exports.searchBarStyles = exports.lightbox = exports.lightboxStyles = exports.toc = exports.tocStyles = exports.i18nSwitcher = exports.i18nStyles = exports.dateSeparatorStyles = exports.markdownStyles = exports.filterPanel = exports.filterPanelStyles = exports.userFlagBadges = exports.codeCopyButton = exports.permalinkButton = void 0; // =========================================================================== // scrollToMessage — uses composedPath() so clicks inside Web-Component shadow // DOM (e.g. inside <discord-container>) still find the [data-goto] element. // =========================================================================== exports.scrollToMessage = `document.addEventListener('click',function(e){if(!e||e.defaultPrevented)return; var path=(typeof e.composedPath==='function')?e.composedPath():[]; if(!path.length){var n=e.target;while(n){path.push(n);n=n.parentNode||(n.host);}} var g=null; for(var i=0;i<path.length;i++){var el=path[i];if(el&&el.getAttribute){var v=el.getAttribute('data-goto');if(v){g=v;break;}}} if(!g)return; var r=document.getElementById('m-'+g); if(r){r.scrollIntoView({behavior:'smooth',block:'center'});var prev=r.style.backgroundColor;r.style.transition='background-color 0.5s ease';r.style.backgroundColor='rgba(148,156,247,0.18)';setTimeout(function(){r.style.backgroundColor=prev||'transparent';},1200);} else{console.warn('Message '+g+' not found.');} });`; exports.revealSpoiler = `var s=document.querySelectorAll('.discord-spoiler');s.forEach(function(s){s.setAttribute('role','button');s.setAttribute('tabindex','0');s.setAttribute('aria-label','Reveal spoiler');function reveal(){if(s.classList.contains('discord-spoiler')){s.classList.add('discord-spoiler--revealing');setTimeout(function(){s.classList.remove('discord-spoiler');s.classList.add('discord-spoiler--revealed');s.classList.remove('discord-spoiler--revealing');},220);}}s.addEventListener('click',reveal);s.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();reveal();}});});`; // =========================================================================== // Code-block copy-button — hovers over any <pre>/.dht-codeblock/.discord-highlighted-code // =========================================================================== exports.codeCopyButton = `(function(){ function attach(){ var blocks=document.querySelectorAll('pre, .dht-codeblock, .discord-highlighted-code, .dht-inline-code'); blocks.forEach(function(b){ if(b.tagName==='CODE' && b.parentElement && b.parentElement.tagName==='PRE')return; if(b.dataset.dhtCopyAttached)return; b.dataset.dhtCopyAttached='1'; b.style.position='relative'; var btn=document.createElement('button');btn.type='button';btn.className='dht-copy-btn';btn.title='Copy code';btn.setAttribute('aria-label','Copy code');btn.innerHTML='&#x2398;'; btn.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();var text=b.innerText||b.textContent||'';try{navigator.clipboard.writeText(text);btn.classList.add('dht-copy-btn--ok');btn.innerHTML='&#x2713;';setTimeout(function(){btn.classList.remove('dht-copy-btn--ok');btn.innerHTML='&#x2398;';},1200);}catch(_){}}); b.appendChild(btn); }); } attach(); if(typeof MutationObserver!=='undefined'){var mo=new MutationObserver(attach);mo.observe(document.body,{childList:true,subtree:true});} })();`; // =========================================================================== // Per-message permalink button (top-right on hover) // =========================================================================== exports.permalinkButton = `(function(){ document.addEventListener('mouseenter',function(e){ if(!e.target||!e.target.tagName)return; var t=e.target; if(t.tagName!=='DISCORD-MESSAGE' && t.tagName!=='DISCORD-SYSTEM-MESSAGE')return; if(t.querySelector('.dht-permalink-btn'))return; var btn=document.createElement('button');btn.type='button';btn.className='dht-permalink-btn';btn.title='Copy permalink';btn.setAttribute('aria-label','Copy permalink');btn.innerHTML='&#x1F517;'; btn.addEventListener('click',function(e2){e2.preventDefault();e2.stopPropagation();var url=location.href.split('#')[0]+'#'+t.id;try{navigator.clipboard.writeText(url);btn.classList.add('dht-permalink-btn--ok');setTimeout(function(){btn.classList.remove('dht-permalink-btn--ok');},1000);}catch(_){}}); t.appendChild(btn); },true); })();`; // =========================================================================== // i18n — default dictionaries, used by both server-side renderers and client-side scripts // =========================================================================== const defaultDicts = { en: { edited: 'edited', editedAt: 'Edited at {time}', joined: 'joined the server', pinned: 'pinned {message} to this channel.', pinnedLink: 'a message', boosted: 'boosted the server!', threadStarted: 'started a thread:', changedChannelName: 'changed the channel name:', changedChannelIcon: 'changed the channel icon.', usedCommand: 'used', startedCall: 'started a call.', addedFollow: 'added {target} to follow this channel.', left: 'left.', autoModBlocked: 'AutoMod blocked a message.', serverAlert: 'Server safety alert.', pollEnded: 'A poll ended.', roleSubPurchased: 'subscribed to {role}!', forwardedFrom: 'Forwarded from', searchPlaceholder: 'Search transcript…', ofMatches: 'of', noMatches: 'no matches', backToTop: 'Back to top', tocTitle: 'Participants', statsMessages: '{n} messages', statsParticipants: '{n} participants', statsImages: '{n} images', crossServerReply: 'Message from another server', appBadge: 'APP', botBadge: 'BOT', editHistoryTitle: 'Edit history', editHistoryAt: 'at', voiceMessage: 'Voice message', forwardedMessage: 'Forwarded', languageLabel: 'Language', threadArchived: 'Archived', threadLocked: 'Locked', suppressedEmbeds: '(embeds hidden)', filterPanel: 'Filter', filterKeyword: 'Keyword', filterAuthor: 'Author', filterRole: 'Role', filterFrom: 'From', filterTo: 'To', filterPinned: 'Pinned only', filterHasImage: 'Has image', filterHasEmbed: 'Has embed', filterHasAttachment: 'Has attachment', filterHasComponentV2: 'Has container', filterApply: 'Apply', filterReset: 'Reset', flags: 'Badges', }, de: { edited: 'bearbeitet', editedAt: 'Bearbeitet am {time}', joined: 'ist dem Server beigetreten', pinned: 'hat {message} in diesem Channel angepinnt.', pinnedLink: 'eine Nachricht', boosted: 'hat den Server geboostet!', threadStarted: 'hat einen Thread gestartet:', changedChannelName: 'hat den Channel-Namen geändert:', changedChannelIcon: 'hat das Channel-Icon geändert.', usedCommand: 'hat', startedCall: 'hat einen Anruf gestartet.', addedFollow: 'hat {target} hinzugefügt, um diesem Channel zu folgen.', left: 'hat den Channel verlassen.', autoModBlocked: 'AutoMod hat eine Nachricht blockiert.', serverAlert: 'Sicherheitswarnung.', pollEnded: 'Eine Umfrage wurde beendet.', roleSubPurchased: 'hat {role} abonniert!', forwardedFrom: 'Weitergeleitet von', searchPlaceholder: 'Im Transcript suchen…', ofMatches: 'von', noMatches: 'keine Treffer', backToTop: 'Nach oben', tocTitle: 'Teilnehmer', statsMessages: '{n} Nachrichten', statsParticipants: '{n} Teilnehmer', statsImages: '{n} Bilder', crossServerReply: 'Nachricht von einem anderen Server', appBadge: 'APP', botBadge: 'BOT', editHistoryTitle: 'Bearbeitungs-Verlauf', editHistoryAt: 'am', voiceMessage: 'Sprachnachricht', forwardedMessage: 'Weitergeleitet', languageLabel: 'Sprache', threadArchived: 'Archiviert', threadLocked: 'Gesperrt', suppressedEmbeds: '(Embeds versteckt)', filterPanel: 'Filter', filterKeyword: 'Suchbegriff', filterAuthor: 'Author', filterRole: 'Rolle', filterFrom: 'Von', filterTo: 'Bis', filterPinned: 'Nur angepinnt', filterHasImage: 'Mit Bild', filterHasEmbed: 'Mit Embed', filterHasAttachment: 'Mit Anhang', filterHasComponentV2: 'Mit Container', filterApply: 'Anwenden', filterReset: 'Zurücksetzen', flags: 'Abzeichen', }, }; // User flag emoji map (Discord badge names → emoji approximation) const FLAG_BADGES = { Staff: { emoji: '🛡', label: 'Discord Staff', color: '#5865F2' }, Partner: { emoji: '🤝', label: 'Partnered Server Owner', color: '#5865F2' }, Hypesquad: { emoji: '🎉', label: 'HypeSquad Events', color: '#FBB848' }, BugHunterLevel1: { emoji: '🐛', label: 'Bug Hunter', color: '#3E70DD' }, BugHunterLevel2: { emoji: '🐞', label: 'Bug Hunter Gold', color: '#FFD700' }, HypeSquadOnlineHouse1: { emoji: '💜', label: 'HypeSquad Bravery', color: '#9C84EF' }, HypeSquadOnlineHouse2: { emoji: '💙', label: 'HypeSquad Brilliance', color: '#F47B67' }, HypeSquadOnlineHouse3: { emoji: '💚', label: 'HypeSquad Balance', color: '#45DDC0' }, PremiumEarlySupporter: { emoji: '⭐', label: 'Early Supporter', color: '#FF73FA' }, VerifiedBot: { emoji: '✓', label: 'Verified Bot', color: '#5865F2' }, VerifiedDeveloper: { emoji: '✅', label: 'Early Verified Developer', color: '#5865F2' }, CertifiedModerator: { emoji: '🎖', label: 'Certified Moderator', color: '#5865F2' }, BotHTTPInteractions: { emoji: '🔗', label: 'HTTP Interactions', color: '#5865F2' }, ActiveDeveloper: { emoji: '🔧', label: 'Active Developer', color: '#3BA55D' }, Spammer: { emoji: '🚫', label: 'Spammer', color: '#ED4245' }, Quarantined: { emoji: '⚠', label: 'Quarantined', color: '#ED4245' }, }; exports.userFlagBadges = FLAG_BADGES; function getDict(lang) { if (lang && defaultDicts[lang]) return defaultDicts[lang]; return defaultDicts.en; } exports.getDict = getDict; exports.defaultDicts = defaultDicts; // =========================================================================== // mentionPopup — clickable user/role/channel pills // Reads from window.$discordMessage.{users,roles,channels,profiles}. // Uses safe color validation to block CSS injection. // =========================================================================== exports.mentionPopup = `(function(){ function DATA(){return (typeof globalThis!=='undefined' && globalThis.$discordMessage)||{};} function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});} function safeColor(c,fb){fb=fb||'#5865F2';return typeof c==='string' && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fb;} function t(key){var d=DATA();var lang=d.lang||'en';var dict=(d.i18n&&d.i18n[lang])||(d.i18n&&d.i18n.en)||{};return dict[key]||key;} function fmt(iso){if(!iso)return '';try{var d=new Date(iso);return isNaN(d.getTime())?'':d.toLocaleString(DATA().lang==='de'?'de-DE':'en-US',{dateStyle:'medium',timeStyle:'short'});}catch(_){return '';}} function copyBtn(text,label){if(text==null||text==='')return '';return '<button type="button" class="dht-popup-copy" data-copy="'+esc(text)+'" title="Copy '+esc(label||'value')+'" aria-label="Copy '+esc(label||'value')+'"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>';} var popup=null; function isTriggerInPath(path){ for(var i=0;i<path.length;i++){ var x=path[i];if(!x)continue; if(x.getAttribute && x.getAttribute('data-mention-id'))return true; if(x.tagName && typeof x.tagName==='string'){var tn=x.tagName.toLowerCase();if(tn==='discord-command')return true;} if(x.classList){ if(x.classList.contains('discord-author-info')||x.classList.contains('discord-author-username')||x.classList.contains('discord-author-avatar')||x.classList.contains('discord-application-tag'))return true; } } return false; } function ensure(){ if(popup)return popup; popup=document.createElement('div');popup.className='dht-popup';popup.setAttribute('role','dialog'); popup.innerHTML='<div class="dht-popup-body"></div><button type="button" class="dht-popup-close" aria-label="Close">&times;</button>'; document.body.appendChild(popup); popup.querySelector('.dht-popup-close').addEventListener('click',hide); document.addEventListener('click',function(e){ if(!e||e.defaultPrevented)return; if(!popup||popup.style.display!=='block')return; if(popup.contains(e.target))return; var path=(typeof e.composedPath==='function')?e.composedPath():[]; if(isTriggerInPath(path))return; hide(); }); document.addEventListener('keydown',function(e){if(e.key==='Escape')hide();}); window.addEventListener('resize',hide); return popup; } function hide(){if(popup)popup.style.display='none';} function showAt(el,html){var p=ensure();p.querySelector('.dht-popup-body').innerHTML=html;p.style.display='block';var r=el.getBoundingClientRect();var pr=p.getBoundingClientRect();var pad=8;var top=r.bottom+6;var left=r.left;if(top+pr.height>window.innerHeight-pad)top=Math.max(pad,r.top-pr.height-6);if(left+pr.width>window.innerWidth-pad)left=Math.max(pad,window.innerWidth-pr.width-pad);if(left<pad)left=pad;p.style.top=top+'px';p.style.left=left+'px';} function rolePill(role){var c=safeColor(role&&role.color);return '<span class="dht-role-pill" style="--dht-role:'+c+';">'+esc(role&&role.name?role.name:'@role')+'</span>';} function flagBadgeHtml(flagName){try{var dh=globalThis.__DHT_FLAG_BADGES__||{};var b=dh[flagName];if(b)return '<span class="dht-flag" title="'+esc(b.label)+'" style="color:'+safeColor(b.color)+';">'+esc(b.emoji)+'</span>';}catch(_){};return '<span class="dht-flag" title="'+esc(flagName)+'">'+esc(flagName)+'</span>';} function userHtml(id){ var d=DATA(); var u=(d.users&&d.users[id])||null; var p=(d.profiles&&d.profiles[id])||null; if(!u&&!p)return '<i>?</i><div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'user ID')+'</div>'; var name=(u&&(u.displayName||u.username))||(p&&p.author)||'Unknown'; var username=u&&u.username?'@'+u.username:''; var avatar=(u&&u.avatar)||(p&&p.avatar)||''; var color=safeColor((u&&u.bannerColor)||(p&&p.roleColor),'#dbdee1'); var bot=(u&&u.bot)||(p&&p.bot); var verifiedBot=u&&u.verifiedBot; var roles=u&&Array.isArray(u.roles)&&u.roles.length?u.roles.map(rolePill).join(''):''; var flags=u&&Array.isArray(u.flags)&&u.flags.length?u.flags.filter(function(f){return f!=='VerifiedBot';}).map(flagBadgeHtml).join(''):''; var joinedAt=u&&u.joinedAt?fmt(u.joinedAt):''; var createdAt=u&&u.createdAt?fmt(u.createdAt):''; var botBadge=bot?(' <span class="dht-popup-badge">'+esc(t('botBadge'))+(verifiedBot?' ✓':'')+'</span>'):''; return '' +'<div class="dht-popup-head">' +(avatar?'<img class="dht-popup-avatar" src="'+esc(avatar)+'" alt="">':'<div class="dht-popup-avatar dht-popup-avatar--ph"></div>') +'<div class="dht-popup-headtext">' +'<div class="dht-popup-name" style="color:'+color+';">'+esc(name)+botBadge+copyBtn(name,'name')+'</div>' +(username?'<div class="dht-popup-sub">'+esc(username)+copyBtn(username,'username')+'</div>':'') +'</div>' +'</div>' +(flags?'<div class="dht-popup-section"><div class="dht-popup-label">'+esc(t('flags'))+'</div><div class="dht-popup-flags">'+flags+'</div></div>':'') +(roles?'<div class="dht-popup-section"><div class="dht-popup-label">Roles</div><div class="dht-popup-roles">'+roles+'</div></div>':'') +(joinedAt?'<div class="dht-popup-row"><span class="dht-popup-label">Server</span><span>'+esc(joinedAt)+'</span></div>':'') +(createdAt?'<div class="dht-popup-row"><span class="dht-popup-label">Account</span><span>'+esc(createdAt)+'</span></div>':'') +'<div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'user ID')+'</div>'; } function roleHtml(id){ var r=(DATA().roles||{})[id]; if(!r)return '<i>?</i><div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'role ID')+'</div>'; var color=safeColor(r.color,'#dbdee1'); return '<div class="dht-popup-head"><div class="dht-popup-rolemark" style="background:'+color+';"></div><div class="dht-popup-headtext"><div class="dht-popup-name" style="color:'+color+';">@'+esc(r.name)+copyBtn('@'+r.name,'role name')+'</div>'+(r.color?'<div class="dht-popup-sub"><code>'+esc(r.color)+'</code>'+copyBtn(r.color,'color')+'</div>':'')+'</div></div>' +(r.memberCount!=null?'<div class="dht-popup-row"><span class="dht-popup-label">Members</span><span>'+esc(r.memberCount)+'</span></div>':'') +(r.position!=null?'<div class="dht-popup-row"><span class="dht-popup-label">Position</span><span>'+esc(r.position)+'</span></div>':'') +(r.hoist?'<div class="dht-popup-row"><span class="dht-popup-label">Hoisted</span><span>Yes</span></div>':'') +(r.mentionable?'<div class="dht-popup-row"><span class="dht-popup-label">Mentionable</span><span>Yes</span></div>':'') +(r.managed?'<div class="dht-popup-row"><span class="dht-popup-label">Managed</span><span>Bot/Integration</span></div>':'') +'<div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'role ID')+'</div>'; } function channelHtml(id){ var c=(DATA().channels||{})[id]; if(!c)return '<i>Channel</i><div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'channel ID')+'</div>'; return '<div class="dht-popup-head"><div class="dht-popup-rolemark" style="background:#5865F2;">#</div><div class="dht-popup-headtext"><div class="dht-popup-name">#'+esc(c.name)+copyBtn('#'+c.name,'channel')+'</div>'+(c.parent?'<div class="dht-popup-sub">in '+esc(c.parent)+'</div>':'')+'</div></div>' +(c.topic?'<div class="dht-popup-section"><div class="dht-popup-label">Topic</div><div>'+esc(c.topic)+'</div></div>':'') +(c.nsfw?'<div class="dht-popup-row"><span class="dht-popup-label">NSFW</span><span>Yes</span></div>':'') +'<div class="dht-popup-id">ID: <code>'+esc(id)+'</code>'+copyBtn(id,'channel ID')+'</div>'; } function slashHtml(el){ var cmd=el.getAttribute('data-slash-cmd')||el.getAttribute('command')||'/?'; var by=el.getAttribute('data-slash-by')||''; var byId=el.getAttribute('data-slash-by-id')||el.getAttribute('profile')||''; var optsJson=el.getAttribute('data-slash-options')||'[]'; var opts; try{opts=JSON.parse(optsJson);}catch(_){opts=[];} if(!Array.isArray(opts))opts=[]; var paramsHtml=opts.length ? '<div class="dht-popup-section"><div class="dht-popup-label">Parameters</div><div class="dht-slash-params">' +opts.map(function(o){return '<div class="dht-slash-param"><span class="dht-slash-param-name">'+esc(o.name)+'</span><span class="dht-slash-param-val">'+esc(o.value)+'</span>'+copyBtn(o.value,o.name)+'</div>';}).join('') +'</div></div>' : '<div class="dht-popup-sub" style="margin-top:8px">No parameters</div>'; return '<div class="dht-popup-head">' +'<div class="dht-popup-rolemark" style="background:#5865F2;font-family:Consolas,Menlo,monospace;font-size:18px;">/</div>' +'<div class="dht-popup-headtext">' +'<div class="dht-popup-name" style="font-family:Consolas,Menlo,monospace;color:#5865F2">'+esc(cmd)+copyBtn(cmd,'command')+'</div>' +(by?'<div class="dht-popup-sub">used by '+esc(by)+'</div>':'') +'</div>' +'</div>' +paramsHtml +(byId?'<div class="dht-popup-id">User ID: <code>'+esc(byId)+'</code>'+copyBtn(byId,'user ID')+'</div>':''); } function findAncestor(path,pred){for(var i=0;i<path.length;i++){if(pred(path[i]))return path[i];}return null;} function isAuthorPath(path){ for(var i=0;i<path.length;i++){ var x=path[i];if(!x||!x.classList)continue; if(x.classList.contains('discord-author-info')||x.classList.contains('discord-author-username')||x.classList.contains('discord-author-avatar')||x.classList.contains('discord-application-tag'))return true; } return false; } // Copy-button delegated handler (any .dht-popup-copy click — inside popup or anywhere else) document.addEventListener('click',function(e){ var t=e.target; if(!t)return; var btn=null; if(t.closest)btn=t.closest('.dht-popup-copy'); if(!btn)return; var text=btn.getAttribute('data-copy')||''; if(!text)return; e.preventDefault();e.stopPropagation(); function done(){btn.classList.add('dht-popup-copy--ok');setTimeout(function(){btn.classList.remove('dht-popup-copy--ok');},900);} try{ if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(text).then(done,function(){fallback();});} else fallback(); }catch(_){fallback();} function fallback(){try{var ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);done();}catch(_2){}} },true); // Main trigger handler document.addEventListener('click',function(e){ if(!e||e.defaultPrevented)return; var path=(typeof e.composedPath==='function')?e.composedPath():[]; if(!path.length){var n=e.target;while(n){path.push(n);n=n.parentNode||(n.host);}} // 0) skip if click is on a copy button (handled in capture phase above) for(var z=0;z<path.length;z++){var zz=path[z];if(zz&&zz.classList&&zz.classList.contains('dht-popup-copy'))return;} // 1) explicit mention pills with data-mention-id (users/roles/channels via markdown) var pill=findAncestor(path,function(x){return x&&x.getAttribute&&x.getAttribute('data-mention-id');}); if(pill){ var tp=pill.getAttribute('data-mention-type');var id=pill.getAttribute('data-mention-id'); if(!tp||!id)return; e.preventDefault();e.stopPropagation(); var html=''; if(tp==='user')html=userHtml(id); else if(tp==='role')html=roleHtml(id); else if(tp==='channel')html=channelHtml(id); else return; showAt(pill,html);return; } // 2) slash command pill — discord-command web component var cmdEl=findAncestor(path,function(x){return x&&x.tagName&&typeof x.tagName==='string'&&x.tagName.toLowerCase()==='discord-command';}); if(cmdEl){ e.preventDefault();e.stopPropagation(); showAt(cmdEl,slashHtml(cmdEl));return; } // 3) author area inside <discord-message> shadow DOM (avatar, name, BOT/APP tag) if(isAuthorPath(path)){ var msgEl=findAncestor(path,function(x){return x&&x.tagName&&typeof x.tagName==='string'&&x.tagName.toLowerCase()==='discord-message';}); if(msgEl){ var uid=msgEl.getAttribute('profile')||msgEl.getAttribute('data-author-id'); if(uid){e.preventDefault();e.stopPropagation();showAt(msgEl,userHtml(uid));return;} } } }); })();`; exports.mentionPopupStyles = ` /* Mention pills — Discord-style. Always render correctly (no web component dependency). */ .dht-mention{ display:inline-block; background-color:rgba(88,101,242,.18); color:#c9cdfb; padding:0 4px; border-radius:3px; font-weight:500; cursor:pointer; text-decoration:none; border:1px solid transparent; transition:filter .15s ease,background-color .15s ease; white-space:nowrap; } .dht-mention:hover{filter:brightness(1.25);background-color:rgba(88,101,242,.32)} .dht-mention--user{background-color:rgba(88,101,242,.18);color:#c9cdfb} .dht-mention--channel{background-color:rgba(88,101,242,.15);color:#c9cdfb} .dht-mention--command{background-color:rgba(88,101,242,.22);color:#c9cdfb;font-family:Consolas,Menlo,monospace} .dht-mention--highlight{background-color:rgba(255,212,0,.25);color:#f9d949} /* Role mentions carry inline style (role color + faded bg) — leave their color/background alone, only set base shape */ .dht-mention--role{font-weight:500} /* Legacy <discord-mention> styling (in case some old code still emits it) */ discord-mention{display:inline-block;background-color:rgba(88,101,242,.3);color:#c9cdfb;padding:0 4px;border-radius:3px;font-weight:500;cursor:pointer} discord-mention:not(:defined)[type="user"]::before, discord-mention:not(:defined)[type="role"]::before, discord-mention:not(:defined)[highlight]::before{content:"@"} discord-mention:not(:defined)[type="channel"]::before{content:"#"} discord-mention:not(:defined)[type="command"]::before{content:"/"} .dht-popup{position:fixed;display:none;max-width:340px;min-width:240px;max-height:80vh;overflow-y:auto;background:#1e1f22;color:#dbdee1;border:1px solid #2b2d31;border-radius:10px;padding:14px 16px 12px;box-shadow:0 12px 32px rgba(0,0,0,.55),0 2px 6px rgba(0,0,0,.35);font-family:"gg sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.4;z-index:99999} .dht-popup-headtext{min-width:0} .dht-popup-close{position:absolute;top:6px;right:8px;background:none;border:none;color:#949ba4;cursor:pointer;font-size:20px;line-height:1;padding:4px 8px;border-radius:4px} .dht-popup-close:hover{background:rgba(255,255,255,.06);color:#dbdee1} .dht-popup-head{display:flex;align-items:center;gap:10px;margin-right:18px} .dht-popup-avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;flex:none} .dht-popup-avatar--ph{background:#313338} .dht-popup-rolemark{width:32px;height:32px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff} .dht-popup-headtext{min-width:0} .dht-popup-name{font-weight:600;font-size:16px;word-break:break-word} .dht-popup-sub{font-size:12px;color:#949ba4;word-break:break-all} .dht-popup-badge{background:#5865F2;color:#fff;font-size:10px;padding:1px 6px;border-radius:4px;margin-left:6px;vertical-align:middle} .dht-popup-section{margin-top:10px} .dht-popup-row{display:flex;justify-content:space-between;gap:12px;margin-top:6px;font-size:13px} .dht-popup-label{color:#949ba4;font-size:11px;text-transform:uppercase;letter-spacing:.04em;font-weight:700} .dht-popup-roles{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px} .dht-role-pill{display:inline-flex;align-items:center;background:color-mix(in srgb,var(--dht-role) 18%,transparent);color:var(--dht-role);border:1px solid color-mix(in srgb,var(--dht-role) 40%,transparent);padding:1px 8px;border-radius:999px;font-size:11px;line-height:1.5} .dht-popup-id{margin-top:10px;padding-top:8px;border-top:1px solid #2b2d31;font-size:11px;color:#6e727a;display:flex;align-items:center;gap:6px} .dht-popup-id code{background:rgba(255,255,255,.05);padding:1px 4px;border-radius:3px} /* Copy buttons inside the popup */ .dht-popup-copy{display:inline-flex;align-items:center;justify-content:center;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.06);color:#b5bac1;border-radius:4px;width:22px;height:22px;padding:0;margin-left:6px;cursor:pointer;vertical-align:middle;transition:background .15s ease,color .15s ease,border-color .15s ease;flex:none} .dht-popup-copy:hover{background:rgba(255,255,255,.1);color:#fff} .dht-popup-copy:focus-visible{outline:2px solid #5865F2;outline-offset:2px} .dht-popup-copy--ok{background:#3ba55d!important;color:#fff!important;border-color:#3ba55d!important} .dht-popup-copy svg{display:block} .dht-popup-name{display:flex;align-items:center;gap:0;flex-wrap:wrap} .dht-popup-sub{display:flex;align-items:center;gap:0;flex-wrap:wrap} /* Slash command popup — parameter list */ .dht-slash-params{display:flex;flex-direction:column;gap:4px;margin-top:4px} .dht-slash-param{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.04);padding:4px 8px;border-radius:4px;font-size:12px} .dht-slash-param-name{color:#949ba4;font-family:Consolas,Menlo,monospace} .dht-slash-param-name::after{content:":"} .dht-slash-param-val{color:#dbdee1;font-family:Consolas,Menlo,monospace;word-break:break-all;flex:1} /* Make message author area look clickable (skyra shadow DOM elements bubble into the host) */ discord-message .discord-author-username, discord-message .discord-author-info, discord-message [slot="author-image"], discord-message .discord-author-avatar, discord-message .discord-application-tag{cursor:pointer} discord-message .discord-author-username:hover, discord-message .discord-author-info:hover{text-decoration:underline} /* Slash command pill — clickable affordance */ discord-command.dht-slash-clickable, discord-command{cursor:pointer;transition:filter .15s ease} discord-command.dht-slash-clickable:hover, discord-command:hover{filter:brightness(1.2)} `; // =========================================================================== // Back-to-top // =========================================================================== exports.backToTop = `(function(){var btn=document.createElement('button');btn.className='dht-back-top';btn.type='button';var DATA=globalThis.$discordMessage||{};var dict=(DATA.i18n&&(DATA.i18n[DATA.lang]||DATA.i18n.en))||{};btn.title=dict.backToTop||'Back to top';btn.setAttribute('aria-label',dict.backToTop||'Back to top');btn.innerHTML='&#x25B2;';document.body.appendChild(btn);btn.addEventListener('click',function(){window.scrollTo({top:0,behavior:'smooth'});});function toggle(){btn.style.opacity=window.scrollY>400?'1':'0';btn.style.pointerEvents=window.scrollY>400?'auto':'none';}window.addEventListener('scroll',toggle,{passive:true});toggle();})();`; exports.transcriptUtils = `.dht-back-top{position:fixed;bottom:24px;right:24px;width:44px;height:44px;border-radius:50%;border:none;background:#5865F2;color:#fff;font-size:16px;cursor:pointer;box-shadow:0 6px 18px rgba(0,0,0,.45);opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease;z-index:9999}.dht-back-top:hover{transform:translateY(-2px)}`; // =========================================================================== // Search — handled inside the unified TOC panel (see exports.toc). // These exports stay for backwards compatibility with anyone wiring them // individually; both are inert (no DOM injection). Search-hit styles still // live here because the TOC IIFE creates <mark class="dht-hit"> nodes. // =========================================================================== exports.searchBar = '/* searchBar: merged into TOC */'; exports.searchBarStyles = `.dht-hit{background:rgba(255,228,0,.35);color:inherit;border-radius:2px}.dht-hit--active{background:rgba(255,140,0,.7);color:#000}`; // =========================================================================== // Image lightbox — click images to fullscreen, arrow keys to navigate // =========================================================================== exports.lightbox = `(function(){ var overlay=null,images=[],idx=0; function collect(){images=Array.from(document.querySelectorAll('img')).filter(function(i){return !i.closest('.dht-popup')&&!i.classList.contains('dht-popup-avatar')&&i.naturalWidth>40;});} function build(){overlay=document.createElement('div');overlay.className='dht-lightbox';overlay.setAttribute('role','dialog');overlay.setAttribute('aria-modal','true');overlay.setAttribute('tabindex','-1');overlay.innerHTML='<button class="dht-lb-close" aria-label="Close">&times;</button><button class="dht-lb-prev" aria-label="Previous">&#x2039;</button><img class="dht-lb-img" alt=""><button class="dht-lb-next" aria-label="Next">&#x203A;</button><div class="dht-lb-caption"></div>';document.body.appendChild(overlay);overlay.addEventListener('click',function(e){if(e.target===overlay||e.target.classList.contains('dht-lb-close'))close();else if(e.target.classList.contains('dht-lb-prev'))nav(-1);else if(e.target.classList.contains('dht-lb-next'))nav(1);});} var imagesCacheDirty=true; function open(src,alt){if(!overlay)build();if(imagesCacheDirty){collect();imagesCacheDirty=false;}idx=images.findIndex(function(i){return i.src===src;});if(idx<0)idx=0;render();overlay.style.display='flex';} function nav(d){if(!images.length)return;idx=(idx+d+images.length)%images.length;render();} function render(){var img=images[idx];if(!img)return;overlay.querySelector('.dht-lb-img').src=img.src;overlay.querySelector('.dht-lb-caption').textContent=img.alt||'';} function close(){if(overlay)overlay.style.display='none';} document.addEventListener('click',function(e){if(!e||e.defaultPrevented)return;var img=e.target;if(img&&img.tagName==='IMG'&&img.naturalWidth>40&&!img.closest('.dht-popup')&&!img.classList.contains('dht-popup-avatar')&&!img.closest('.dht-toc')&&!img.closest('.dht-search')&&!img.closest('.dht-filter')&&!img.closest('discord-message-reply')&&!img.closest('discord-thread')&&!img.classList.contains('dht-emoji')){e.preventDefault();open(img.src,img.alt);}}); document.addEventListener('keydown',function(e){if(!overlay||overlay.style.display!=='flex')return;if(e.key==='Escape')close();else if(e.key==='ArrowLeft')nav(-1);else if(e.key==='ArrowRight')nav(1);}); })();`; exports.lightboxStyles = `.dht-lightbox{position:fixed;inset:0;background:rgba(0,0,0,.92);display:none;align-items:center;justify-content:center;z-index:100000;flex-direction:column}.dht-lb-img{max-width:90vw;max-height:80vh;object-fit:contain;box-shadow:0 4px 20px rgba(0,0,0,.6);border-radius:6px}.dht-lb-close,.dht-lb-prev,.dht-lb-next{position:absolute;background:rgba(0,0,0,.4);color:#fff;border:none;cursor:pointer;font-size:28px;width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center}.dht-lb-close:hover,.dht-lb-prev:hover,.dht-lb-next:hover{background:rgba(255,255,255,.15)}.dht-lb-close{top:20px;right:20px}.dht-lb-prev{left:20px;top:50%;transform:translateY(-50%)}.dht-lb-next{right:20px;top:50%;transform:translateY(-50%)}.dht-lb-caption{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);color:#dbdee1;font-family:"gg sans",sans-serif;font-size:14px;padding:6px 14px;background:rgba(0,0,0,.5);border-radius:4px;max-width:80%}`; // =========================================================================== // TOC / Sidebar — list participants, click jumps to their first message // =========================================================================== exports.toc = `(function(){ var DATA=globalThis.$discordMessage||{}; var dict=(DATA.i18n&&(DATA.i18n[DATA.lang]||DATA.i18n.en))||{}; var users=DATA.users||DATA.profiles||{}; var roles=DATA.roles||{}; var userIds=Object.keys(users).filter(function(x){return /^\\d+$/.test(x);}).sort(function(a,b){var na=(users[a].displayName||users[a].author||users[a].username||'').toLowerCase();var nb=(users[b].displayName||users[b].author||users[b].username||'').toLowerCase();return na.localeCompare(nb);}); var roleIds=Object.keys(roles).filter(function(x){return /^\\d+$/.test(x);}); function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});} function safeColor(c,fb){fb=fb||'#dbdee1';return typeof c==='string' && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fb;} function safeUrl(u){if(typeof u!=='string'||!u)return '';if(/^https?:\\/\\//i.test(u))return u;return '';} function firstMessageOf(uid){var m=document.querySelector('discord-message[profile="'+uid+'"],[data-profile="'+uid+'"]');return m?m.id:null;} function parseId(v){if(!v)return null;var t=v.trim();if(/^\\d{15,21}$/.test(t))return t;var m=t.match(/\\((\\d{15,21})\\)/);return m?m[1]:null;} function authorHasRole(authorId,roleId){var u=users[authorId];if(!u||!Array.isArray(u.roles))return false;for(var i=0;i<u.roles.length;i++){if(u.roles[i].id===roleId)return true;}return false;} var btn=document.createElement('button');btn.className='dht-toc-toggle';btn.type='button';btn.title=dict.tocTitle||'Participants';btn.setAttribute('aria-label',dict.tocTitle||'Participants');btn.innerHTML='&#x2630;';document.body.appendChild(btn); var panel=document.createElement('aside');panel.className='dht-toc';panel.setAttribute('aria-hidden','true'); // Header var headerHtml='<header><h3>'+esc(dict.tocTitle||'Participants')+'</h3><button class="dht-toc-close" type="button" aria-label="Close">&times;</button></header>'; // Search field — keyword highlight + participant name filter var searchHtml='<div class="dht-toc-search-wrap"><span class="dht-toc-search-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg></span><input type="search" class="dht-toc-search" placeholder="'+esc(dict.searchPlaceholder||'Search…')+'"><span class="dht-toc-counter" aria-live="polite"></span><button class="dht-toc-srch-btn" type="button" data-act="prev" title="Previous" aria-label="Previous match">&#x25B2;</button><button class="dht-toc-srch-btn" type="button" data-act="next" title="Next" aria-label="Next match">&#x25BC;</button></div>'; // Participants list — collapsible, open by default var listHtml='<details class="dht-toc-section" open><summary>'+esc(dict.tocTitle||'Participants')+'</summary><ul class="dht-toc-list" role="list">'; if(userIds.length>0){ userIds.forEach(function(id){ var u=users[id];var name=u.displayName||u.author||u.username||'Unknown';var avatar=safeUrl(u.avatar||'');var color=safeColor(u.bannerColor||u.roleColor,'#dbdee1'); listHtml+='<li data-uid="'+esc(id)+'" data-name="'+esc(String(name).toLowerCase())+'" tabindex="0" role="button">'+(avatar?'<img src="'+esc(avatar)+'" alt="" loading="lazy">':'<span class="dht-toc-ph"></span>')+'<span class="dht-toc-name" style="color:'+color+'">'+esc(name)+'</span></li>'; }); } listHtml+='</ul></details>'; // Filter form — replaces the standalone bottom-right magnifying glass. Open by default. var filterHtml='<details class="dht-toc-section dht-toc-filter-details" open><summary>'+esc(dict.filterPanel||'Filter')+'</summary><div class="dht-toc-filter-body">'; filterHtml+='<label class="dht-f-field">'+esc(dict.filterAuthor||'Author')+'<input list="dht-f-authors" class="dht-f-author" placeholder="@user / ID / name"></label>'; filterHtml+='<datalist id="dht-f-authors">'+userIds.map(function(id){var u=users[id];return '<option value="'+esc(u.displayName||u.author||u.username||'')+' ('+esc(id)+')">';}).join('')+'</datalist>'; filterHtml+='<label class="dht-f-field">'+esc(dict.filterRole||'Role')+'<input list="dht-f-roles" class="dht-f-role" placeholder="@role / ID / name"></label>'; filterHtml+='<datalist id="dht-f-roles">'+roleIds.map(function(id){var r=roles[id];return '<option value="'+esc(r.name||'')+' ('+esc(id)+')">';}).join('')+'</datalist>'; filterHtml+='<div class="dht-f-grid"><label class="dht-f-field">'+esc(dict.filterFrom||'From')+'<input type="datetime-local" class="dht-f-from"></label><label class="dht-f-field">'+esc(dict.filterTo||'To')+'<input type="datetime-local" class="dht-f-to"></label></div>'; filterHtml+='<div class="dht-f-checks">'; filterHtml+='<label><input type="checkbox" class="dht-f-pinned"> '+esc(dict.filterPinned||'Pinned only')+'</label>'; filterHtml+='<label><input type="checkbox" class="dht-f-image"> '+esc(dict.filterHasImage||'Has image')+'</label>'; filterHtml+='<label><input type="checkbox" class="dht-f-embed"> '+esc(dict.filterHasEmbed||'Has embed')+'</label>'; filterHtml+='<label><input type="checkbox" class="dht-f-attach"> '+esc(dict.filterHasAttachment||'Has attachment')+'</label>'; filterHtml+='<label><input type="checkbox" class="dht-f-v2"> '+esc(dict.filterHasComponentV2||'Has container')+'</label>'; filterHtml+='</div>'; filterHtml+='<div class="dht-f-actions"><button type="button" class="dht-f-apply">'+esc(dict.filterApply||'Apply')+'</button><button type="button" class="dht-f-reset">'+esc(dict.filterReset||'Reset')+'</button></div>'; filterHtml+='</div></details>'; panel.innerHTML=headerHtml+searchHtml+listHtml+filterHtml;document.body.appendChild(panel); function syncToggleVisibility(open){btn.style.opacity=open?'0':'1';btn.style.pointerEvents=open?'none':'auto';btn.setAttribute('aria-expanded',open?'true':'false');} function openPanel(){panel.classList.add('dht-toc--open');panel.setAttribute('aria-hidden','false');syncToggleVisibility(true);} function closePanel(){panel.classList.remove('dht-toc--open');panel.setAttribute('aria-hidden','true');syncToggleVisibility(false);} btn.addEventListener('click',function(){if(panel.classList.contains('dht-toc--open'))closePanel();else openPanel();}); panel.querySelector('.dht-toc-close').addEventListener('click',closePanel); function activate(li){var uid=li.getAttribute('data-uid');if(!uid||!/^\\d+$/.test(uid))return;var mid=firstMessageOf(uid);if(mid){var el=document.getElementById(mid);if(el){el.scrollIntoView({behavior:'smooth',block:'center'});var prev=el.style.backgroundColor;el.style.backgroundColor='rgba(148,156,247,0.18)';setTimeout(function(){el.style.backgroundColor=prev||'transparent';},1500);}}} panel.querySelectorAll('.dht-toc-list li').forEach(function(li){li.addEventListener('click',function(){activate(li);});li.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();activate(li);}});}); // Keyword highlight + participant filter (driven by the top search input) var input=panel.querySelector('.dht-toc-search'); var counter=panel.querySelector('.dht-toc-counter'); var matches=[],idx=-1; function clearMatches(){matches.forEach(function(m){var p=m.parentNode;if(!p)return;var tn=document.createTextNode(m.textContent);p.replaceChild(tn,m);p.normalize();});matches=[];idx=-1;counter.textContent='';} function walk(node,re,acc){if(node.nodeType===3){var text=node.nodeValue;if(!re.test(text))return;re.lastIndex=0;var frag=document.createDocumentFragment();var last=0;var m;while((m=re.exec(text))!==null){if(m.index>last)frag.appendChild(document.createTextNode(text.slice(last,m.index)));var span=document.createElement('mark');span.className='dht-hit';span.textContent=m[0];frag.appendChild(span);acc.push(span);last=re.lastIndex;}if(last<text.length)frag.appendChild(document.createTextNode(text.slice(last)));node.parentNode.replaceChild(frag,node);}else if(node.nodeType===1){if(node.classList&&(node.classList.contains('dht-toc')||node.classList.contains('dht-popup')||node.tagName==='SCRIPT'||node.tagName==='STYLE'))return;for(var i=node.childNodes.length-1;i>=0;i--)walk(node.childNodes[i],re,acc);}} function goTo(){matches.forEach(function(m,i){m.classList.toggle('dht-hit--active',i===idx);});if(idx>=0&&matches[idx]){matches[idx].scrollIntoView({behavior:'smooth',block:'center'});counter.textContent=(idx+1)+' '+(dict.ofMatches||'of')+' '+matches.length;}} function applySearch(q){ clearMatches(); var lowerQ=(q||'').trim().toLowerCase(); panel.querySelectorAll('.dht-toc-list li').forEach(function(li){var n=li.getAttribute('data-name')||'';li.style.display=(!lowerQ||n.indexOf(lowerQ)!==-1)?'':'none';}); if(!lowerQ||lowerQ.length<2){counter.textContent='';return;} var re=new RegExp(lowerQ.replace(/[.*+?^\${}()|[\\]\\\\]/g,function(c){return '\\\\'+c;}),'gi'); walk(document.body,re,matches); if(matches.length){idx=0;goTo();}else{counter.textContent=dict.noMatches||'no matches';} } input.addEventListener('input',function(){applySearch(input.value);}); panel.querySelectorAll('.dht-toc-srch-btn').forEach(function(b){b.addEventListener('click',function(){var act=b.getAttribute('data-act');if(!matches.length)return;if(act==='next')idx=(idx+1)%matches.length;else if(act==='prev')idx=(idx-1+matches.length)%matches.length;goTo();});}); input.addEventListener('keydown',function(e){if(e.key==='Enter'){if(matches.length){idx=(idx+(e.shiftKey?-1:1)+matches.length)%matches.length;goTo();}e.preventDefault();}}); // Filter form — author / role / date / has-flags. Replaces the old standalone .dht-filter aside. function applyFilter(){ var authorIdRaw=parseId(panel.querySelector('.dht-f-author').value); var authorTextRaw=(panel.querySelector('.dht-f-author').value||'').trim().toLowerCase(); var roleIdRaw=parseId(panel.querySelector('.dht-f-role').value); var roleTextRaw=(panel.querySelector('.dht-f-role').value||'').trim().toLowerCase(); var fromVal=panel.querySelector('.dht-f-from').value; var toVal=panel.querySelector('.dht-f-to').value; var fromMs=fromVal?new Date(fromVal).getTime():null; var toMs=toVal?new Date(toVal).getTime():null; var needPinned=panel.querySelector('.dht-f-pinned').checked; var needImage=panel.querySelector('.dht-f-image').checked; var needEmbed=panel.querySelector('.dht-f-embed').checked; var needAttach=panel.querySelector('.dht-f-attach').checked; var needV2=panel.querySelector('.dht-f-v2').checked; var matchedRoleId=null; if(roleIdRaw){matchedRoleId=roleIdRaw;} else if(roleTextRaw){var exact=null,sub=null;for(var rid in roles){if(Object.prototype.hasOwnProperty.call(roles,rid)){var rn=(roles[rid].name||'').toLowerCase();if(rn===roleTextRaw){exact=rid;break;}else if(!sub && rn.indexOf(roleTextRaw)!==-1){sub=rid;}}}matchedRoleId=exact||sub;} var matchedAuthorId=authorIdRaw||null; var msgs=document.querySelectorAll('discord-message, discord-system-message'); for(var i=0;i<msgs.length;i++){ var m=msgs[i];var ok=true; var authorId=m.getAttribute('data-author-id')||''; var authorName=(m.getAttribute('data-author-name')||'').toLowerCase(); var rolesAttr=m.getAttribute('data-roles')||''; var tsStr=m.getAttribute('data-timestamp'); var ts=tsStr?parseInt(tsStr,10):0; var pinned=m.getAttribute('data-pinned')==='true'; var hasImg=m.getAttribute('data-has-image')==='true'; var hasEmb=m.getAttribute('data-has-embed')==='true'; var hasAtt=m.getAttribute('data-has-attachment')==='true'; var hasV2=m.getAttribute('data-has-component-v2')==='true'; if(matchedAuthorId && authorId!==matchedAuthorId)ok=false; if(ok && !matchedAuthorId && !authorIdRaw && authorTextRaw && authorName.indexOf(authorTextRaw)===-1)ok=false; if(ok && matchedRoleId){var rIds=rolesAttr.split(',');if(rIds.indexOf(matchedRoleId)===-1 && !authorHasRole(authorId,matchedRoleId))ok=false;} if(ok && fromMs){if(!ts || ts<fromMs)ok=false;} if(ok && toMs){if(!ts || ts>toMs)ok=false;} if(ok && needPinned && !pinned)ok=false; if(ok && needImage && !hasImg)ok=false; if(ok && needEmbed && !hasEmb)ok=false; if(ok && needAttach && !hasAtt)ok=false; if(ok && needV2 && !hasV2)ok=false; m.style.display=ok?'':'none'; } var seps=document.querySelectorAll('.dht-date-sep'); for(var s=0;s<seps.length;s++){var sep=seps[s];var next=sep.nextElementSibling;var hasVis=false;while(next && !next.classList.contains('dht-date-sep')){if(next.style.display!=='none' && (next.tagName==='DISCORD-MESSAGE' || next.tagName==='DISCORD-SYSTEM-MESSAGE')){hasVis=true;break;}next=next.nextElementSibling;}sep.style.display=hasVis?'':'none';} } function resetFilter(){panel.querySelectorAll('.dht-toc-filter-body input').forEach(function(i){if(i.type==='checkbox')i.checked=false;else i.value='';});document.querySelectorAll('discord-message, discord-system-message, .dht-date-sep').forEach(function(m){m.style.display='';});} panel.querySelector('.dht-f-apply').addEventListener('click',applyFilter); panel.querySelector('.dht-f-reset').addEventListener('click',resetFilter); panel.querySelector('.dht-toc-filter-body').addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault();applyFilter();}}); try{globalThis.__DHT_FLAG_BADGES__=${JSON.stringify(FLAG_BADGES)};}catch(_){} // Ctrl+F / Cmd+F opens the panel and focuses the search input document.addEventListener('keydown',function(e){ if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='f'){ e.preventDefault();openPanel();setTimeout(function(){input.focus();input.select();},20); }else if(e.key==='Escape'&&panel.classList.contains('dht-toc--open')){ if(document.activeElement===input&&input.value){input.value='';applySearch('');} else closePanel(); } }); })();`; exports.tocStyles = `.dht-toc-toggle{position:fixed;top:16px;right:16px;width:40px;height:40px;border-radius:50%;border:none;background:#2b2d31;color:#fff;font-size:16px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.4);z-index:9998}.dht-toc-toggle:hover{background:#5865F2} .dht-toc{position:fixed;top:0;right:-340px;left:auto;width:320px;height:100vh;background:#1e1f22;border-left:1px solid #2b2d31;transition:right .25s ease;z-index:9997;overflow-y:auto;font-family:"gg sans",sans-serif;color:#dbdee1;padding:16px;box-sizing:border-box} .dht-toc--open{right:0} .dht-toc header{display:flex;justify-content