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
JavaScript
"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='⎘';
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='✓';setTimeout(function(){btn.classList.remove('dht-copy-btn--ok');btn.innerHTML='⎘';},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='🔗';
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 ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[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">×</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='▲';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">×</button><button class="dht-lb-prev" aria-label="Previous">‹</button><img class="dht-lb-img" alt=""><button class="dht-lb-next" aria-label="Next">›</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 ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[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='☰';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">×</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">▲</button><button class="dht-toc-srch-btn" type="button" data-act="next" title="Next" aria-label="Next match">▼</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