195 lines
9.5 KiB
JavaScript
195 lines
9.5 KiB
JavaScript
import { state } from './state.js';
|
|
import { initTheme, toggleTheme,
|
|
openSettings, closeSettings,
|
|
saveSettingsPanel, updateBackendVis,
|
|
checkPort, openCertPage } from './settings.js';
|
|
import { connect, disconnect, wsSend } from './connection.js';
|
|
import { toggleSmartFilter } from './chat.js';
|
|
import { sendInput, onInputKey } from './input.js';
|
|
import { uploadFile, initDragDrop } from './upload.js';
|
|
import { setWsSend } from './buffers.js';
|
|
|
|
const el = id => document.getElementById(id);
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initTheme();
|
|
|
|
// ── Operator config (config.js → window.CATHODE_CONFIG) ──────────────────
|
|
const cfg = window.CATHODE_CONFIG || {};
|
|
if (cfg.uploadBackend) {
|
|
state.settings.uploadBackend = cfg.uploadBackend;
|
|
state.settings.filehostUrl = cfg.filehostUrl || '';
|
|
state.settings.imgurClientId = cfg.imgurClientId || '';
|
|
const sec = el('settings-upload-section');
|
|
if (sec) sec.style.display = 'none';
|
|
}
|
|
if (cfg.prefixAlignMax) {
|
|
state.prefixAlignMax = cfg.prefixAlignMax;
|
|
state.settings.prefixAlignMax = cfg.prefixAlignMax;
|
|
}
|
|
|
|
// ── Restore saved connection settings ────────────────────────────────────
|
|
const s = state.settings;
|
|
if (s.host) el('host').value = s.host;
|
|
if (s.port) el('port').value = s.port;
|
|
if (s.tls !== undefined) el('tls').checked = s.tls;
|
|
if (s.prefixAlignMax) state.prefixAlignMax = s.prefixAlignMax;
|
|
|
|
// ── Initial UI state ──────────────────────────────────────────────────────
|
|
el('connect-screen').style.display = '';
|
|
el('chat-screen').style.display = 'none';
|
|
el('status-dot').className = 'status-dot disconnected';
|
|
el('status-text').textContent = 'DISCONNECTED';
|
|
el('disconnect-btn').style.display = 'none';
|
|
|
|
if (location.protocol === 'https:') {
|
|
el('http-notice').style.display = '';
|
|
el('tls-locked-note').style.display = '';
|
|
}
|
|
|
|
// ── Wire wsSend into buffers (nick menu commands) ─────────────────────────
|
|
setWsSend(wsSend);
|
|
|
|
// ── Connection ────────────────────────────────────────────────────────────
|
|
el('connect-btn') .addEventListener('click', connect);
|
|
el('disconnect-btn').addEventListener('click', disconnect);
|
|
['host','port','password'].forEach(id =>
|
|
el(id).addEventListener('keydown', e => { if (e.key === 'Enter') connect(); })
|
|
);
|
|
el('port').addEventListener('input', checkPort);
|
|
|
|
// ── Chat input ────────────────────────────────────────────────────────────
|
|
el('send-btn') .addEventListener('click', () => sendInput(wsSend));
|
|
el('chat-input').addEventListener('keydown', e => onInputKey(e, wsSend));
|
|
|
|
// ── Smart filter ──────────────────────────────────────────────────────────
|
|
el('smartfilter-btn').addEventListener('click', toggleSmartFilter);
|
|
|
|
// ── Buffer close buttons (delegated) ─────────────────────────────────────
|
|
el('buffer-list').addEventListener('click', e => {
|
|
const btn = e.target.closest('.buf-close');
|
|
if (!btn) return;
|
|
e.stopPropagation();
|
|
const buf = state.buffers.get(btn.dataset.id);
|
|
if (!buf) return;
|
|
wsSend({ request: 'POST /api/input', body: { buffer_name: buf.name, command: '/close' } });
|
|
});
|
|
|
|
// ── Sidebar join button ───────────────────────────────────────────────────
|
|
el('sidebar-join-btn').addEventListener('click', () => {
|
|
const ch = prompt('Channel to join (e.g. #weechat):');
|
|
if (!ch) return;
|
|
const buf = state.buffers.get(state.activeBufferId);
|
|
if (!buf) return;
|
|
wsSend({ request: 'POST /api/input', body: { buffer_name: buf.name, command: '/join ' + ch } });
|
|
});
|
|
|
|
// ── Media preview (document-level delegation) ─────────────────────────────
|
|
document.addEventListener('click', e => {
|
|
const btn = e.target.closest('.media-toggle');
|
|
if (!btn) return;
|
|
const url = btn.dataset.url.replace(/&/g, '&');
|
|
const type = btn.dataset.type;
|
|
const existing = btn.nextElementSibling;
|
|
if (existing && existing.classList.contains('media-preview')) {
|
|
existing.remove();
|
|
btn.textContent = type === 'img' ? 'Show Image' : 'Show Video';
|
|
return;
|
|
}
|
|
const wrap = document.createElement('span');
|
|
wrap.className = 'media-preview';
|
|
if (type === 'img') {
|
|
const img = document.createElement('img');
|
|
img.src = url;
|
|
img.className = 'preview-img';
|
|
img.alt = 'image';
|
|
img.title = 'Click to open full size';
|
|
img.addEventListener('click', () => window.open(url, '_blank'));
|
|
wrap.appendChild(img);
|
|
} else {
|
|
const vid = document.createElement('video');
|
|
vid.src = url;
|
|
vid.controls = true;
|
|
vid.className = 'preview-vid';
|
|
wrap.appendChild(vid);
|
|
}
|
|
btn.after(wrap);
|
|
btn.textContent = type === 'img' ? 'Hide Image' : 'Hide Video';
|
|
});
|
|
|
|
// ── Theme ─────────────────────────────────────────────────────────────────
|
|
el('theme-toggle').addEventListener('click', toggleTheme);
|
|
window.addEventListener('cathode:themechange', () => {
|
|
const buf = state.buffers.get(state.activeBufferId);
|
|
if (buf) import('./chat.js').then(m => m.renderMessages(buf));
|
|
if (_emojiPicker) { _emojiPicker.remove(); _emojiPicker = null; }
|
|
});
|
|
|
|
// ── Emoji picker ──────────────────────────────────────────────────────────
|
|
let _emojiPicker = null;
|
|
let _emojiDataCache = null;
|
|
|
|
async function getEmojiData() {
|
|
if (_emojiDataCache) return _emojiDataCache;
|
|
const res = await fetch('vendor/emoji-data.json');
|
|
_emojiDataCache = await res.json();
|
|
return _emojiDataCache;
|
|
}
|
|
|
|
el('emoji-btn').addEventListener('click', async e => {
|
|
e.stopPropagation();
|
|
if (_emojiPicker) {
|
|
_emojiPicker.remove(); _emojiPicker = null; return;
|
|
}
|
|
if (!window.EmojiMart?.Picker) return;
|
|
const data = await getEmojiData();
|
|
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
|
|
_emojiPicker = new EmojiMart.Picker({
|
|
data,
|
|
theme,
|
|
onEmojiSelect: emoji => {
|
|
const input = el('chat-input');
|
|
const pos = input.selectionStart ?? input.value.length;
|
|
input.value =
|
|
input.value.slice(0, pos) + emoji.native + input.value.slice(pos);
|
|
input.selectionStart = input.selectionEnd = pos + [...emoji.native].length;
|
|
_emojiPicker.remove(); _emojiPicker = null;
|
|
input.focus();
|
|
},
|
|
onClickOutside: () => {
|
|
if (_emojiPicker) { _emojiPicker.remove(); _emojiPicker = null; }
|
|
},
|
|
});
|
|
_emojiPicker.style.cssText = 'position:absolute;bottom:48px;right:8px;z-index:200;';
|
|
el('main').appendChild(_emojiPicker);
|
|
});
|
|
|
|
// ── Cert helper ───────────────────────────────────────────────────────────
|
|
el('cert-btn').addEventListener('click', openCertPage);
|
|
|
|
// ── Upload ────────────────────────────────────────────────────────────────
|
|
el('upload-btn') .addEventListener('click', () => el('upload-file').click());
|
|
el('upload-file').addEventListener('change', e => {
|
|
const file = e.target.files[0];
|
|
if (file) { uploadFile(file); e.target.value = ''; }
|
|
});
|
|
initDragDrop();
|
|
|
|
// ── Settings panel ────────────────────────────────────────────────────────
|
|
el('settings-btn') .addEventListener('click', openSettings);
|
|
el('settings-close') .addEventListener('click', closeSettings);
|
|
el('settings-save') .addEventListener('click', saveSettingsPanel);
|
|
el('s-upload-backend').addEventListener('change', updateBackendVis);
|
|
el('settings-overlay').addEventListener('click', e => {
|
|
if (e.target === el('settings-overlay')) closeSettings();
|
|
});
|
|
|
|
// ── Global keyboard shortcuts ─────────────────────────────────────────────
|
|
document.addEventListener('keydown', e => {
|
|
if (e.key === 'Escape') {
|
|
closeSettings();
|
|
if (_emojiPicker) { _emojiPicker.remove(); _emojiPicker = null; }
|
|
}
|
|
});
|
|
});
|