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);