Files
chat/web/dist/app.js
clawbot 8e3166969e
All checks were successful
check / check (push) Successful in 6s
Redesign SPA to look like a proper IRC client (closes #50) (#53)
## Summary

Complete UI overhaul of the embedded web SPA to look and feel like a proper IRC client.

## Changes

### Layout & Structure
- **Tab bar** at top with channel/DM/server tabs and unread indicators
- **Topic bar** below tabs for channel windows
- **Messages panel** with classic IRC message formatting
- **User list** on right side for channels with @/+/regular prefixes
- **Persistent input line** at bottom with IRC-style prompt `[nick] #channel >`

### IRC Commands
Full command support in the input line:
- `/join #channel` — Join a channel
- `/part [reason]` — Part the current channel
- `/msg nick message` — Send a private message
- `/me action` — Send a CTCP ACTION
- `/nick newnick` — Change nickname
- `/topic [text]` — View or set channel topic
- `/mode +/-flags` — Set channel modes
- `/quit [reason]` — Disconnect
- `/help` — Show available commands

### Message Display
- Messages on the **same line** as the nick: `[HH:MM:SS] <nick> message text`
- System messages: `[HH:MM:SS] * alice has parted #channel (reason)`
- Actions: `[HH:MM:SS] * alice waves hello`
- IRC vocabulary throughout ("parted" not "left", etc.)

### User List
- Right-side panel showing channel members
- Sorted by mode: `@operators` first, then `+voiced`, then regular users
- Click to open DM
- Parses RPL_NAMREPLY (353) for mode prefixes when available

### Other
- Input history with up/down arrow keys
- Dark theme with monospace font (classic IRC aesthetic)
- CTCP ACTION support (`/me`)
- RPL_TOPIC (332) parsing for server-sent topics
- Responsive: user list hidden on narrow screens

closes #50

Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: #53
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-09 22:12:34 +01:00

3 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var ie,w,Ue,pt,K,Ee,He,Re,De,he,de,pe,mt,Z={},Le=[],ht=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,ce=Array.isArray;function W(e,t){for(var n in t)e[n]=t[n];return e}function ye(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function p(e,t,n){var o,a,s,c={};for(s in t)s=="key"?o=t[s]:s=="ref"?a=t[s]:c[s]=t[s];if(arguments.length>2&&(c.children=arguments.length>3?ie.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)c[s]===void 0&&(c[s]=e.defaultProps[s]);return oe(e,c,o,a,null)}function oe(e,t,n,o,a){var s={type:e,props:t,key:n,ref:o,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:a??++Ue,__i:-1,__u:0};return a==null&&w.vnode!=null&&w.vnode(s),s}function _e(e){return e.children}function se(e,t){this.props=e,this.context=t}function Y(e,t){if(t==null)return e.__?Y(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?Y(e):null}function Fe(e){var t,n;if((e=e.__)!=null&&e.__c!=null){for(e.__e=e.__c.base=null,t=0;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null){e.__e=e.__c.base=n.__e;break}return Fe(e)}}function Me(e){(!e.__d&&(e.__d=!0)&&K.push(e)&&!ae.__r++||Ee!=w.debounceRendering)&&((Ee=w.debounceRendering)||He)(ae)}function ae(){for(var e,t,n,o,a,s,c,_=1;K.length;)K.length>_&&K.sort(Re),e=K.shift(),_=K.length,e.__d&&(n=void 0,o=void 0,a=(o=(t=e).__v).__e,s=[],c=[],t.__P&&((n=W({},o)).__v=o.__v+1,w.vnode&&w.vnode(n),ve(t.__P,n,o,t.__n,t.__P.namespaceURI,32&o.__u?[a]:null,s,a??Y(o),!!(32&o.__u),c),n.__v=o.__v,n.__.__k[n.__i]=n,Je(s,n,c),o.__e=o.__=null,n.__e!=a&&Fe(n)));ae.__r=0}function We(e,t,n,o,a,s,c,_,h,u,b){var i,m,y,C,M,I,k,g=o&&o.__k||Le,U=t.length;for(h=yt(n,t,g,h,U),i=0;i<U;i++)(y=n.__k[i])!=null&&(m=y.__i==-1?Z:g[y.__i]||Z,y.__i=i,I=ve(e,y,m,a,s,c,_,h,u,b),C=y.__e,y.ref&&m.ref!=y.ref&&(m.ref&&be(m.ref,null,y),b.push(y.ref,y.__c||C,y)),M==null&&C!=null&&(M=C),(k=!!(4&y.__u))||m.__k===y.__k?h=je(y,h,e,k):typeof y.type=="function"&&I!==void 0?h=I:C&&(h=C.nextSibling),y.__u&=-7);return n.__e=M,h}function yt(e,t,n,o,a){var s,c,_,h,u,b=n.length,i=b,m=0;for(e.__k=new Array(a),s=0;s<a;s++)(c=t[s])!=null&&typeof c!="boolean"&&typeof c!="function"?(typeof c=="string"||typeof c=="number"||typeof c=="bigint"||c.constructor==String?c=e.__k[s]=oe(null,c,null,null,null):ce(c)?c=e.__k[s]=oe(_e,{children:c},null,null,null):c.constructor===void 0&&c.__b>0?c=e.__k[s]=oe(c.type,c.props,c.key,c.ref?c.ref:null,c.__v):e.__k[s]=c,h=s+m,c.__=e,c.__b=e.__b+1,_=null,(u=c.__i=vt(c,n,h,i))!=-1&&(i--,(_=n[u])&&(_.__u|=2)),_==null||_.__v==null?(u==-1&&(a>b?m--:a<b&&m++),typeof c.type!="function"&&(c.__u|=4)):u!=h&&(u==h-1?m--:u==h+1?m++:(u>h?m--:m++,c.__u|=4))):e.__k[s]=null;if(i)for(s=0;s<b;s++)(_=n[s])!=null&&(2&_.__u)==0&&(_.__e==o&&(o=Y(_)),Be(_,_));return o}function je(e,t,n,o){var a,s;if(typeof e.type=="function"){for(a=e.__k,s=0;a&&s<a.length;s++)a[s]&&(a[s].__=e,t=je(a[s],t,n,o));return t}e.__e!=t&&(o&&(t&&e.type&&!t.parentNode&&(t=Y(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function vt(e,t,n,o){var a,s,c,_=e.key,h=e.type,u=t[n],b=u!=null&&(2&u.__u)==0;if(u===null&&_==null||b&&_==u.key&&h==u.type)return n;if(o>(b?1:0)){for(a=n-1,s=n+1;a>=0||s<t.length;)if((u=t[c=a>=0?a--:s++])!=null&&(2&u.__u)==0&&_==u.key&&h==u.type)return c}return-1}function Oe(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||ht.test(t)?n:n+"px"}function re(e,t,n,o,a){var s,c;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof o=="string"&&(e.style.cssText=o=""),o)for(t in o)n&&t in n||Oe(e.style,t,"");if(n)for(t in n)o&&n[t]==o[t]||Oe(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")s=t!=(t=t.replace(De,"$1")),c=t.toLowerCase(),t=c in e||t=="onFocusOut"||t=="onFocusIn"?c.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+s]=n,n?o?n.u=o.u:(n.u=he,e.addEventListener(t,s?pe:de,s)):e.removeEventListener(t,s?pe:de,s);else{if(a=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function $e(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t.t==null)t.t=he++;else if(t.t<n.u)return;return n(w.event?w.event(t):t)}}}function ve(e,t,n,o,a,s,c,_,h,u){var b,i,m,y,C,M,I,k,g,U,O,z,R,B,j,J,Q,P=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(h=!!(32&n.__u),s=[_=t.__e=n.__e]),(b=w.__b)&&b(t);e:if(typeof P=="function")try{if(k=t.props,g="prototype"in P&&P.prototype.render,U=(b=P.contextType)&&o[b.__c],O=b?U?U.props.value:b.__:o,n.__c?I=(i=t.__c=n.__c).__=i.__E:(g?t.__c=i=new P(k,O):(t.__c=i=new se(k,O),i.constructor=P,i.render=kt),U&&U.sub(i),i.state||(i.state={}),i.__n=o,m=i.__d=!0,i.__h=[],i._sb=[]),g&&i.__s==null&&(i.__s=i.state),g&&P.getDerivedStateFromProps!=null&&(i.__s==i.state&&(i.__s=W({},i.__s)),W(i.__s,P.getDerivedStateFromProps(k,i.__s))),y=i.props,C=i.state,i.__v=t,m)g&&P.getDerivedStateFromProps==null&&i.componentWillMount!=null&&i.componentWillMount(),g&&i.componentDidMount!=null&&i.__h.push(i.componentDidMount);else{if(g&&P.getDerivedStateFromProps==null&&k!==y&&i.componentWillReceiveProps!=null&&i.componentWillReceiveProps(k,O),t.__v==n.__v||!i.__e&&i.shouldComponentUpdate!=null&&i.shouldComponentUpdate(k,i.__s,O)===!1){for(t.__v!=n.__v&&(i.props=k,i.state=i.__s,i.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(F){F&&(F.__=t)}),z=0;z<i._sb.length;z++)i.__h.push(i._sb[z]);i._sb=[],i.__h.length&&c.push(i);break e}i.componentWillUpdate!=null&&i.componentWillUpdate(k,i.__s,O),g&&i.componentDidUpdate!=null&&i.__h.push(function(){i.componentDidUpdate(y,C,M)})}if(i.context=O,i.props=k,i.__P=e,i.__e=!1,R=w.__r,B=0,g){for(i.state=i.__s,i.__d=!1,R&&R(t),b=i.render(i.props,i.state,i.context),j=0;j<i._sb.length;j++)i.__h.push(i._sb[j]);i._sb=[]}else do i.__d=!1,R&&R(t),b=i.render(i.props,i.state,i.context),i.state=i.__s;while(i.__d&&++B<25);i.state=i.__s,i.getChildContext!=null&&(o=W(W({},o),i.getChildContext())),g&&!m&&i.getSnapshotBeforeUpdate!=null&&(M=i.getSnapshotBeforeUpdate(y,C)),J=b,b!=null&&b.type===_e&&b.key==null&&(J=Ve(b.props.children)),_=We(e,ce(J)?J:[J],t,n,o,a,s,c,_,h,u),i.base=t.__e,t.__u&=-161,i.__h.length&&c.push(i),I&&(i.__E=i.__=null)}catch(F){if(t.__v=null,h||s!=null)if(F.then){for(t.__u|=h?160:128;_&&_.nodeType==8&&_.nextSibling;)_=_.nextSibling;s[s.indexOf(_)]=null,t.__e=_}else{for(Q=s.length;Q--;)ye(s[Q]);me(t)}else t.__e=n.__e,t.__k=n.__k,F.then||me(t);w.__e(F,t,n)}else s==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):_=t.__e=bt(n.__e,t,n,o,a,s,c,h,u);return(b=w.diffed)&&b(t),128&t.__u?void 0:_}function me(e){e&&e.__c&&(e.__c.__e=!0),e&&e.__k&&e.__k.forEach(me)}function Je(e,t,n){for(var o=0;o<n.length;o++)be(n[o],n[++o],n[++o]);w.__c&&w.__c(t,e),e.some(function(a){try{e=a.__h,a.__h=[],e.some(function(s){s.call(a)})}catch(s){w.__e(s,a.__v)}})}function Ve(e){return typeof e!="object"||e==null||e.__b&&e.__b>0?e:ce(e)?e.map(Ve):W({},e)}function bt(e,t,n,o,a,s,c,_,h){var u,b,i,m,y,C,M,I=n.props||Z,k=t.props,g=t.type;if(g=="svg"?a="http://www.w3.org/2000/svg":g=="math"?a="http://www.w3.org/1998/Math/MathML":a||(a="http://www.w3.org/1999/xhtml"),s!=null){for(u=0;u<s.length;u++)if((y=s[u])&&"setAttribute"in y==!!g&&(g?y.localName==g:y.nodeType==3)){e=y,s[u]=null;break}}if(e==null){if(g==null)return document.createTextNode(k);e=document.createElementNS(a,g,k.is&&k),_&&(w.__m&&w.__m(t,s),_=!1),s=null}if(g==null)I===k||_&&e.data==k||(e.data=k);else{if(s=s&&ie.call(e.childNodes),!_&&s!=null)for(I={},u=0;u<e.attributes.length;u++)I[(y=e.attributes[u]).name]=y.value;for(u in I)if(y=I[u],u!="children"){if(u=="dangerouslySetInnerHTML")i=y;else if(!(u in k)){if(u=="value"&&"defaultValue"in k||u=="checked"&&"defaultChecked"in k)continue;re(e,u,null,y,a)}}for(u in k)y=k[u],u=="children"?m=y:u=="dangerouslySetInnerHTML"?b=y:u=="value"?C=y:u=="checked"?M=y:_&&typeof y!="function"||I[u]===y||re(e,u,y,I[u],a);if(b)_||i&&(b.__html==i.__html||b.__html==e.innerHTML)||(e.innerHTML=b.__html),t.__k=[];else if(i&&(e.innerHTML=""),We(t.type=="template"?e.content:e,ce(m)?m:[m],t,n,o,g=="foreignObject"?"http://www.w3.org/1999/xhtml":a,s,c,s?s[0]:n.__k&&Y(n,0),_,h),s!=null)for(u=s.length;u--;)ye(s[u]);_||(u="value",g=="progress"&&C==null?e.removeAttribute("value"):C!=null&&(C!==e[u]||g=="progress"&&!C||g=="option"&&C!=I[u])&&re(e,u,C,I[u],a),u="checked",M!=null&&M!=e[u]&&re(e,u,M,I[u],a))}return e}function be(e,t,n){try{if(typeof e=="function"){var o=typeof e.__u=="function";o&&e.__u(),o&&t==null||(e.__u=e(t))}else e.current=t}catch(a){w.__e(a,n)}}function Be(e,t,n){var o,a;if(w.unmount&&w.unmount(e),(o=e.ref)&&(o.current&&o.current!=e.__e||be(o,null,t)),(o=e.__c)!=null){if(o.componentWillUnmount)try{o.componentWillUnmount()}catch(s){w.__e(s,t)}o.base=o.__P=null}if(o=e.__k)for(a=0;a<o.length;a++)o[a]&&Be(o[a],t,n||typeof e.type!="function");n||ye(e.__e),e.__c=e.__=e.__e=void 0}function kt(e,t,n){return this.constructor(e,n)}function qe(e,t,n){var o,a,s,c;t==document&&(t=document.documentElement),w.__&&w.__(e,t),a=(o=typeof n=="function")?null:n&&n.__k||t.__k,s=[],c=[],ve(t,e=(!o&&n||t).__k=p(_e,null,[e]),a||Z,Z,t.namespaceURI,!o&&n?[n]:a?null:t.firstChild?ie.call(t.childNodes):null,s,!o&&n?n:a?a.__e:t.firstChild,o,c),Je(s,e,c)}ie=Le.slice,w={__e:function(e,t,n,o){for(var a,s,c;t=t.__;)if((a=t.__c)&&!a.__)try{if((s=a.constructor)&&s.getDerivedStateFromError!=null&&(a.setState(s.getDerivedStateFromError(e)),c=a.__d),a.componentDidCatch!=null&&(a.componentDidCatch(e,o||{}),c=a.__d),c)return a.__E=a}catch(_){e=_}throw e}},Ue=0,pt=function(e){return e!=null&&e.constructor===void 0},se.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=W({},this.state),typeof e=="function"&&(e=e(W({},n),this.props)),e&&W(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Me(this))},se.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Me(this))},se.prototype.render=_e,K=[],He=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Re=function(e,t){return e.__v.__b-t.__v.__b},ae.__r=0,De=/(PointerCapture)$|Capture$/i,he=0,de=$e(!1),pe=$e(!0),mt=0;var ee,x,ke,Ge,te=0,tt=[],A=w,Ke=A.__b,ze=A.__r,Qe=A.diffed,Ye=A.__c,Xe=A.unmount,Ze=A.__;function Se(e,t){A.__h&&A.__h(x,e,te||t),te=0;var n=x.__H||(x.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function N(e){return te=1,gt(ot,e)}function gt(e,t,n){var o=Se(ee++,2);if(o.t=e,!o.__c&&(o.__=[n?n(t):ot(void 0,t),function(_){var h=o.__N?o.__N[0]:o.__[0],u=o.t(h,_);h!==u&&(o.__N=[u,o.__[1]],o.__c.setState({}))}],o.__c=x,!x.__f)){var a=function(_,h,u){if(!o.__c.__H)return!0;var b=o.__c.__H.__.filter(function(m){return!!m.__c});if(b.every(function(m){return!m.__N}))return!s||s.call(this,_,h,u);var i=o.__c.props!==_;return b.forEach(function(m){if(m.__N){var y=m.__[0];m.__=m.__N,m.__N=void 0,y!==m.__[0]&&(i=!0)}}),s&&s.call(this,_,h,u)||i};x.__f=!0;var s=x.shouldComponentUpdate,c=x.componentWillUpdate;x.componentWillUpdate=function(_,h,u){if(this.__e){var b=s;s=void 0,a(_,h,u),s=b}c&&c.call(this,_,h,u)},x.shouldComponentUpdate=a}return o.__N||o.__}function $(e,t){var n=Se(ee++,3);!A.__s&&rt(n.__H,t)&&(n.__=e,n.u=t,x.__H.__h.push(n))}function L(e){return te=5,nt(function(){return{current:e}},[])}function nt(e,t){var n=Se(ee++,7);return rt(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function X(e,t){return te=8,nt(function(){return e},t)}function St(){for(var e;e=tt.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(le),e.__H.__h.forEach(ge),e.__H.__h=[]}catch(t){e.__H.__h=[],A.__e(t,e.__v)}}A.__b=function(e){x=null,Ke&&Ke(e)},A.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),Ze&&Ze(e,t)},A.__r=function(e){ze&&ze(e),ee=0;var t=(x=e.__c).__H;t&&(ke===x?(t.__h=[],x.__h=[],t.__.forEach(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.forEach(le),t.__h.forEach(ge),t.__h=[],ee=0)),ke=x},A.diffed=function(e){Qe&&Qe(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(tt.push(t)!==1&&Ge===A.requestAnimationFrame||((Ge=A.requestAnimationFrame)||wt)(St)),t.__H.__.forEach(function(n){n.u&&(n.__H=n.u),n.u=void 0})),ke=x=null},A.__c=function(e,t){t.some(function(n){try{n.__h.forEach(le),n.__h=n.__h.filter(function(o){return!o.__||ge(o)})}catch(o){t.some(function(a){a.__h&&(a.__h=[])}),t=[],A.__e(o,n.__v)}}),Ye&&Ye(e,t)},A.unmount=function(e){Xe&&Xe(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.forEach(function(o){try{le(o)}catch(a){t=a}}),n.__H=void 0,t&&A.__e(t,n.__v))};var et=typeof requestAnimationFrame=="function";function wt(e){var t,n=function(){clearTimeout(o),et&&cancelAnimationFrame(t),setTimeout(e)},o=setTimeout(n,35);et&&(t=requestAnimationFrame(n))}function le(e){var t=x,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),x=t}function ge(e){var t=x;e.__c=e.__(),x=t}function rt(e,t){return!e||e.length!==t.length||t.some(function(n,o){return n!==e[o]})}function ot(e,t){return typeof t=="function"?t(e):t}var Ct="/api/v1",Tt=15,xt=3e3,It=1e4,Ce="ACTION ",Te="";function E(e,t={}){let n=localStorage.getItem("neoirc_token"),o={"Content-Type":"application/json",...t.headers||{}};n&&(o.Authorization=`Bearer ${n}`);let{signal:a,...s}=t;return fetch(Ct+e,{...s,headers:o,signal:a}).then(async c=>{let _=await c.json().catch(()=>null);if(!c.ok)throw{status:c.status,data:_};return _})}function At(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})}function we(e){let t=0;for(let o=0;o<e.length;o++)t=e.charCodeAt(o)+((t<<5)-t);return`hsl(${Math.abs(t)%360}, 60%, 65%)`}function Pt(e){return e.startsWith(Ce)&&e.endsWith(Te)}function Nt(e){return e.slice(Ce.length,-Te.length)}function Et({onLogin:e}){let[t,n]=N(""),[o,a]=N(""),[s,c]=N(""),[_,h]=N("NeoIRC"),u=L();return $(()=>{E("/server").then(m=>{m.name&&h(m.name),m.motd&&c(m.motd)}).catch(()=>{}),localStorage.getItem("neoirc_token")&&E("/state").then(m=>e(m.nick)).catch(()=>localStorage.removeItem("neoirc_token")),u.current?.focus()},[]),p("div",{class:"login-screen"},p("div",{class:"login-box"},p("h1",null,_),s&&p("pre",{class:"motd"},s),p("form",{onSubmit:async i=>{i.preventDefault(),a("");try{let m=await E("/session",{method:"POST",body:JSON.stringify({nick:t.trim()})});localStorage.setItem("neoirc_token",m.token),e(m.nick)}catch(m){a(m.data?.error||"Connection failed")}}},p("label",null,"Nickname:"),p("input",{ref:u,type:"text",placeholder:"Enter nickname",value:t,onInput:i=>n(i.target.value),maxLength:32,autoFocus:!0}),p("button",{type:"submit"},"Connect")),o&&p("div",{class:"error"},o)))}function Mt({msg:e,myNick:t}){let n=At(e.ts);return e.system?p("div",{class:"message system-message"},p("span",{class:"timestamp"},"[",n,"]"),p("span",{class:"system-text"}," * ",e.text)):e.isAction?p("div",{class:"message action-message"},p("span",{class:"timestamp"},"[",n,"]"),p("span",{class:"action-text"}," ","* ",p("span",{style:{color:we(e.from)}},e.from)," ",e.text)):p("div",{class:"message"},p("span",{class:"timestamp"},"[",n,"]")," ",p("span",{class:"nick",style:{color:we(e.from)}},"<",e.from,">")," ",p("span",{class:"content"},e.text))}function Ot({members:e,onNickClick:t}){let n=[],o=[],a=[];for(let _ of e){let h=_.mode||"";h==="o"?n.push(_):h==="v"?o.push(_):a.push(_)}let s=(_,h)=>_.nick.toLowerCase().localeCompare(h.nick.toLowerCase());n.sort(s),o.sort(s),a.sort(s);let c=(_,h)=>p("div",{class:"nick-entry",onClick:()=>t(_.nick),title:_.nick},p("span",{class:"nick-prefix"},h),p("span",{class:"nick-name",style:{color:we(_.nick)}},_.nick));return p("div",{class:"user-list"},p("div",{class:"user-list-header"},e.length," user",e.length!==1?"s":""),p("div",{class:"user-list-entries"},n.map(_=>c(_,"@")),o.map(_=>c(_,"+")),a.map(_=>c(_,""))))}function $t(){let[e,t]=N(!1),[n,o]=N(""),[a,s]=N([{type:"server",name:"Server"}]),[c,_]=N(0),[h,u]=N({Server:[]}),[b,i]=N({}),[m,y]=N({}),[C,M]=N({}),[I,k]=N(""),[g,U]=N(!0),[O,z]=N([]),[R,B]=N(-1),j=L(0),J=L(new Set),Q=L(null),P=L(a),F=L(c),ne=L(n),xe=L(),Ie=L();$(()=>{P.current=a},[a]),$(()=>{F.current=c},[c]),$(()=>{ne.current=n},[n]),$(()=>{let r=a.filter(l=>l.type==="channel").map(l=>l.name);localStorage.setItem("neoirc_channels",JSON.stringify(r))},[a]),$(()=>{let r=a[c];r&&M(l=>({...l,[r.name]:0}))},[c,a]);let D=X((r,l)=>{if(l.id&&J.current.has(l.id))return;l.id&&J.current.add(l.id),u(f=>({...f,[r]:[...f[r]||[],l]}));let d=P.current[F.current];(!d||d.name!==r)&&M(f=>({...f,[r]:(f[r]||0)+1}))},[]),T=X((r,l)=>{u(d=>({...d,[r]:[...d[r]||[],{id:"sys-"+Date.now()+"-"+Math.random(),ts:new Date().toISOString(),text:l,system:!0}]}))},[]),q=X(r=>{let l=r.replace("#","");E(`/channels/${l}/members`).then(d=>{i(f=>({...f,[r]:d}))}).catch(()=>{})},[]),ue=X(r=>{let l=Array.isArray(r.body)?r.body.join(`
`):"",d={id:r.id,ts:r.ts,from:r.from,to:r.to,command:r.command};switch(r.command){case"PRIVMSG":case"NOTICE":{let f=l,v=!1;Pt(f)&&(f=Nt(f),v=!0);let S={...d,text:f,system:!1,isAction:v},G=r.to;if(G&&G.startsWith("#"))D(G,S);else{let H=r.from===ne.current?r.to:r.from;s(fe=>fe.find(Ne=>Ne.type==="dm"&&Ne.name===H)?fe:[...fe,{type:"dm",name:H}]),D(H,S)}break}case"JOIN":{let f=`${r.from} has joined ${r.to}`;r.to&&D(r.to,{...d,text:f,system:!0}),r.to&&r.to.startsWith("#")&&q(r.to);break}case"PART":{let f=l?" ("+l+")":"",v=`${r.from} has parted ${r.to}${f}`;r.to&&D(r.to,{...d,text:v,system:!0}),r.to&&r.to.startsWith("#")&&q(r.to);break}case"QUIT":{let f=l?" ("+l+")":"",v=`${r.from} has quit${f}`;P.current.forEach(S=>{S.type==="channel"&&D(S.name,{...d,text:v,system:!0})});break}case"NICK":{let f=Array.isArray(r.body)?r.body[0]:l,v=`${r.from} is now known as ${f}`;P.current.forEach(S=>{S.type==="channel"&&D(S.name,{...d,text:v,system:!0})}),r.from===ne.current&&f&&o(f),P.current.forEach(S=>{S.type==="channel"&&q(S.name)});break}case"TOPIC":{let f=`${r.from} has changed the topic to: ${l}`;r.to&&(D(r.to,{...d,text:f,system:!0}),y(v=>({...v,[r.to]:l})));break}case"353":{if(Array.isArray(r.params)&&r.params.length>=2&&r.body){let f=r.params[1],G=(Array.isArray(r.body)?r.body[0]:String(r.body)).split(/\s+/).filter(Boolean).map(H=>H.startsWith("@")?{nick:H.slice(1),mode:"o"}:H.startsWith("+")?{nick:H.slice(1),mode:"v"}:{nick:H,mode:""});i(H=>({...H,[f]:G}))}break}case"332":{if(Array.isArray(r.params)&&r.params.length>=1){let f=r.params[0],v=Array.isArray(r.body)?r.body[0]:l;v&&y(S=>({...S,[f]:v}))}break}case"375":case"372":case"376":D("Server",{...d,text:l,system:!0});break;default:l&&D("Server",{...d,text:l,system:!0})}},[D,q]);$(()=>{if(!e)return;let r=!0;return(async()=>{for(;r;)try{let d=new AbortController;Q.current=d;let f=await E(`/messages?after=${j.current}&timeout=${Tt}`,{signal:d.signal});if(!r)break;if(U(!0),f.messages)for(let v of f.messages)ue(v);f.last_id>j.current&&(j.current=f.last_id)}catch(d){if(!r)break;if(d.name==="AbortError")continue;U(!1),await new Promise(f=>setTimeout(f,xt))}})(),()=>{r=!1,Q.current?.abort()}},[e,ue]),$(()=>{if(!e)return;let r=a[c];if(!r||r.type!=="channel")return;q(r.name);let l=setInterval(()=>q(r.name),It);return()=>clearInterval(l)},[e,c,a,q]),$(()=>{xe.current?.scrollIntoView({behavior:"smooth"})},[h,c]),$(()=>{Ie.current?.focus()},[c]),$(()=>{if(!e)return;let r=a[c];!r||r.type!=="channel"||E("/channels").then(l=>{let d=l.find(f=>f.name===r.name);d&&d.topic&&y(f=>({...f,[r.name]:d.topic}))}).catch(()=>{})},[e,c,a]);let st=X(async r=>{o(r),t(!0),T("Server",`Connected as ${r}`);let l=JSON.parse(localStorage.getItem("neoirc_channels")||"[]");for(let d of l)try{await E("/messages",{method:"POST",body:JSON.stringify({command:"JOIN",to:d})}),s(f=>f.find(v=>v.type==="channel"&&v.name===d)?f:[...f,{type:"channel",name:d}])}catch{}},[T]),at=async r=>{if(r){r=r.trim(),r.startsWith("#")||(r="#"+r);try{await E("/messages",{method:"POST",body:JSON.stringify({command:"JOIN",to:r})}),s(d=>d.find(f=>f.type==="channel"&&f.name===r)?d:[...d,{type:"channel",name:r}]);let l=P.current.length;_(l);try{let d=await E(`/history?target=${encodeURIComponent(r)}&limit=50`);if(Array.isArray(d))for(let f of d)ue(f)}catch{}}catch(l){T("Server",`Failed to join ${r}: ${l.data?.error||"error"}`)}}},Ae=async(r,l)=>{try{await E("/messages",{method:"POST",body:JSON.stringify(l?{command:"PART",to:r,body:[l]}:{command:"PART",to:r})})}catch{}s(d=>d.filter(f=>!(f.type==="channel"&&f.name===r))),_(0)},it=r=>{let l=a[r];l.type==="channel"?Ae(l.name):l.type==="dm"&&(s(d=>d.filter((f,v)=>v!==r)),c>=r&&_(Math.max(0,c-1)))},Pe=r=>{if(r===ne.current)return;s(d=>d.find(f=>f.type==="dm"&&f.name===r)?d:[...d,{type:"dm",name:r}]);let l=a.findIndex(d=>d.type==="dm"&&d.name===r);_(l>=0?l:a.length)},ct=async r=>{let l=r.split(" "),d=l[0].toLowerCase(),f=a[c];switch(d){case"/join":{l[1]?at(l[1]):T("Server","Usage: /join #channel");break}case"/part":{if(f.type==="channel"){let v=l.slice(1).join(" ")||void 0;Ae(f.name,v)}else T("Server","You are not in a channel");break}case"/msg":{if(l[1]&&l.slice(2).join(" ")){let v=l[1],S=l.slice(2).join(" ");try{await E("/messages",{method:"POST",body:JSON.stringify({command:"PRIVMSG",to:v,body:[S]})}),Pe(v)}catch(G){T("Server",`Message failed: ${G.data?.error||"error"}`)}}else T("Server","Usage: /msg <nick> <message>");break}case"/me":{if(f.type==="server"){T("Server","Cannot use /me in server window");break}let v=l.slice(1).join(" ");if(v)try{await E("/messages",{method:"POST",body:JSON.stringify({command:"PRIVMSG",to:f.name,body:[Ce+v+Te]})})}catch(S){T(f.name,`Action failed: ${S.data?.error||"error"}`)}else T("Server","Usage: /me <action>");break}case"/nick":{if(l[1])try{await E("/messages",{method:"POST",body:JSON.stringify({command:"NICK",body:[l[1]]})})}catch(v){T("Server",`Nick change failed: ${v.data?.error||"error"}`)}else T("Server","Usage: /nick <newnick>");break}case"/topic":{if(f.type!=="channel"){T("Server","You are not in a channel");break}let v=l.slice(1).join(" ");if(v)try{await E("/messages",{method:"POST",body:JSON.stringify({command:"TOPIC",to:f.name,body:[v]})})}catch(S){T("Server",`Topic change failed: ${S.data?.error||"error"}`)}else T("Server",`Current topic for ${f.name}: ${m[f.name]||"(none)"}`);break}case"/mode":{if(f.type!=="channel"){T("Server","You are not in a channel");break}let v=l.slice(1);if(v.length>0)try{await E("/messages",{method:"POST",body:JSON.stringify({command:"MODE",to:f.name,params:v})})}catch(S){T("Server",`Mode change failed: ${S.data?.error||"error"}`)}else T("Server","Usage: /mode <+/-mode> [params]");break}case"/quit":{let v=l.slice(1).join(" ")||void 0;try{await E("/messages",{method:"POST",body:JSON.stringify(v?{command:"QUIT",body:[v]}:{command:"QUIT"})})}catch{}localStorage.removeItem("neoirc_token"),localStorage.removeItem("neoirc_channels"),window.location.reload();break}case"/help":{let v=["Available commands:"," /join #channel \u2014 Join a channel"," /part [reason] \u2014 Part the current channel"," /msg nick message \u2014 Send a private message"," /me action \u2014 Send an action"," /nick newnick \u2014 Change your nickname"," /topic [text] \u2014 View or set channel topic"," /mode +/-flags \u2014 Set channel modes"," /quit [reason] \u2014 Disconnect from server"," /help \u2014 Show this help"];for(let S of v)T("Server",S);break}default:T("Server",`Unknown command: ${d}`)}},_t=async()=>{let r=I.trim();if(!r)return;z(d=>{let f=[...d,r];return f.length>100&&f.shift(),f}),B(-1),k("");let l=a[c];if(l){if(r.startsWith("/")){await ct(r);return}if(l.type==="server"){T("Server","Cannot send messages to the server window. Use /join #channel first.");return}try{await E("/messages",{method:"POST",body:JSON.stringify({command:"PRIVMSG",to:l.name,body:[r]})})}catch(d){T(l.name,`Send failed: ${d.data?.error||"error"}`)}}},lt=r=>{if(r.key==="Enter")_t();else if(r.key==="ArrowUp"){if(r.preventDefault(),O.length>0){let l=R===-1?O.length-1:Math.max(0,R-1);B(l),k(O[l])}}else if(r.key==="ArrowDown"&&(r.preventDefault(),R>=0)){let l=R+1;l>=O.length?(B(-1),k("")):(B(l),k(O[l]))}};if(!e)return p(Et,{onLogin:st});let V=a[c]||a[0],ut=h[V.name]||[],ft=b[V.name]||[],dt=m[V.name]||"";return p("div",{class:"irc-app"},p("div",{class:"tab-bar"},p("div",{class:"tabs"},a.map((r,l)=>p("div",{class:`tab ${l===c?"active":""} ${C[r.name]>0&&l!==c?"has-unread":""}`,onClick:()=>_(l),key:r.name},p("span",{class:"tab-label"},(r.type==="dm",r.name)),C[r.name]>0&&l!==c&&p("span",{class:"unread-count"},"(",C[r.name],")"),r.type!=="server"&&p("span",{class:"tab-close",onClick:d=>{d.stopPropagation(),it(l)}},"\xD7")))),p("div",{class:"status-area"},!g&&p("span",{class:"status-warn"},"\u25CF Reconnecting"),p("span",{class:"status-nick"},n))),V.type==="channel"&&p("div",{class:"topic-bar"},p("span",{class:"topic-label"},"Topic:")," ",p("span",{class:"topic-text"},dt||"(no topic set)")),p("div",{class:"main-area"},p("div",{class:"messages-panel"},p("div",{class:"messages-scroll"},ut.map(r=>p(Mt,{msg:r,myNick:n,key:r.id})),p("div",{ref:xe}))),V.type==="channel"&&p(Ot,{members:ft,onNickClick:Pe})),p("div",{class:"input-line"},p("span",{class:"input-prompt"},"[",n,"]",V.type!=="server"?` ${V.name}`:""," >"),p("input",{ref:Ie,type:"text",value:I,onInput:r=>k(r.target.value),onKeyDown:lt,placeholder:V.type==="server"?"Type /help for commands":"",spellCheck:!1,autoComplete:"off"})))}qe(p($t,null),document.getElementById("root"));