@@ -23,10 +23,13 @@ if (qo) qo.addEventListener('click', (e) => { e.preventDefault(); chrome.tabs.cr
2323
2424// --- IRC ------------------------------------------------------------------
2525const STORE_KEY = 'ircConfig' ;
26+ const STATUS = '✻ status' ; // server/system window (always present, pinned first)
2627const irc = new IrcClient ( ) ;
2728const buffers = new Map ( ) ; // channel -> [{from,text,time,...}]
2829let active = null ;
2930
31+ function logStatus ( text ) { appendMsg ( { channel : STATUS , sys : true , text, time : Date . now ( ) } ) ; }
32+
3033function setStatus ( text , kind ) { const s = $ ( 'irc-status' ) ; s . textContent = text ; s . className = 'status ' + ( kind || '' ) ; }
3134
3235function ensureBuffer ( chan ) {
@@ -72,19 +75,22 @@ function appendMsg(line, store = true) {
7275
7376irc . addEventListener ( 'status' , ( e ) => {
7477 const { state, error } = e . detail ;
75- if ( state === 'connecting' ) setStatus ( 'Connecting…' ) ;
78+ if ( state === 'connecting' ) { setStatus ( 'Connecting…' ) ; logStatus ( 'Connecting…' ) ; }
7679 else if ( state === 'connected' ) {
7780 setStatus ( 'Connected ✓' , 'ok' ) ;
7881 $ ( 'irc-connect' ) . classList . add ( 'hidden' ) ;
7982 $ ( 'irc-client' ) . classList . remove ( 'hidden' ) ;
8083 $ ( 'irc-me' ) . textContent = irc . nick ;
84+ logStatus ( `Connected as ${ irc . nick } ` ) ;
8185 } else if ( state === 'disconnected' ) {
8286 setStatus ( 'Disconnected' , 'err' ) ;
87+ logStatus ( 'Disconnected' ) ;
8388 } else if ( state === 'error' ) {
8489 setStatus ( error || 'error' , 'err' ) ;
90+ logStatus ( 'Error: ' + ( error || 'unknown' ) ) ;
8591 }
8692} ) ;
87- irc . addEventListener ( 'joined' , ( e ) => { ensureBuffer ( e . detail . channel ) ; if ( ! active ) selectChannel ( e . detail . channel ) ; } ) ;
93+ irc . addEventListener ( 'joined' , ( e ) => { ensureBuffer ( e . detail . channel ) ; if ( ! active || active === STATUS ) selectChannel ( e . detail . channel ) ; } ) ;
8894irc . addEventListener ( 'message' , ( e ) => { appendMsg ( e . detail ) ; maybeNotify ( e . detail ) ; } ) ;
8995
9096// --- web notifications for incoming IRC messages --------------------------
@@ -119,7 +125,8 @@ if (chrome?.notifications?.onClicked) {
119125 }
120126 } ) ;
121127}
122- irc . addEventListener ( 'system' , ( e ) => { if ( e . detail . channel ) appendMsg ( { channel : e . detail . channel , sys : true , text : e . detail . text } ) ; } ) ;
128+ irc . addEventListener ( 'system' , ( e ) => { appendMsg ( { channel : e . detail . channel || STATUS , sys : true , text : e . detail . text } ) ; } ) ;
129+ irc . addEventListener ( 'names' , ( e ) => logStatus ( `Names ${ e . detail . channel } : ${ ( e . detail . names || [ ] ) . join ( ' ' ) } ` ) ) ;
123130irc . addEventListener ( 'topic' , ( e ) => { if ( e . detail . channel === active ) $ ( 'chan-title' ) . textContent = `${ e . detail . channel } — ${ e . detail . topic } ` ; } ) ;
124131
125132async function doConnect ( ) {
@@ -145,13 +152,42 @@ $('join-form').addEventListener('submit', (e) => { e.preventDefault(); const v =
145152$ ( 'say-form' ) . addEventListener ( 'submit' , ( e ) => {
146153 e . preventDefault ( ) ;
147154 const v = $ ( 'say-input' ) . value ;
148- if ( ! v . trim ( ) || ! active ) return ;
149- if ( v . startsWith ( '/join ' ) ) irc . join ( v . slice ( 6 ) . trim ( ) ) ;
150- else if ( v . startsWith ( '/msg ' ) ) { const [ , who , ... rest ] = v . split ( ' ' ) ; irc . say ( who , rest . join ( ' ' ) ) ; }
151- else irc . say ( active , v ) ;
155+ if ( ! v . trim ( ) ) return ;
156+ if ( v [ 0 ] === '/' ) handleCommand ( v . slice ( 1 ) ) ;
157+ else if ( active && active !== STATUS ) irc . say ( active , v ) ;
158+ else logStatus ( 'Not in a channel — use /join #channel' ) ;
152159 $ ( 'say-input' ) . value = '' ;
153160} ) ;
154161
162+ // Slash-commands → real IRC. Known verbs get friendly handling; anything else
163+ // is passed through verbatim (e.g. "/mode #chan +o nick", "/who #chan").
164+ function handleCommand ( raw ) {
165+ const sp = raw . indexOf ( ' ' ) ;
166+ const cmd = ( sp === - 1 ? raw : raw . slice ( 0 , sp ) ) . toLowerCase ( ) ;
167+ const rest = sp === - 1 ? '' : raw . slice ( sp + 1 ) . trim ( ) ;
168+ const args = rest ? rest . split ( / \s + / ) : [ ] ;
169+ const inChan = active && active !== STATUS ;
170+ switch ( cmd ) {
171+ case 'join' : case 'j' : if ( args [ 0 ] ) irc . join ( args [ 0 ] ) ; break ;
172+ case 'part' : case 'leave' : irc . part ( args [ 0 ] || ( inChan ? active : '' ) ) ; break ;
173+ case 'msg' : case 'query' : if ( args [ 0 ] ) irc . say ( args [ 0 ] , args . slice ( 1 ) . join ( ' ' ) ) ; break ;
174+ case 'me' :
175+ if ( inChan && rest ) {
176+ irc . send ( `PRIVMSG ${ active } :ACTION ${ rest } ` ) ;
177+ appendMsg ( { channel : active , from : irc . nick , text : rest , self : true , time : new Date ( ) . toISOString ( ) } ) ;
178+ }
179+ break ;
180+ case 'nick' : if ( args [ 0 ] ) irc . send ( `NICK ${ args [ 0 ] } ` ) ; break ;
181+ case 'topic' : if ( inChan ) irc . send ( `TOPIC ${ active } ${ rest ? ' :' + rest : '' } ` ) ; break ;
182+ case 'names' : irc . send ( `NAMES ${ args [ 0 ] || ( inChan ? active : '' ) } ` . trim ( ) ) ; break ;
183+ case 'list' : irc . send ( `LIST ${ rest } ` . trim ( ) ) ; break ;
184+ case 'whois' : if ( args [ 0 ] ) irc . send ( `WHOIS ${ args [ 0 ] } ` ) ; break ;
185+ case 'quit' : irc . quit ( ) ; break ;
186+ case 'raw' : case 'quote' : if ( rest ) irc . send ( rest ) ; break ;
187+ default : irc . send ( raw ) ; // generic passthrough — any other IRC command
188+ }
189+ }
190+
155191// Prefill from saved config — and auto-connect if a full account is set up.
156192( async ( ) => {
157193 const { [ STORE_KEY ] : cfg } = await chrome . storage . local . get ( STORE_KEY ) ;
0 commit comments