discord-html-transcripts-fix 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,224 +1,697 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ggSansFont = exports.revealSpoiler = exports.scrollToMessage = exports.mentionPopup = exports.mentionPopupStyles = exports.backToTop = exports.transcriptUtils = void 0;
4
-
5
- /*
6
- // === scrollToMessage source ===
7
- // Whenever the user clicks on element with data-goto attribute, scroll to that message
8
- document.addEventListener('click', (e) => {
9
- const target = e.target;
10
- if(!target) return;
11
- const goto = target?.getAttribute('data-goto');
12
- if (goto) {
13
- const message = document.getElementById(`m-${goto}`);
14
- if (message) {
15
- message.scrollIntoView({ behavior: 'smooth', block: 'center' });
16
- message.style.backgroundColor = 'rgba(148, 156, 247, 0.1)';
17
- message.style.transition = 'background-color 0.5s ease';
18
- setTimeout(() => { message.style.backgroundColor = 'transparent'; }, 1000);
19
- } else {
20
- console.warn(`Message ${goto} not found.`);
21
- }
3
+ 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;
4
+
5
+ // ===========================================================================
6
+ // scrollToMessage — uses composedPath() so clicks inside Web-Component shadow
7
+ // DOM (e.g. inside <discord-container>) still find the [data-goto] element.
8
+ // ===========================================================================
9
+ exports.scrollToMessage = `document.addEventListener('click',function(e){if(!e||e.defaultPrevented)return;
10
+ var path=(typeof e.composedPath==='function')?e.composedPath():[];
11
+ if(!path.length){var n=e.target;while(n){path.push(n);n=n.parentNode||(n.host);}}
12
+ var g=null;
13
+ 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;}}}
14
+ if(!g)return;
15
+ var r=document.getElementById('m-'+g);
16
+ 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);}
17
+ else{console.warn('Message '+g+' not found.');}
18
+ });`;
19
+
20
+ 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();}});});`;
21
+
22
+ // ===========================================================================
23
+ // Code-block copy-button — hovers over any <pre>/.dht-codeblock/.discord-highlighted-code
24
+ // ===========================================================================
25
+ exports.codeCopyButton = `(function(){
26
+ function attach(){
27
+ var blocks=document.querySelectorAll('pre, .dht-codeblock, .discord-highlighted-code, .dht-inline-code');
28
+ blocks.forEach(function(b){
29
+ if(b.tagName==='CODE' && b.parentElement && b.parentElement.tagName==='PRE')return;
30
+ if(b.dataset.dhtCopyAttached)return;
31
+ b.dataset.dhtCopyAttached='1';
32
+ b.style.position='relative';
33
+ 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;';
34
+ 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(_){}});
35
+ b.appendChild(btn);
36
+ });
22
37
  }
23
- });
24
- */
25
- exports.scrollToMessage = 'document.addEventListener("click",t=>{let e=t.target;if(!e)return;let o=e?.getAttribute("data-goto");if(o){let r=document.getElementById(`m-${o}`);r?(r.scrollIntoView({behavior:"smooth",block:"center"}),r.style.backgroundColor="rgba(148, 156, 247, 0.1)",r.style.transition="background-color 0.5s ease",setTimeout(()=>{r.style.backgroundColor="transparent"},1e3)):console.warn("Message ${goto} not found.")}});';
38
+ attach();
39
+ if(typeof MutationObserver!=='undefined'){var mo=new MutationObserver(attach);mo.observe(document.body,{childList:true,subtree:true});}
40
+ })();`;
26
41
 
27
- exports.revealSpoiler = 'const s=document.querySelectorAll(".discord-spoiler");s.forEach(s=>s.addEventListener("click",()=>{if(s.classList.contains("discord-spoiler")){s.classList.remove("discord-spoiler");s.classList.add("discord-spoiler--revealed");}}));';
42
+ // ===========================================================================
43
+ // Per-message permalink button (top-right on hover)
44
+ // ===========================================================================
45
+ exports.permalinkButton = `(function(){
46
+ document.addEventListener('mouseenter',function(e){
47
+ if(!e.target||!e.target.tagName)return;
48
+ var t=e.target;
49
+ if(t.tagName!=='DISCORD-MESSAGE' && t.tagName!=='DISCORD-SYSTEM-MESSAGE')return;
50
+ if(t.querySelector('.dht-permalink-btn'))return;
51
+ 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;';
52
+ 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(_){}});
53
+ t.appendChild(btn);
54
+ },true);
55
+ })();`;
56
+
57
+ // ===========================================================================
58
+ // i18n — default dictionaries, used by both server-side renderers and client-side scripts
59
+ // ===========================================================================
60
+ const defaultDicts = {
61
+ en: {
62
+ edited: 'edited',
63
+ editedAt: 'Edited at {time}',
64
+ joined: 'joined the server',
65
+ pinned: 'pinned a message to this channel.',
66
+ boosted: 'boosted the server!',
67
+ threadStarted: 'started a thread:',
68
+ changedChannelName: 'changed the channel name:',
69
+ changedChannelIcon: 'changed the channel icon.',
70
+ usedCommand: 'used',
71
+ startedCall: 'started a call.',
72
+ addedFollow: 'added {target} to follow this channel.',
73
+ left: 'left.',
74
+ autoModBlocked: 'AutoMod blocked a message.',
75
+ serverAlert: 'Server safety alert.',
76
+ pollEnded: 'A poll ended.',
77
+ roleSubPurchased: 'subscribed to {role}!',
78
+ forwardedFrom: 'Forwarded from',
79
+ searchPlaceholder: 'Search transcript…',
80
+ ofMatches: 'of',
81
+ noMatches: 'no matches',
82
+ backToTop: 'Back to top',
83
+ tocTitle: 'Participants',
84
+ statsMessages: '{n} messages',
85
+ statsParticipants: '{n} participants',
86
+ statsImages: '{n} images',
87
+ crossServerReply: 'Message from another server',
88
+ appBadge: 'APP',
89
+ botBadge: 'BOT',
90
+ editHistoryTitle: 'Edit history',
91
+ editHistoryAt: 'at',
92
+ voiceMessage: 'Voice message',
93
+ forwardedMessage: 'Forwarded',
94
+ languageLabel: 'Language',
95
+ threadArchived: 'Archived',
96
+ threadLocked: 'Locked',
97
+ suppressedEmbeds: '(embeds hidden)',
98
+ filterPanel: 'Filter',
99
+ filterKeyword: 'Keyword',
100
+ filterAuthor: 'Author',
101
+ filterRole: 'Role',
102
+ filterFrom: 'From',
103
+ filterTo: 'To',
104
+ filterPinned: 'Pinned only',
105
+ filterHasImage: 'Has image',
106
+ filterHasEmbed: 'Has embed',
107
+ filterHasAttachment: 'Has attachment',
108
+ filterApply: 'Apply',
109
+ filterReset: 'Reset',
110
+ filterShowing: 'Showing {visible} of {total}',
111
+ flags: 'Badges',
112
+ },
113
+ de: {
114
+ edited: 'bearbeitet',
115
+ editedAt: 'Bearbeitet am {time}',
116
+ joined: 'ist dem Server beigetreten',
117
+ pinned: 'hat eine Nachricht angepinnt.',
118
+ boosted: 'hat den Server geboostet!',
119
+ threadStarted: 'hat einen Thread gestartet:',
120
+ changedChannelName: 'hat den Channel-Namen geändert:',
121
+ changedChannelIcon: 'hat das Channel-Icon geändert.',
122
+ usedCommand: 'hat',
123
+ startedCall: 'hat einen Anruf gestartet.',
124
+ addedFollow: 'hat {target} hinzugefügt, um diesem Channel zu folgen.',
125
+ left: 'hat den Channel verlassen.',
126
+ autoModBlocked: 'AutoMod hat eine Nachricht blockiert.',
127
+ serverAlert: 'Sicherheitswarnung.',
128
+ pollEnded: 'Eine Umfrage wurde beendet.',
129
+ roleSubPurchased: 'hat {role} abonniert!',
130
+ forwardedFrom: 'Weitergeleitet von',
131
+ searchPlaceholder: 'Im Transcript suchen…',
132
+ ofMatches: 'von',
133
+ noMatches: 'keine Treffer',
134
+ backToTop: 'Nach oben',
135
+ tocTitle: 'Teilnehmer',
136
+ statsMessages: '{n} Nachrichten',
137
+ statsParticipants: '{n} Teilnehmer',
138
+ statsImages: '{n} Bilder',
139
+ crossServerReply: 'Nachricht von einem anderen Server',
140
+ appBadge: 'APP',
141
+ botBadge: 'BOT',
142
+ editHistoryTitle: 'Bearbeitungs-Verlauf',
143
+ editHistoryAt: 'am',
144
+ voiceMessage: 'Sprachnachricht',
145
+ forwardedMessage: 'Weitergeleitet',
146
+ languageLabel: 'Sprache',
147
+ threadArchived: 'Archiviert',
148
+ threadLocked: 'Gesperrt',
149
+ suppressedEmbeds: '(Embeds versteckt)',
150
+ filterPanel: 'Filter',
151
+ filterKeyword: 'Suchbegriff',
152
+ filterAuthor: 'Author',
153
+ filterRole: 'Rolle',
154
+ filterFrom: 'Von',
155
+ filterTo: 'Bis',
156
+ filterPinned: 'Nur angepinnt',
157
+ filterHasImage: 'Mit Bild',
158
+ filterHasEmbed: 'Mit Embed',
159
+ filterHasAttachment: 'Mit Anhang',
160
+ filterApply: 'Anwenden',
161
+ filterReset: 'Zurücksetzen',
162
+ filterShowing: 'Zeige {visible} von {total}',
163
+ flags: 'Abzeichen',
164
+ },
165
+ };
166
+
167
+ // User flag emoji map (Discord badge names → emoji approximation)
168
+ const FLAG_BADGES = {
169
+ Staff: { emoji: '🛡', label: 'Discord Staff', color: '#5865F2' },
170
+ Partner: { emoji: '🤝', label: 'Partnered Server Owner', color: '#5865F2' },
171
+ Hypesquad: { emoji: '🎉', label: 'HypeSquad Events', color: '#FBB848' },
172
+ BugHunterLevel1: { emoji: '🐛', label: 'Bug Hunter', color: '#3E70DD' },
173
+ BugHunterLevel2: { emoji: '🐞', label: 'Bug Hunter Gold', color: '#FFD700' },
174
+ HypeSquadOnlineHouse1: { emoji: '💜', label: 'HypeSquad Bravery', color: '#9C84EF' },
175
+ HypeSquadOnlineHouse2: { emoji: '💙', label: 'HypeSquad Brilliance', color: '#F47B67' },
176
+ HypeSquadOnlineHouse3: { emoji: '💚', label: 'HypeSquad Balance', color: '#45DDC0' },
177
+ PremiumEarlySupporter: { emoji: '⭐', label: 'Early Supporter', color: '#FF73FA' },
178
+ VerifiedBot: { emoji: '✓', label: 'Verified Bot', color: '#5865F2' },
179
+ VerifiedDeveloper: { emoji: '✅', label: 'Early Verified Developer', color: '#5865F2' },
180
+ CertifiedModerator: { emoji: '🎖', label: 'Certified Moderator', color: '#5865F2' },
181
+ BotHTTPInteractions: { emoji: '🔗', label: 'HTTP Interactions', color: '#5865F2' },
182
+ ActiveDeveloper: { emoji: '🔧', label: 'Active Developer', color: '#3BA55D' },
183
+ Spammer: { emoji: '🚫', label: 'Spammer', color: '#ED4245' },
184
+ Quarantined: { emoji: '⚠', label: 'Quarantined', color: '#ED4245' },
185
+ };
186
+ exports.userFlagBadges = FLAG_BADGES;
28
187
 
29
- // ============================================================================
30
- // Mention popup: click on a user/role/channel mention to see detailed info
31
- // Reads from globalThis.$discordMessage.{users,roles,channels,profiles}.
32
- // ============================================================================
188
+ function getDict(lang) {
189
+ if (lang && defaultDicts[lang]) return defaultDicts[lang];
190
+ return defaultDicts.en;
191
+ }
192
+ exports.getDict = getDict;
193
+ exports.defaultDicts = defaultDicts;
194
+
195
+ // ===========================================================================
196
+ // mentionPopup — clickable user/role/channel pills
197
+ // Reads from window.$discordMessage.{users,roles,channels,profiles}.
198
+ // Uses safe color validation to block CSS injection.
199
+ // ===========================================================================
33
200
  exports.mentionPopup = `(function(){
34
- var DATA = function(){ return (typeof globalThis !== 'undefined' && globalThis.$discordMessage) || {}; };
35
- function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g, function(c){ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]; }); }
36
- function fmt(iso){ if(!iso) return ''; try { var d = new Date(iso); return isNaN(d.getTime()) ? '' : d.toLocaleString('de-DE',{dateStyle:'medium', timeStyle:'short'}); } catch(_){ return ''; } }
37
-
38
- var popup = null;
39
- function ensure(){
40
- if (popup) return popup;
41
- popup = document.createElement('div');
42
- popup.className = 'dht-popup';
43
- popup.innerHTML = '<div class="dht-popup-body"></div><button type="button" class="dht-popup-close" aria-label="Schliessen">&times;</button>';
44
- document.body.appendChild(popup);
45
- popup.querySelector('.dht-popup-close').addEventListener('click', hide);
46
- document.addEventListener('click', function(e){
47
- if (!popup || popup.style.display !== 'block') return;
48
- if (popup.contains(e.target)) return;
49
- if (e.target.closest && e.target.closest('[data-mention-id]')) return;
50
- hide();
51
- });
52
- document.addEventListener('keydown', function(e){ if (e.key === 'Escape') hide(); });
53
- window.addEventListener('resize', hide);
54
- return popup;
201
+ function DATA(){return (typeof globalThis!=='undefined' && globalThis.$discordMessage)||{};}
202
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});}
203
+ function safeColor(c,fb){fb=fb||'#5865F2';return typeof c==='string' && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fb;}
204
+ 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;}
205
+ 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 '';}}
206
+ var popup=null;
207
+ 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():[];var onMention=false;for(var i=0;i<path.length;i++){var x=path[i];if(x&&x.getAttribute&&x.getAttribute('data-mention-id')){onMention=true;break;}}if(onMention)return;hide();});document.addEventListener('keydown',function(e){if(e.key==='Escape')hide();});window.addEventListener('resize',hide);return popup;}
208
+ function hide(){if(popup)popup.style.display='none';}
209
+ 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';}
210
+ 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>';}
211
+ 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>';}
212
+ 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></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 labels=DATA().i18n&&(DATA().i18n[DATA().lang]||DATA().i18n.en)||{};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+'</div>'+(username?'<div class="dht-popup-sub">'+esc(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></div>';}
213
+ function roleHtml(id){var r=(DATA().roles||{})[id];if(!r)return '<i>?</i><div class="dht-popup-id">ID: <code>'+esc(id)+'</code></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)+'</div>'+(r.color?'<div class="dht-popup-sub"><code>'+esc(r.color)+'</code></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></div>';}
214
+ function channelHtml(id){var c=(DATA().channels||{})[id];if(!c)return '<i>Channel</i><div class="dht-popup-id">ID: <code>'+esc(id)+'</code></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)+'</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></div>';}
215
+ document.addEventListener('click',function(e){if(!e||e.defaultPrevented)return;
216
+ var path=(typeof e.composedPath==='function')?e.composedPath():[];
217
+ if(!path.length){var n=e.target;while(n){path.push(n);n=n.parentNode||(n.host);}}
218
+ var el=null;
219
+ for(var i=0;i<path.length;i++){var x=path[i];if(x&&x.getAttribute&&x.getAttribute('data-mention-id')){el=x;break;}}
220
+ if(!el)return;
221
+ var tp=el.getAttribute('data-mention-type');var id=el.getAttribute('data-mention-id');
222
+ if(!tp||!id)return;
223
+ e.preventDefault();e.stopPropagation();
224
+ var html='';if(tp==='user')html=userHtml(id);else if(tp==='role')html=roleHtml(id);else if(tp==='channel')html=channelHtml(id);else return;
225
+ showAt(el,html);
226
+ });
227
+ })();`;
228
+
229
+ exports.mentionPopupStyles = `
230
+ /* Mention pills — Discord-style. Always render correctly (no web component dependency). */
231
+ .dht-mention{
232
+ display:inline-block;
233
+ background-color:rgba(88,101,242,.18);
234
+ color:#c9cdfb;
235
+ padding:0 4px;
236
+ border-radius:3px;
237
+ font-weight:500;
238
+ cursor:pointer;
239
+ text-decoration:none;
240
+ border:1px solid transparent;
241
+ transition:filter .15s ease,background-color .15s ease;
242
+ white-space:nowrap;
55
243
  }
56
- function hide(){ if (popup) popup.style.display = 'none'; }
57
- function showAt(el, html){
58
- var p = ensure();
59
- p.querySelector('.dht-popup-body').innerHTML = html;
60
- p.style.display = 'block';
61
- var r = el.getBoundingClientRect();
62
- var pr = p.getBoundingClientRect();
63
- var pad = 8;
64
- var top = r.bottom + 6;
65
- var left = r.left;
66
- if (top + pr.height > window.innerHeight - pad) top = Math.max(pad, r.top - pr.height - 6);
67
- if (left + pr.width > window.innerWidth - pad) left = Math.max(pad, window.innerWidth - pr.width - pad);
68
- if (left < pad) left = pad;
69
- p.style.top = top + 'px';
70
- p.style.left = left + 'px';
244
+ .dht-mention:hover{filter:brightness(1.25);background-color:rgba(88,101,242,.32)}
245
+ .dht-mention--user{background-color:rgba(88,101,242,.18);color:#c9cdfb}
246
+ .dht-mention--channel{background-color:rgba(88,101,242,.15);color:#c9cdfb}
247
+ .dht-mention--command{background-color:rgba(88,101,242,.22);color:#c9cdfb;font-family:Consolas,Menlo,monospace}
248
+ .dht-mention--highlight{background-color:rgba(255,212,0,.25);color:#f9d949}
249
+ /* Role mentions carry inline style (role color + faded bg) — leave their color/background alone, only set base shape */
250
+ .dht-mention--role{font-weight:500}
251
+ /* Legacy <discord-mention> styling (in case some old code still emits it) */
252
+ 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}
253
+ discord-mention:not(:defined)[type="user"]::before,
254
+ discord-mention:not(:defined)[type="role"]::before,
255
+ discord-mention:not(:defined)[highlight]::before{content:"@"}
256
+ discord-mention:not(:defined)[type="channel"]::before{content:"#"}
257
+ discord-mention:not(:defined)[type="command"]::before{content:"/"}
258
+ .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}
259
+ .dht-popup-headtext{min-width:0}
260
+ .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}
261
+ .dht-popup-close:hover{background:rgba(255,255,255,.06);color:#dbdee1}
262
+ .dht-popup-head{display:flex;align-items:center;gap:10px;margin-right:18px}
263
+ .dht-popup-avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;flex:none}
264
+ .dht-popup-avatar--ph{background:#313338}
265
+ .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}
266
+ .dht-popup-headtext{min-width:0}
267
+ .dht-popup-name{font-weight:600;font-size:16px;word-break:break-word}
268
+ .dht-popup-sub{font-size:12px;color:#949ba4;word-break:break-all}
269
+ .dht-popup-badge{background:#5865F2;color:#fff;font-size:10px;padding:1px 6px;border-radius:4px;margin-left:6px;vertical-align:middle}
270
+ .dht-popup-section{margin-top:10px}
271
+ .dht-popup-row{display:flex;justify-content:space-between;gap:12px;margin-top:6px;font-size:13px}
272
+ .dht-popup-label{color:#949ba4;font-size:11px;text-transform:uppercase;letter-spacing:.04em;font-weight:700}
273
+ .dht-popup-roles{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
274
+ .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}
275
+ .dht-popup-id{margin-top:10px;padding-top:8px;border-top:1px solid #2b2d31;font-size:11px;color:#6e727a}
276
+ .dht-popup-id code{background:rgba(255,255,255,.05);padding:1px 4px;border-radius:3px}
277
+ `;
278
+
279
+ // ===========================================================================
280
+ // Back-to-top
281
+ // ===========================================================================
282
+ 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();})();`;
283
+ 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)}`;
284
+
285
+ // ===========================================================================
286
+ // Search bar — Ctrl+F replacement: live highlight, x of n matches, prev/next nav
287
+ // ===========================================================================
288
+ exports.searchBar = `(function(){
289
+ var DATA=globalThis.$discordMessage||{};
290
+ var dict=(DATA.i18n&&(DATA.i18n[DATA.lang]||DATA.i18n.en))||{};
291
+ var bar=document.createElement('div');
292
+ bar.className='dht-search';
293
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});}
294
+ bar.innerHTML='<input type="search" class="dht-search-input" placeholder="'+esc(dict.searchPlaceholder||'Search…')+'"><span class="dht-search-counter">0</span><button class="dht-search-btn" data-act="prev" title="Previous">&#x25B2;</button><button class="dht-search-btn" data-act="next" title="Next">&#x25BC;</button><button class="dht-search-btn" data-act="close" title="Close">&times;</button>';
295
+ document.body.appendChild(bar);
296
+ var input=bar.querySelector('.dht-search-input');
297
+ var counter=bar.querySelector('.dht-search-counter');
298
+ var matches=[],idx=-1;
299
+ function clear(){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='0';}
300
+ 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-search')||node.tagName==='SCRIPT'||node.tagName==='STYLE'))return;for(var i=node.childNodes.length-1;i>=0;i--)walk(node.childNodes[i],re,acc);}}
301
+ function search(q){clear();if(!q||q.length<2)return;var re=new RegExp(q.replace(/[.*+?^\${}()|[\\]\\\\]/g,function(c){return '\\\\'+c;}),'gi');walk(document.body,re,matches);if(matches.length){idx=0;goTo();}counter.textContent=matches.length?(idx+1)+' '+(dict.ofMatches||'of')+' '+matches.length:(dict.noMatches||'no matches');}
302
+ 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;}}
303
+ input.addEventListener('input',function(){search(input.value);});
304
+ bar.addEventListener('click',function(e){var t=e.target;var act=t.getAttribute&&t.getAttribute('data-act');if(act==='next'){if(matches.length){idx=(idx+1)%matches.length;goTo();}}else if(act==='prev'){if(matches.length){idx=(idx-1+matches.length)%matches.length;goTo();}}else if(act==='close'){bar.classList.remove('dht-search--open');input.value='';clear();}});
305
+ document.addEventListener('keydown',function(e){
306
+ if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='f'){
307
+ // Skip if user is currently typing in the filter panel
308
+ var ae=document.activeElement;
309
+ if(ae && ae.closest && ae.closest('.dht-filter'))return;
310
+ e.preventDefault();bar.classList.add('dht-search--open');input.focus();input.select();
311
+ }else if(e.key==='Escape'&&bar.classList.contains('dht-search--open')){
312
+ bar.classList.remove('dht-search--open');input.value='';clear();
313
+ }else if(e.key==='Enter'&&document.activeElement===input){
314
+ if(matches.length){idx=(idx+(e.shiftKey?-1:1)+matches.length)%matches.length;goTo();}
315
+ }
316
+ });
317
+ })();`;
318
+ exports.searchBarStyles = `.dht-search{position:fixed;top:16px;right:16px;background:#1e1f22;border:1px solid #2b2d31;border-radius:8px;padding:6px 8px;display:none;align-items:center;gap:6px;z-index:99998;box-shadow:0 6px 18px rgba(0,0,0,.5);font-family:"gg sans",sans-serif;color:#dbdee1}.dht-search--open{display:flex}.dht-search-input{background:#2b2d31;color:#dbdee1;border:1px solid #2b2d31;border-radius:4px;padding:4px 8px;font-size:13px;width:220px;outline:none}.dht-search-input:focus{border-color:#5865F2}.dht-search-counter{color:#949ba4;font-size:12px;min-width:60px}.dht-search-btn{background:none;border:none;color:#dbdee1;cursor:pointer;padding:4px 6px;border-radius:4px;font-size:12px}.dht-search-btn:hover{background:rgba(255,255,255,.06)}.dht-hit{background:rgba(255,228,0,.35);color:inherit;border-radius:2px}.dht-hit--active{background:rgba(255,140,0,.7);color:#000}`;
319
+
320
+ // ===========================================================================
321
+ // Image lightbox — click images to fullscreen, arrow keys to navigate
322
+ // ===========================================================================
323
+ exports.lightbox = `(function(){
324
+ var overlay=null,images=[],idx=0;
325
+ 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;});}
326
+ 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);});}
327
+ var imagesCacheDirty=true;
328
+ 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';}
329
+ function nav(d){if(!images.length)return;idx=(idx+d+images.length)%images.length;render();}
330
+ 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||'';}
331
+ function close(){if(overlay)overlay.style.display='none';}
332
+ 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);}});
333
+ 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);});
334
+ })();`;
335
+ 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%}`;
336
+
337
+ // ===========================================================================
338
+ // TOC / Sidebar — list participants, click jumps to their first message
339
+ // ===========================================================================
340
+ exports.toc = `(function(){
341
+ var DATA=globalThis.$discordMessage||{};
342
+ var dict=(DATA.i18n&&(DATA.i18n[DATA.lang]||DATA.i18n.en))||{};
343
+ var users=DATA.users||DATA.profiles||{};
344
+ var ids=Object.keys(users).filter(function(x){return /^\\d+$/.test(x);});
345
+ if(ids.length===0)return;
346
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});}
347
+ function safeColor(c,fb){fb=fb||'#dbdee1';return typeof c==='string' && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fb;}
348
+ function safeUrl(u){if(typeof u!=='string'||!u)return '';if(/^https?:\\/\\//i.test(u))return u;return '';}
349
+ function firstMessageOf(uid){var m=document.querySelector('discord-message[profile="'+uid+'"],[data-profile="'+uid+'"]');return m?m.id:null;}
350
+ 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);
351
+ var panel=document.createElement('aside');panel.className='dht-toc';panel.setAttribute('aria-hidden','true');
352
+ var html='<header><h3>'+esc(dict.tocTitle||'Participants')+'</h3><button class="dht-toc-close" aria-label="Close">&times;</button></header><ul role="list">';
353
+ ids.sort(function(a,b){var ua=users[a],ub=users[b];var na=(ua.displayName||ua.author||ua.username||'').toLowerCase();var nb=(ub.displayName||ub.author||ub.username||'').toLowerCase();return na.localeCompare(nb);}).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');html+='<li data-uid="'+esc(id)+'" 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>';});
354
+ html+='</ul>';panel.innerHTML=html;document.body.appendChild(panel);
355
+ function syncToggleVisibility(open){btn.style.opacity=open?'0':'1';btn.style.pointerEvents=open?'none':'auto';btn.setAttribute('aria-expanded',open?'true':'false');}
356
+ btn.addEventListener('click',function(){var open=panel.classList.toggle('dht-toc--open');panel.setAttribute('aria-hidden',open?'false':'true');syncToggleVisibility(open);});
357
+ panel.querySelector('.dht-toc-close').addEventListener('click',function(){panel.classList.remove('dht-toc--open');panel.setAttribute('aria-hidden','true');syncToggleVisibility(false);});
358
+ 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);}}}
359
+ panel.querySelectorAll('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);}});});
360
+ })();`;
361
+ exports.tocStyles = `.dht-toc-toggle{position:fixed;top:16px;left: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;left:-320px;width:300px;height:100vh;background:#1e1f22;border-right:1px solid #2b2d31;transition:left .25s ease;z-index:9997;overflow-y:auto;font-family:"gg sans",sans-serif;color:#dbdee1;padding:16px}.dht-toc--open{left:0}.dht-toc header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #2b2d31}.dht-toc h3{margin:0;font-size:14px;text-transform:uppercase;letter-spacing:.04em;color:#949ba4}.dht-toc-close{background:none;border:none;color:#949ba4;cursor:pointer;font-size:22px;line-height:1}.dht-toc ul{list-style:none;padding:0;margin:0}.dht-toc li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:14px}.dht-toc li:hover{background:rgba(255,255,255,.04)}.dht-toc li img,.dht-toc-ph{width:28px;height:28px;border-radius:50%;flex:none}.dht-toc-ph{background:#313338}.dht-toc-name{flex:1;font-weight:500}`;
362
+
363
+ // ===========================================================================
364
+ // Language switcher
365
+ // ===========================================================================
366
+ exports.i18nSwitcher = `(function(){
367
+ var DATA=globalThis.$discordMessage||{};
368
+ if(!DATA.i18n)return;
369
+ var langs=Object.keys(DATA.i18n);
370
+ if(langs.length<2)return;
371
+ var current=DATA.lang||'en';
372
+ function apply(lang){DATA.lang=lang;var dict=DATA.i18n[lang]||DATA.i18n.en||{};document.querySelectorAll('[data-i18n]').forEach(function(el){var key=el.getAttribute('data-i18n');var raw=dict[key];if(typeof raw!=='string')return;var params=el.getAttribute('data-i18n-params');if(params){try{var obj=JSON.parse(params);Object.keys(obj).forEach(function(k){raw=raw.replace('{'+k+'}',obj[k]);});}catch(_){}}el.textContent=raw;});}
373
+ var wrap=document.createElement('div');wrap.className='dht-langs';langs.forEach(function(l){var b=document.createElement('button');b.type='button';b.textContent=l.toUpperCase();b.className='dht-lang'+(l===current?' dht-lang--active':'');b.addEventListener('click',function(){wrap.querySelectorAll('.dht-lang').forEach(function(x){x.classList.remove('dht-lang--active');});b.classList.add('dht-lang--active');apply(l);});wrap.appendChild(b);});document.body.appendChild(wrap);
374
+ })();`;
375
+ exports.i18nStyles = `.dht-langs{position:fixed;bottom:24px;left:24px;display:flex;gap:4px;background:#1e1f22;border:1px solid #2b2d31;border-radius:6px;padding:4px;z-index:9998;font-family:"gg sans",sans-serif}.dht-lang{background:none;border:none;color:#949ba4;cursor:pointer;padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600}.dht-lang:hover{color:#dbdee1}.dht-lang--active{background:#5865F2;color:#fff}`;
376
+
377
+ // ===========================================================================
378
+ // Date separators (server renders <div class="dht-date-sep">…</div>)
379
+ // ===========================================================================
380
+ exports.dateSeparatorStyles = `.dht-date-sep{display:flex;align-items:center;gap:12px;margin:24px 16px 8px;color:#b5bac1;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em}.dht-date-sep::before,.dht-date-sep::after{content:"";flex:1;height:1px;background:#3f4248}`;
381
+
382
+ // ===========================================================================
383
+ // Markdown rendering styles — headings, subtext, inline code, emoji, separators.
384
+ // Applied directly on the elements we emit (no web-component dependency).
385
+ // ===========================================================================
386
+ exports.markdownStyles = `
387
+ .dht-heading{
388
+ font-weight:700;
389
+ color:#fff;
390
+ line-height:1.3;
391
+ margin:8px 0 4px;
392
+ font-family:inherit;
71
393
  }
72
- function rolePill(role){
73
- var c = role && role.color ? role.color : '#5865F2';
74
- return '<span class="dht-role-pill" style="--dht-role:' + esc(c) + ';">' + esc(role && role.name ? role.name : '@role') + '</span>';
394
+ .dht-heading-1{font-size:1.5rem}
395
+ .dht-heading-2{font-size:1.25rem}
396
+ .dht-heading-3{font-size:1rem}
397
+ .dht-subtext{
398
+ color:#949ba4;
399
+ font-size:.85em;
400
+ display:block;
401
+ margin:2px 0;
75
402
  }
76
- function userHtml(id){
77
- var d = DATA();
78
- var u = (d.users && d.users[id]) || null;
79
- var p = (d.profiles && d.profiles[id]) || null;
80
- if (!u && !p) return '<i>Unbekannter User</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
81
- var name = (u && (u.displayName || u.username)) || (p && p.author) || 'Unknown';
82
- var username = u && u.username ? '@' + u.username : '';
83
- var avatar = (u && u.avatar) || (p && p.avatar) || '';
84
- var color = (u && u.bannerColor) || (p && p.roleColor) || '#dbdee1';
85
- var bot = (u && u.bot) || (p && p.bot);
86
- var roles = u && Array.isArray(u.roles) && u.roles.length ? u.roles.map(rolePill).join('') : '';
87
- var joinedAt = u && u.joinedAt ? fmt(u.joinedAt) : '';
88
- var createdAt = u && u.createdAt ? fmt(u.createdAt) : '';
89
- return ''
90
- + '<div class="dht-popup-head">'
91
- + (avatar ? '<img class="dht-popup-avatar" src="' + esc(avatar) + '" alt="">' : '<div class="dht-popup-avatar dht-popup-avatar--ph"></div>')
92
- + '<div class="dht-popup-headtext">'
93
- + '<div class="dht-popup-name" style="color:' + esc(color) + ';">' + esc(name) + (bot ? '<span class="dht-popup-badge">BOT</span>' : '') + '</div>'
94
- + (username ? '<div class="dht-popup-sub">' + esc(username) + '</div>' : '')
95
- + '</div>'
96
- + '</div>'
97
- + (roles ? '<div class="dht-popup-section"><div class="dht-popup-label">Rollen</div><div class="dht-popup-roles">' + roles + '</div></div>' : '')
98
- + (joinedAt ? '<div class="dht-popup-row"><span class="dht-popup-label">Server seit</span><span>' + esc(joinedAt) + '</span></div>' : '')
99
- + (createdAt ? '<div class="dht-popup-row"><span class="dht-popup-label">Account seit</span><span>' + esc(createdAt) + '</span></div>' : '')
100
- + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
403
+ .dht-inline-code{
404
+ font-family:Consolas,Menlo,Monaco,"Courier New",monospace;
405
+ background:rgba(0,0,0,.35);
406
+ padding:1px 4px;
407
+ border-radius:3px;
408
+ font-size:.875em;
409
+ color:#dbdee1;
410
+ white-space:pre-wrap;
101
411
  }
102
- function roleHtml(id){
103
- var r = (DATA().roles || {})[id];
104
- if (!r) return '<i>Unbekannte Rolle</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
105
- var color = r.color || '#dbdee1';
106
- return ''
107
- + '<div class="dht-popup-head">'
108
- + '<div class="dht-popup-rolemark" style="background:' + esc(color) + ';"></div>'
109
- + '<div class="dht-popup-headtext">'
110
- + '<div class="dht-popup-name" style="color:' + esc(color) + ';">@' + esc(r.name) + '</div>'
111
- + (r.color ? '<div class="dht-popup-sub"><code>' + esc(r.color) + '</code></div>' : '')
112
- + '</div>'
113
- + '</div>'
114
- + (r.memberCount != null ? '<div class="dht-popup-row"><span class="dht-popup-label">Mitglieder</span><span>' + esc(r.memberCount) + '</span></div>' : '')
115
- + (r.position != null ? '<div class="dht-popup-row"><span class="dht-popup-label">Position</span><span>' + esc(r.position) + '</span></div>' : '')
116
- + (r.hoist ? '<div class="dht-popup-row"><span class="dht-popup-label">Hoisted</span><span>Ja</span></div>' : '')
117
- + (r.mentionable ? '<div class="dht-popup-row"><span class="dht-popup-label">Erwähnbar</span><span>Ja</span></div>' : '')
118
- + (r.managed ? '<div class="dht-popup-row"><span class="dht-popup-label">Verwaltet</span><span>Bot/Integration</span></div>' : '')
119
- + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
412
+ .dht-emoji{
413
+ width:1.375em;
414
+ height:1.375em;
415
+ vertical-align:bottom;
416
+ margin:0 .05em;
120
417
  }
121
- function channelHtml(id){
122
- var c = (DATA().channels || {})[id];
123
- if (!c) return '<i>Channel</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
124
- return ''
125
- + '<div class="dht-popup-head">'
126
- + '<div class="dht-popup-rolemark" style="background:#5865F2;">#</div>'
127
- + '<div class="dht-popup-headtext">'
128
- + '<div class="dht-popup-name">#' + esc(c.name) + '</div>'
129
- + (c.parent ? '<div class="dht-popup-sub">in ' + esc(c.parent) + '</div>' : '')
130
- + '</div>'
131
- + '</div>'
132
- + (c.topic ? '<div class="dht-popup-section"><div class="dht-popup-label">Topic</div><div>' + esc(c.topic) + '</div></div>' : '')
133
- + (c.nsfw ? '<div class="dht-popup-row"><span class="dht-popup-label">NSFW</span><span>Ja</span></div>' : '')
134
- + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
418
+ .dht-emoji--jumbo{width:3em;height:3em}
419
+ .dht-timestamp{
420
+ background:rgba(255,255,255,.06);
421
+ padding:0 4px;
422
+ border-radius:3px;
423
+ font-size:.85em;
424
+ color:#dbdee1;
425
+ cursor:help;
135
426
  }
136
- document.addEventListener('click', function(e){
137
- var el = e.target && e.target.closest ? e.target.closest('[data-mention-id]') : null;
138
- if (!el) return;
139
- var t = el.getAttribute('data-mention-type');
140
- var id = el.getAttribute('data-mention-id');
141
- if (!t || !id) return;
142
- e.preventDefault();
143
- e.stopPropagation();
144
- var html = '';
145
- if (t === 'user') html = userHtml(id);
146
- else if (t === 'role') html = roleHtml(id);
147
- else if (t === 'channel') html = channelHtml(id);
148
- else return;
149
- showAt(el, html);
150
- });
151
- })();`;
152
-
153
- // ============================================================================
154
- // Mention popup styles
155
- // ============================================================================
156
- exports.mentionPopupStyles = `
157
- .dht-mention { cursor: pointer; }
158
- .dht-mention:hover { filter: brightness(1.15); }
159
- .dht-popup {
160
- position: fixed; display: none; max-width: 340px; min-width: 240px;
161
- background: #1e1f22; color: #dbdee1; border: 1px solid #2b2d31;
162
- border-radius: 10px; padding: 14px 16px 12px;
163
- box-shadow: 0 12px 32px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35);
164
- font-family: "gg sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
165
- font-size: 14px; line-height: 1.4; z-index: 99999;
427
+ .dht-v2-separator{box-sizing:border-box}
428
+ /* Tidy up <discord-message> children spacing inside Components V2 containers */
429
+ discord-message strong{font-weight:700;color:#fff}
430
+ discord-message em{font-style:italic;font-display:swap}
431
+ discord-message u{text-decoration:underline}
432
+ discord-message s{text-decoration:line-through;opacity:.75}
433
+ discord-message blockquote,discord-quote{
434
+ border-left:4px solid #4f5359;padding-left:10px;margin:4px 0;color:#dbdee1;
435
+ }
436
+ /* Make code blocks readable */
437
+ .discord-highlighted-code, pre.dht-codeblock{
438
+ background:#1e1f22;
439
+ border-radius:6px;
440
+ padding:8px 12px;
441
+ margin:6px 0;
442
+ overflow-x:auto;
443
+ font-family:Consolas,Menlo,Monaco,"Courier New",monospace;
444
+ font-size:.875em;
445
+ color:#dbdee1;
446
+ white-space:pre-wrap;
447
+ word-break:break-word;
448
+ width:100%;
449
+ align-self:stretch;
450
+ box-sizing:border-box;
166
451
  }
167
- .dht-popup-close {
168
- position: absolute; top: 6px; right: 8px; background: none; border: none;
169
- color: #949ba4; cursor: pointer; font-size: 20px; line-height: 1; padding: 4px 8px;
170
- border-radius: 4px;
452
+ /* Jump link affordance */
453
+ .dht-jump{cursor:pointer;color:#7289da;font-style:italic;text-decoration:underline;text-decoration-color:rgba(114,137,218,.4)}
454
+ .dht-jump:hover{color:#a3c0ff}
455
+ /* Focus rings — keyboard accessibility */
456
+ .dht-mention:focus-visible,
457
+ .dht-jump:focus-visible,
458
+ .dht-back-top:focus-visible,
459
+ .dht-toc-toggle:focus-visible,
460
+ .dht-toc li:focus-visible,
461
+ .dht-search-btn:focus-visible,
462
+ .dht-search-input:focus-visible,
463
+ .dht-popup-close:focus-visible,
464
+ .dht-lb-close:focus-visible,
465
+ .dht-lb-prev:focus-visible,
466
+ .dht-lb-next:focus-visible,
467
+ [data-goto]:focus-visible{outline:2px solid #5865F2;outline-offset:2px;border-radius:3px}
468
+ /* Higher-contrast subtle text (was failing WCAG AA) */
469
+ .dht-subtext,.dht-popup-sub,.dht-stats,.dht-poll-foot,.dht-voice-dur,.dht-forwarded-head{color:#b5bac1}
470
+ /* Search hit visibility on any background */
471
+ .dht-hit{background:rgba(255,228,0,.45);color:#000;box-shadow:0 0 0 1px rgba(0,0,0,.4)}
472
+ .dht-hit--active{background:rgba(255,140,0,.85);color:#000}
473
+ /* Body fallback when web components fail to load from CDN */
474
+ body{background:#313338;color:#dbdee1;font-family:"gg sans","Helvetica Neue",Helvetica,Arial,sans-serif}
475
+ discord-messages{background:#313338}
476
+ /* Date separator divider — more visible */
477
+ .dht-date-sep::before,.dht-date-sep::after{background:rgba(255,255,255,.1)}
478
+ /* Reduce motion */
479
+ @media (prefers-reduced-motion:reduce){
480
+ *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
171
481
  }
172
- .dht-popup-close:hover { background: rgba(255,255,255,0.06); color: #dbdee1; }
173
- .dht-popup-head { display: flex; align-items: center; gap: 10px; margin-right: 18px; }
174
- .dht-popup-avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; flex: none; }
175
- .dht-popup-avatar--ph { background: #313338; }
176
- .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; }
177
- .dht-popup-headtext { min-width: 0; }
178
- .dht-popup-name { font-weight: 600; font-size: 16px; word-break: break-word; }
179
- .dht-popup-sub { font-size: 12px; color: #949ba4; word-break: break-all; }
180
- .dht-popup-badge { background: #5865F2; color: #fff; font-size: 10px; padding: 1px 6px; border-radius: 4px; margin-left: 6px; vertical-align: middle; }
181
- .dht-popup-section { margin-top: 10px; }
182
- .dht-popup-row { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px; font-size: 13px; }
183
- .dht-popup-label { color: #949ba4; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700; }
184
- .dht-popup-roles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
185
- .dht-role-pill {
186
- display: inline-flex; align-items: center;
187
- background: color-mix(in srgb, var(--dht-role) 18%, transparent);
188
- color: var(--dht-role); border: 1px solid color-mix(in srgb, var(--dht-role) 40%, transparent);
189
- padding: 1px 8px; border-radius: 999px; font-size: 11px; line-height: 1.5;
482
+ /* Mobile responsiveness */
483
+ @media (max-width:640px){
484
+ .dht-search{left:8px!important;right:8px!important;width:auto!important;top:8px!important}
485
+ .dht-search-input{width:100%!important}
486
+ .dht-toc{width:85vw!important}
487
+ .dht-langs{bottom:80px!important}
488
+ .dht-back-top{bottom:16px!important;right:16px!important;width:40px;height:40px}
190
489
  }
191
- .dht-popup-id { margin-top: 10px; padding-top: 8px; border-top: 1px solid #2b2d31; font-size: 11px; color: #6e727a; }
192
- .dht-popup-id code { background: rgba(255,255,255,0.05); padding: 1px 4px; border-radius: 3px; }
490
+ /* Print: hide all floating UI, light theme */
491
+ @media print{
492
+ .dht-back-top,.dht-search,.dht-toc-toggle,.dht-toc,.dht-langs,.dht-popup,.dht-lightbox,.dht-stats,.dht-filter,.dht-filter-toggle,.dht-copy-btn,.dht-permalink-btn{display:none!important}
493
+ body,discord-messages{background:#fff!important;color:#000!important}
494
+ .discord-header{background:#fff!important;color:#000!important;border-color:#ccc!important}
495
+ discord-message,discord-system-message{break-inside:avoid;color:#000}
496
+ a::after{content:" (" attr(href) ")";font-size:10px;color:#666}
497
+ .dht-mention{background:rgba(0,0,0,.06)!important;color:#000!important;border-color:#999!important}
498
+ .dht-heading{color:#000!important}
499
+ }
500
+ /* Spoiler reveal fade */
501
+ .discord-spoiler{cursor:pointer;transition:opacity .2s ease, filter .2s ease}
502
+ .discord-spoiler--revealing{opacity:.3;filter:blur(0)}
503
+ .discord-spoiler--revealed{opacity:1;filter:none;background:transparent!important}
504
+ /* Code copy button */
505
+ pre.dht-codeblock,.discord-highlighted-code{position:relative}
506
+ .dht-copy-btn{position:absolute;top:6px;right:6px;background:rgba(255,255,255,.06);color:#dbdee1;border:1px solid rgba(255,255,255,.08);border-radius:4px;width:28px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;opacity:0;transition:opacity .15s ease,background .15s ease}
507
+ pre.dht-codeblock:hover .dht-copy-btn,.discord-highlighted-code:hover .dht-copy-btn{opacity:1}
508
+ .dht-copy-btn:hover{background:rgba(255,255,255,.12)}
509
+ .dht-copy-btn--ok{background:#3ba55d!important;color:#fff}
510
+ /* Permalink button on message hover */
511
+ discord-message,discord-system-message{position:relative}
512
+ .dht-permalink-btn{position:absolute;top:6px;right:8px;background:rgba(255,255,255,.06);color:#b5bac1;border:none;border-radius:4px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;opacity:0;transition:opacity .15s ease,background .15s ease;z-index:5}
513
+ discord-message:hover .dht-permalink-btn,discord-system-message:hover .dht-permalink-btn{opacity:.7}
514
+ .dht-permalink-btn:hover{background:rgba(255,255,255,.12);opacity:1!important}
515
+ .dht-permalink-btn--ok{background:#3ba55d!important;color:#fff;opacity:1!important}
516
+ /* Avatar role-color ring (Discord 2024+ style) */
517
+ discord-message[data-role-color] [slot="author-image"],discord-message[data-role-color] img.discord-author-avatar{box-shadow:0 0 0 2px var(--dht-role,#5865F2)}
518
+ /* Applied forum tags */
519
+ .dht-applied-tags{display:flex;flex-wrap:wrap;gap:4px;margin:2px 0 4px}
520
+ .dht-tag-pill{display:inline-flex;align-items:center;background:rgba(88,101,242,.18);color:#c9cdfb;border-radius:10px;padding:1px 8px;font-size:11px;border:1px solid rgba(88,101,242,.32)}
521
+ .dht-tag-emoji{margin-right:2px}
522
+ /* Additional badges */
523
+ .dht-badge{display:inline-block;font-size:10px;padding:1px 6px;border-radius:4px;margin-left:6px;vertical-align:middle;font-weight:600}
524
+ .dht-badge-crosspost{background:rgba(88,101,242,.25);color:#c9cdfb}
525
+ .dht-badge-silent{background:rgba(255,255,255,.06);color:#949ba4}
526
+ /* Activity card */
527
+ .dht-activity{display:inline-flex;align-items:center;gap:6px;background:rgba(88,101,242,.12);border:1px solid rgba(88,101,242,.2);padding:4px 10px;border-radius:6px;font-size:12px;color:#dbdee1;margin-top:4px}
528
+ .dht-activity-icon{font-size:14px}
529
+ /* Embed image-only / GIFV / video variants */
530
+ .dht-embed--image,.dht-embed--gifv,.dht-embed--video{max-width:500px;border-radius:6px;overflow:hidden;margin-top:4px;position:relative}
531
+ .dht-embed-image,.dht-embed-gifv,.dht-embed-video-thumb{max-width:100%;height:auto;display:block;border-radius:6px}
532
+ .dht-embed-video-wrap{display:block;position:relative;cursor:pointer}
533
+ .dht-embed-video-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:48px;color:#fff;background:rgba(0,0,0,.35);text-shadow:0 2px 8px rgba(0,0,0,.6)}
534
+ .dht-embed--video .dht-embed-title{display:block;color:#8ab4ff;margin-top:6px;font-weight:600}
535
+ /* Stickers — Lottie placeholder */
536
+ .dht-sticker--lottie{display:inline-flex;flex-direction:column;align-items:center;justify-content:center;width:160px;height:160px;background:rgba(255,255,255,.04);border-radius:8px;color:#dbdee1;font-size:12px;text-align:center;padding:8px;box-sizing:border-box}
537
+ .dht-sticker-placeholder{font-size:48px;margin-bottom:8px}
538
+ .dht-sticker-name{color:#b5bac1;font-size:11px;word-break:break-word}
193
539
  `;
194
540
 
195
- // ============================================================================
196
- // Back-to-top floating button
197
- // ============================================================================
198
- exports.backToTop = `(function(){
199
- var btn = document.createElement('button');
200
- btn.className = 'dht-back-top';
201
- btn.type = 'button';
202
- btn.title = 'Nach oben scrollen';
203
- btn.setAttribute('aria-label','Nach oben scrollen');
204
- btn.innerHTML = '&#x25B2;';
205
- document.body.appendChild(btn);
206
- btn.addEventListener('click', function(){ window.scrollTo({ top: 0, behavior: 'smooth' }); });
207
- function toggle(){ btn.style.opacity = window.scrollY > 400 ? '1' : '0'; btn.style.pointerEvents = window.scrollY > 400 ? 'auto' : 'none'; }
208
- window.addEventListener('scroll', toggle, { passive: true });
209
- toggle();
541
+ // ===========================================================================
542
+ // Advanced filter panel — keyword + author + role + date range + has-image/embed/attachment + pinned-only
543
+ // Hides non-matching messages, respects date separators
544
+ // ===========================================================================
545
+ exports.filterPanel = `(function(){
546
+ var DATA=globalThis.$discordMessage||{};
547
+ var dict=(DATA.i18n&&(DATA.i18n[DATA.lang]||DATA.i18n.en))||{};
548
+ var users=DATA.users||DATA.profiles||{};
549
+ var roles=DATA.roles||{};
550
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];});}
551
+ 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;}
552
+ 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;}
553
+ var btn=document.createElement('button');btn.className='dht-filter-toggle';btn.type='button';btn.title=dict.filterPanel||'Filter';btn.setAttribute('aria-label',dict.filterPanel||'Filter');btn.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>';document.body.appendChild(btn);
554
+ var panel=document.createElement('aside');panel.className='dht-filter';panel.setAttribute('aria-hidden','true');panel.setAttribute('role','region');
555
+ var userIds=Object.keys(users).filter(function(x){return /^\\d+$/.test(x);}).sort(function(a,b){var na=(users[a].displayName||users[a].author||'').toLowerCase();var nb=(users[b].displayName||users[b].author||'').toLowerCase();return na.localeCompare(nb);});
556
+ var roleIds=Object.keys(roles).filter(function(x){return /^\\d+$/.test(x);});
557
+ var html='<header><h3>'+esc(dict.filterPanel||'Filter')+'</h3><button class="dht-filter-close" type="button" aria-label="Close">&times;</button></header><div class="dht-filter-body">';
558
+ html+='<label class="dht-f-field">'+esc(dict.filterKeyword||'Keyword')+'<input type="search" class="dht-f-keyword" placeholder="'+esc(dict.searchPlaceholder||'Search…')+'"></label>';
559
+ html+='<label class="dht-f-field">'+esc(dict.filterAuthor||'Author')+'<input list="dht-f-authors" class="dht-f-author" placeholder="@user / ID / name"></label>';
560
+ html+='<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>';
561
+ html+='<label class="dht-f-field">'+esc(dict.filterRole||'Role')+'<input list="dht-f-roles" class="dht-f-role" placeholder="@role / ID / name"></label>';
562
+ html+='<datalist id="dht-f-roles">'+roleIds.map(function(id){var r=roles[id];return '<option value="'+esc(r.name||'')+' ('+esc(id)+')">';}).join('')+'</datalist>';
563
+ html+='<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>';
564
+ html+='<div class="dht-f-checks">';
565
+ html+='<label><input type="checkbox" class="dht-f-pinned"> '+esc(dict.filterPinned||'Pinned only')+'</label>';
566
+ html+='<label><input type="checkbox" class="dht-f-image"> '+esc(dict.filterHasImage||'Has image')+'</label>';
567
+ html+='<label><input type="checkbox" class="dht-f-embed"> '+esc(dict.filterHasEmbed||'Has embed')+'</label>';
568
+ html+='<label><input type="checkbox" class="dht-f-attach"> '+esc(dict.filterHasAttachment||'Has attachment')+'</label>';
569
+ html+='</div>';
570
+ html+='<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>';
571
+ html+='<div class="dht-f-counter" aria-live="polite"></div>';
572
+ html+='</div>';
573
+ panel.innerHTML=html;document.body.appendChild(panel);
574
+ function close(){panel.classList.remove('dht-filter--open');panel.setAttribute('aria-hidden','true');}
575
+ function open(){panel.classList.add('dht-filter--open');panel.setAttribute('aria-hidden','false');var k=panel.querySelector('.dht-f-keyword');if(k)k.focus();}
576
+ btn.addEventListener('click',open);
577
+ panel.querySelector('.dht-filter-close').addEventListener('click',close);
578
+ function apply(){
579
+ var keyword=(panel.querySelector('.dht-f-keyword').value||'').trim().toLowerCase();
580
+ var authorIdRaw=parseId(panel.querySelector('.dht-f-author').value);
581
+ var authorTextRaw=(panel.querySelector('.dht-f-author').value||'').trim().toLowerCase();
582
+ var roleIdRaw=parseId(panel.querySelector('.dht-f-role').value);
583
+ var roleTextRaw=(panel.querySelector('.dht-f-role').value||'').trim().toLowerCase();
584
+ var fromVal=panel.querySelector('.dht-f-from').value;
585
+ var toVal=panel.querySelector('.dht-f-to').value;
586
+ var fromMs=fromVal?new Date(fromVal).getTime():null;
587
+ var toMs=toVal?new Date(toVal).getTime():null;
588
+ var needPinned=panel.querySelector('.dht-f-pinned').checked;
589
+ var needImage=panel.querySelector('.dht-f-image').checked;
590
+ var needEmbed=panel.querySelector('.dht-f-embed').checked;
591
+ var needAttach=panel.querySelector('.dht-f-attach').checked;
592
+ var matchedRoleId=null;
593
+ if(roleIdRaw){matchedRoleId=roleIdRaw;}
594
+ else if(roleTextRaw){
595
+ // exact-match wins over substring
596
+ var exact=null,sub=null;
597
+ 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;}}}
598
+ matchedRoleId=exact||sub;
599
+ }
600
+ var matchedAuthorId=null;
601
+ if(authorIdRaw){matchedAuthorId=authorIdRaw;}
602
+ var msgs=document.querySelectorAll('discord-message, discord-system-message');
603
+ var visible=0;
604
+ for(var i=0;i<msgs.length;i++){
605
+ var m=msgs[i];
606
+ var ok=true;
607
+ var text=(m.getAttribute('data-text')||'').toLowerCase();
608
+ var authorId=m.getAttribute('data-author-id')||'';
609
+ var authorName=(m.getAttribute('data-author-name')||'').toLowerCase();
610
+ var rolesAttr=m.getAttribute('data-roles')||'';
611
+ var tsStr=m.getAttribute('data-timestamp');
612
+ var ts=tsStr?parseInt(tsStr,10):0;
613
+ var pinned=m.getAttribute('data-pinned')==='true';
614
+ var hasImg=m.getAttribute('data-has-image')==='true';
615
+ var hasEmb=m.getAttribute('data-has-embed')==='true';
616
+ var hasAtt=m.getAttribute('data-has-attachment')==='true';
617
+ if(keyword && text.indexOf(keyword)===-1 && authorName.indexOf(keyword)===-1)ok=false;
618
+ if(ok && matchedAuthorId && authorId!==matchedAuthorId)ok=false;
619
+ if(ok && !matchedAuthorId && !authorIdRaw && authorTextRaw && authorName.indexOf(authorTextRaw)===-1)ok=false;
620
+ if(ok && matchedRoleId){var rIds=rolesAttr.split(',');if(rIds.indexOf(matchedRoleId)===-1 && !authorHasRole(authorId,matchedRoleId))ok=false;}
621
+ // If date filter active but message has no timestamp, exclude it (safer than always-passing).
622
+ if(ok && fromMs){if(!ts || ts<fromMs)ok=false;}
623
+ if(ok && toMs){if(!ts || ts>toMs)ok=false;}
624
+ if(ok && needPinned && !pinned)ok=false;
625
+ if(ok && needImage && !hasImg)ok=false;
626
+ if(ok && needEmbed && !hasEmb)ok=false;
627
+ if(ok && needAttach && !hasAtt)ok=false;
628
+ m.style.display=ok?'':'none';
629
+ if(ok)visible++;
630
+ }
631
+ var seps=document.querySelectorAll('.dht-date-sep');
632
+ for(var s=0;s<seps.length;s++){
633
+ var sep=seps[s];var next=sep.nextElementSibling;var hasVis=false;
634
+ while(next && !next.classList.contains('dht-date-sep')){
635
+ if(next.style.display!=='none' && (next.tagName==='DISCORD-MESSAGE' || next.tagName==='DISCORD-SYSTEM-MESSAGE')){hasVis=true;break;}
636
+ next=next.nextElementSibling;
637
+ }
638
+ sep.style.display=hasVis?'':'none';
639
+ }
640
+ panel.querySelector('.dht-f-counter').textContent=(dict.filterShowing||'Showing {visible} of {total}').replace('{visible}',visible).replace('{total}',msgs.length);
641
+ }
642
+ function reset(){
643
+ panel.querySelectorAll('input').forEach(function(i){if(i.type==='checkbox')i.checked=false;else i.value='';});
644
+ document.querySelectorAll('discord-message, discord-system-message, .dht-date-sep').forEach(function(m){m.style.display='';});
645
+ panel.querySelector('.dht-f-counter').textContent='';
646
+ }
647
+ panel.querySelector('.dht-f-apply').addEventListener('click',apply);
648
+ panel.querySelector('.dht-f-reset').addEventListener('click',reset);
649
+ panel.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault();apply();}else if(e.key==='Escape'){close();}});
650
+ // Bind flag badges dict for popup access
651
+ try{globalThis.__DHT_FLAG_BADGES__=${JSON.stringify(FLAG_BADGES)};}catch(_){}
210
652
  })();`;
211
653
 
212
- exports.transcriptUtils = `
213
- .dht-back-top {
214
- position: fixed; bottom: 24px; right: 24px; width: 44px; height: 44px;
215
- border-radius: 50%; border: none; background: #5865F2; color: #fff;
216
- font-size: 16px; cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,0.45);
217
- opacity: 0; pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease;
218
- z-index: 9999;
219
- }
220
- .dht-back-top:hover { transform: translateY(-2px); }
654
+ exports.filterPanelStyles = `
655
+ .dht-filter-toggle{position:fixed;bottom:76px;right:24px;width:44px;height:44px;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}
656
+ .dht-filter-toggle:hover{background:#5865F2}
657
+ .dht-filter{position:fixed;top:0;right:-360px;width:340px;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}
658
+ .dht-filter--open{right:0}
659
+ .dht-filter header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #2b2d31}
660
+ .dht-filter h3{margin:0;font-size:14px;text-transform:uppercase;letter-spacing:.04em;color:#949ba4}
661
+ .dht-filter-close{background:none;border:none;color:#949ba4;cursor:pointer;font-size:22px;line-height:1}
662
+ .dht-filter-body{display:flex;flex-direction:column;gap:10px}
663
+ .dht-f-field{display:flex;flex-direction:column;gap:4px;font-size:11px;color:#b5bac1;text-transform:uppercase;letter-spacing:.04em;font-weight:600}
664
+ .dht-f-field input{background:#2b2d31;color:#dbdee1;border:1px solid #2b2d31;border-radius:4px;padding:6px 8px;font-size:13px;outline:none;font-family:inherit;text-transform:none;letter-spacing:0;font-weight:400}
665
+ .dht-f-field input:focus{border-color:#5865F2}
666
+ .dht-f-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
667
+ .dht-f-checks{display:flex;flex-direction:column;gap:6px;font-size:13px;color:#dbdee1;font-weight:400}
668
+ .dht-f-checks label{display:flex;align-items:center;gap:6px;cursor:pointer}
669
+ .dht-f-actions{display:flex;gap:8px;margin-top:8px}
670
+ .dht-f-actions button{flex:1;padding:8px;border:none;border-radius:4px;cursor:pointer;font-weight:600;font-size:13px}
671
+ .dht-f-apply{background:#5865F2;color:#fff}.dht-f-apply:hover{background:#4752c4}
672
+ .dht-f-reset{background:#3f4248;color:#dbdee1}.dht-f-reset:hover{background:#4a4d54}
673
+ .dht-f-counter{margin-top:8px;font-size:12px;color:#b5bac1;text-align:center}
674
+ @media (max-width:640px){.dht-filter{width:85vw;right:-85vw}.dht-filter-toggle{bottom:70px;right:16px;width:40px;height:40px}}
675
+ .dht-flag{display:inline-flex;align-items:center;background:rgba(255,255,255,.06);border-radius:4px;padding:2px 6px;font-size:12px;margin:2px 2px 0 0}
676
+ .dht-popup-flags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
677
+ /* Pinned background — Discord-style amber tint */
678
+ .dht-msg-pinned, discord-message[data-pinned="true"]{background:rgba(255,180,73,.06);border-left:2px solid rgba(255,180,73,.5);padding-left:6px;box-sizing:border-box}
679
+ /* Verified bot checkmark */
680
+ .dht-verified-check{color:#fff;font-weight:bold;margin-left:2px}
681
+ /* Slash command options line */
682
+ .dht-slash-options{display:flex;flex-wrap:wrap;gap:4px;font-size:12px;background:rgba(88,101,242,.08);padding:4px 8px;border-radius:4px;margin:2px 0;color:#b5bac1}
683
+ .dht-slash-cmd-name{color:#5865F2;font-weight:600;font-family:Consolas,Menlo,monospace}
684
+ .dht-slash-opt{display:inline-flex;gap:4px;background:rgba(255,255,255,.05);padding:1px 6px;border-radius:3px}
685
+ .dht-slash-opt-name{color:#949ba4}
686
+ .dht-slash-opt-val{color:#dbdee1;font-family:Consolas,Menlo,monospace}
687
+ /* Forwarded message nesting — depth shows via margin AND border-color shading */
688
+ .dht-forwarded[data-depth="0"]{border-left-color:#5865F2}
689
+ .dht-forwarded[data-depth="1"]{margin-left:12px;border-left-color:#4752c4}
690
+ .dht-forwarded[data-depth="2"]{margin-left:24px;border-left-color:#3a44a3}
691
+ .dht-forwarded[data-depth="3"]{margin-left:36px;border-left-color:#2c3680}
692
+ .dht-forwarded[data-depth="4"]{margin-left:48px;border-left-color:#1f275f}
693
+ .dht-forwarded-embeds{font-size:11px;color:#949ba4;margin-top:2px}
221
694
  `;
222
695
 
223
- exports.ggSansFont = '@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-400.woff2);font-family:"gg sans";font-weight:400;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-500.woff2);font-family:"gg sans";font-weight:500;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-600.woff2);font-family:"gg sans";font-weight:600;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-700.woff2);font-family:"gg sans";font-weight:700;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-800.woff2);font-family:"gg sans";font-weight:800;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-400.woff2);font-family:"gg sans";font-weight:400;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-500.woff2);font-family:"gg sans";font-weight:500;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-600.woff2);font-family:"gg sans";font-weight:600;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-700.woff2);font-family:"gg sans";font-weight:700;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-800.woff2);font-family:"gg sans";font-weight:800;font-style:italic}';
696
+ exports.ggSansFont = '@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-400.woff2);font-family:"gg sans";font-weight:400;font-style:normal;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-500.woff2);font-family:"gg sans";font-weight:500;font-style:normal;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-600.woff2);font-family:"gg sans";font-weight:600;font-style:normal;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-700.woff2);font-family:"gg sans";font-weight:700;font-style:normal;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-800.woff2);font-family:"gg sans";font-weight:800;font-style:normal;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-400.woff2);font-family:"gg sans";font-weight:400;font-style:italic;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-500.woff2);font-family:"gg sans";font-weight:500;font-style:italic;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-600.woff2);font-family:"gg sans";font-weight:600;font-style:italic;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-700.woff2);font-family:"gg sans";font-weight:700;font-style:italic;font-display:swap}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-800.woff2);font-family:"gg sans";font-weight:800;font-style:italic;font-display:swap}';
224
697
  //# sourceMappingURL=client.js.map