diff --git a/app.js b/app.js index ef0a5b3..c362447 100644 --- a/app.js +++ b/app.js @@ -18,6 +18,7 @@ const state = { settings: loadSettings(), prefixAlignMax: 16, // mirrors weechat.look.prefix_align_max scroll: { pinned: true, newCount: 0 }, + smartFilter: new Map(), // bufferId → boolean }; // ─── Upload ─────────────────────────────────────────────────────────────────── @@ -290,7 +291,7 @@ function ansiToHtml(text) { const isImg = /\.(png|jpe?g|gif|webp|svg|bmp)(\?.*)?$/i.test(href); const isVid = /\.(mp4|webm|ogv|mov)(\?.*)?$/i.test(href); const btn = (isImg || isVid) - ? ` ` + ? ` ` : ''; return `${url}${btn}`; }); @@ -435,6 +436,7 @@ function handleInit(msg) { const nicks = {}; collectNicks(buf.nicklist_root, nicks); state.buffers.set(buf.id, { ...buf, lines: buf.lines||[], nicks, unread:0, highlight:0 }); + if (!state.smartFilter.has(buf.id)) state.smartFilter.set(buf.id, true); } // Data is ready — now clear the connecting state and reveal the chat UI setConnecting(false); @@ -470,6 +472,7 @@ function onDisconnected() { function onBufOpened(buf) { if (!buf) return; state.buffers.set(buf.id, { ...buf, lines:buf.lines||[], nicks:{}, unread:0, highlight:0 }); + if (!state.smartFilter.has(buf.id)) state.smartFilter.set(buf.id, true); rebuildBufList(); if (state.activeBufferId == null) activateBuffer(buf.id); } @@ -697,6 +700,29 @@ function renderChatHeader() { if (!buf) return; el('chat-title').textContent = buf.short_name || buf.name || ''; el('chat-topic').innerHTML = buf.title ? ansiToHtml(buf.title) : ''; + // Smart filter toggle — only show for IRC channel/private buffers + const lv = buf.local_variables || {}; + const isIrc = lv.plugin === 'irc' && lv.type !== 'server'; + const sfBtn = el('smartfilter-btn'); + if (isIrc) { + const on = state.smartFilter.get(buf.id) !== false; + sfBtn.textContent = on ? 'FILTER: ON' : 'FILTER: OFF'; + sfBtn.classList.toggle('sf-off', !on); + sfBtn.style.display = ''; + } else { + sfBtn.style.display = 'none'; + } +} + +function toggleSmartFilter() { + const id = state.activeBufferId; + if (id == null) return; + const cur = state.smartFilter.get(id) !== false; + state.smartFilter.set(id, !cur); + renderChatHeader(); + // Re-render messages with new filter state + const buf = state.buffers.get(id); + if (buf) renderMessages(buf); } function renderMessages(buf) { @@ -710,6 +736,11 @@ function renderMessages(buf) { function appendLine(line, scroll = true) { if (!line.displayed) return; + // Smart filter: hide join/part/quit/nick noise tagged by WeeChat + if (line.tags && line.tags.includes('irc_smart_filter')) { + const on = state.smartFilter.get(state.activeBufferId) !== false; + if (on) return; + } const box = el('messages'); // WeeChat may put newlines in a single message — split into sub-lines. @@ -1114,7 +1145,9 @@ document.addEventListener('DOMContentLoaded', () => { const type = btn.dataset.type; const existing = btn.nextElementSibling; if (existing && existing.classList.contains('media-preview')) { - existing.remove(); btn.textContent = '▶'; return; + existing.remove(); + btn.textContent = type === 'img' ? 'Show Image' : 'Show Video'; + return; } const wrap = document.createElement('span'); wrap.className = 'media-preview'; @@ -1129,9 +1162,10 @@ document.addEventListener('DOMContentLoaded', () => { vid.src = url; vid.controls = true; vid.className = 'preview-vid'; wrap.appendChild(vid); } - btn.after(wrap); btn.textContent = '▼'; + btn.after(wrap); btn.textContent = type === 'img' ? 'Hide Image' : 'Hide Video'; }); + el('smartfilter-btn').addEventListener('click', toggleSmartFilter); el('connect-btn') .addEventListener('click', connect); el('disconnect-btn').addEventListener('click', disconnect); el('theme-toggle') .addEventListener('click', toggleTheme);