gubes mirror. how does this work
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

is this anything

leah 242bbafc ce49c1a4

+199 -106
+1
core/whois.ts
··· 87 87 thisguy.registered = true; 88 88 break; 89 89 case Numeric.RPL_WHOISACCOUNT: 90 + thisguy.registered = true; 90 91 thisguy.account = msg.params[2]; 91 92 break; 92 93 case Numeric.RPL_WHOISMODES:
+31
neo/src/bits/picker.tsx
··· 1 + import { useEffect, useRef } from "preact/hooks"; 2 + 3 + export default function Picker({ onChange, children, selected, ...rest }: { 4 + onChange: (e: string) => any, 5 + selected?: string, 6 + children: any, 7 + [x: string]: any, 8 + }) { 9 + const ref = useRef<HTMLFieldSetElement>(null); 10 + useEffect(() => { 11 + const inputs = ref.current?.querySelectorAll("input[type=radio]") as NodeListOf<HTMLInputElement>; 12 + const onchange2 = (e: { currentTarget: any; }) => { 13 + onChange(e.currentTarget!.value) 14 + } 15 + for (const input of inputs) { 16 + input.addEventListener("change", onchange2); 17 + if (input.value == selected) { 18 + input.checked = true; 19 + } 20 + } 21 + return () => { 22 + for (const input of inputs) { 23 + input.removeEventListener("change", onchange2); 24 + } 25 + } 26 + }, []) 27 + return <fieldset class="view-switcher" ref={ref} aria-label="Switch Views" {...rest}> 28 + {children} 29 + </fieldset>; 30 + 31 + }
+4 -3
neo/src/bits/sidebar/network-section.tsx
··· 302 302 } 303 303 304 304 const SocketError: FunctionalComponent<{ conn: Connection }> = ({ conn }) => { 305 + const [, setLocation] = useLocation(); 305 306 return <> 306 307 <h3>Couldn't Connect</h3> 307 308 <p class="body-small low-emphasis"> 308 309 Tubes could not connect to <i>{conn.config.url}</i>. <br /> 309 - Check if you're connected to the internet, the URL is correct 310 - and the network isn't experiencing any downtime. 310 + Make sure you're connected to the internet, the URL is correct 311 + and that <i>{conn.label}</i> isn't experiencing any downtime. 311 312 </p> 312 313 313 314 <div class="buttons"> 314 315 <TextButton onClick={() => conn.connect()}>Try Again</TextButton> 315 - <TextButton>Configure</TextButton> 316 + <TextButton onClick={() => setLocation(`${connection_base(conn)}/configure`)}>Configure</TextButton> 316 317 </div> 317 318 </>; 318 319 }
+7 -22
neo/src/bits/sidebar/sidebar-footer.tsx
··· 13 13 import AllIcon from "~icons/ph/list"; 14 14 import SplitIcon from "~icons/ph/rows"; 15 15 import SeperatedIcon from "~icons/ph/square-split-horizontal"; 16 + import Picker from "../picker"; 16 17 import { sidebar_view } from "./sidebar"; 17 - import { useEffect, useRef } from "preact/hooks"; 18 18 19 19 const ViewSwitcher: FunctionalComponent = () => { 20 - const ref = useRef<HTMLFieldSetElement>(null); 21 - useEffect(() => { 22 - const inputs = ref.current?.querySelectorAll("input[type=radio]") as NodeListOf<HTMLInputElement>; 23 - const h = (e: any) => { 24 - sidebar_view.value = e.currentTarget!.value; 25 - localStorage.setItem("sidebar", e.currentTarget!.value) 26 - }; 27 - for (const input of inputs) { 28 - input.addEventListener("change", h); 29 - if (input.value == sidebar_view.value) { 30 - input.checked = true; 31 - } 32 - } 33 - return () => { 34 - for (const input of inputs) { 35 - input.removeEventListener("change", h); 36 - } 37 - } 38 - }, []) 39 - return <fieldset class="view-switcher" ref={ref} aria-label="Switch Views"> 20 + return <Picker selected={sidebar_view.value} onChange={x => { 21 + //@ts-ignore who cares 22 + sidebar_view.value = x; 23 + localStorage.setItem("sidebar", x) 24 + }}> 40 25 <label title="Intermingled"> 41 26 <input type="radio" name="view" value="intermingled" /> 42 27 <AllIcon aria-hidden /> ··· 49 34 <input type="radio" name="view" value="seperated" /> 50 35 <SeperatedIcon aria-hidden /> 51 36 </label> 52 - </fieldset>; 37 + </Picker>; 53 38 } 54 39 55 40
+24 -2
neo/src/buffer/channel-info.tsx
··· 1 - export default function ChannelInfoDialog() { 1 + import { css } from "goober"; 2 + import { IrcChannel } from "tubes_core/channel"; 3 + 4 + export default function ChannelInfo({ channel }: { channel: IrcChannel }) { 5 + 6 + return <aside class={css` 7 + grid-row: 1 / -1; 8 + border-left: 1px solid var(--colour-grey-100); 9 + padding: .75rem; 10 + min-width: 12rem; 11 + font-size: .85rem; 2 12 3 - return <></> 13 + ul { 14 + list-style: none; 15 + margin: 0; 16 + padding: 0; 17 + 18 + display: flex; 19 + flex-direction: column; 20 + } 21 + ` + " rightbar"}> 22 + <ul > 23 + a 24 + </ul> 25 + </aside> 4 26 }
+45 -1
neo/src/buffer/list-elements.tsx
··· 70 70 import GoneIcon from "~icons/ph/arrow-left"; 71 71 import { BufferContext, WhoisDialogContext } from "./page"; 72 72 import { show_whois } from "./whois"; 73 + import { css } from "goober"; 74 + 75 + const nickstyle = css` 76 + font: inherit; 77 + background-color: transparent; 78 + border: none; 79 + padding: 0; 80 + margin: 0; 81 + display: inline; 82 + align-items: baseline; 83 + gap: .25rem; 84 + justify-self: end; 85 + font-weight: 500; 86 + font-variation-settings: 'GRAD' 100; 87 + color: var(--colour); 88 + 89 + width: max-content; 90 + overflow: hidden; 91 + white-space: nowrap; 92 + text-overflow: ellipsis; 93 + 94 + &:is(button) { 95 + cursor: pointer; 96 + &:hover { 97 + text-decoration: underline; 98 + } 99 + } 100 + 101 + &.squish { 102 + font-stretch: ultra-condensed; 103 + 104 + &:hover { 105 + position: relative; 106 + z-index: 1; 107 + width: 1000%; 108 + overflow: visible; 109 + padding-right: .25rem; 110 + background-color: white; 111 + outline: 1px solid var(--colour-grey-100); 112 + font-stretch: normal; 113 + } 114 + } 115 + ` 73 116 74 117 export const Nick 75 118 : FunctionalComponent<{ colour?: string, nick: string, max_length?: number }> ··· 90 133 91 134 <button 92 135 class={` 93 - name 136 + name 94 137 ${gone.value ? "gone" : ""} 95 138 ${nick.length > max_len ? "squish" : ""} 139 + ${nickstyle} 96 140 `} 97 141 style={`--colour: var(--colour-${colour}-700)`} 98 142 title={nick}
+21 -4
neo/src/buffer/members.tsx
··· 1 1 import { css } from "goober"; 2 2 import { FunctionalComponent } from "preact"; 3 3 import { IrcChannel } from "tubes_core/channel"; 4 + import { Nick } from "./list-elements"; 5 + import { pick_colour } from "@src/chat/colours"; 4 6 5 7 export const MembersPanel: FunctionalComponent<{ channel: IrcChannel }> = ({ channel }) => { 6 8 const members = channel.$members.value; 7 9 return <aside class={css` 8 - display: flex; 9 - `}> 10 - <ul> 11 - {members.map(x => <li>{x}</li>)} 10 + grid-row: 1 / -1; 11 + border-left: 1px solid var(--colour-grey-100); 12 + padding: .75rem; 13 + min-width: 12rem; 14 + font-size: .85rem; 15 + 16 + ul { 17 + list-style: none; 18 + margin: 0; 19 + padding: 0; 20 + 21 + display: flex; 22 + flex-direction: column; 23 + } 24 + ` + " rightbar"}> 25 + <ul > 26 + {members.map(x => <li> 27 + <Nick nick={x} colour={pick_colour(x)} /> 28 + </li>)} 12 29 </ul> 13 30 </aside> 14 31 }
+27 -17
neo/src/buffer/page.tsx
··· 1 + // todo: tidy this up somehow? 1 2 import "@css/messages.css"; 2 - import { computed, signal, Signal, useSignal } from "@preact/signals"; 3 + import { signal, Signal, useSignal } from "@preact/signals"; 4 + import { IconButton, PrimaryButton } from "@src/bits/buttons"; 3 5 import { execute_command } from "@src/chat/commands"; 4 6 import ReadMarkers, { ReadMarker } from "@src/chat/read"; 5 7 import Storage from "@src/chat/storage"; 8 + import dump from "@src/debug/dump"; 6 9 import { message_style } from "@src/support"; 7 - import { FunctionalComponent, RefObject, createContext } from "preact"; 10 + import { createContext, FunctionalComponent, FunctionComponent, RefObject } from "preact"; 8 11 import { HTMLProps } from "preact/compat"; 9 12 import { useEffect, useRef } from "preact/hooks"; 13 + import { ChatBuffer } from "tubes_core/buffer"; 10 14 import { IrcChannel } from "tubes_core/channel"; 11 15 import { IrcMessage } from "tubes_core/parser"; 12 16 import Trangle from "~icons/ph/triangle-fill"; 13 17 import MessageInput from "./input"; 14 18 import { squish_messages } from "./squisher"; 15 - import dump from "@src/debug/dump"; 16 19 import { WhoisDialog, WhoisState } from "./whois"; 17 - 20 + import { create_dialog } from "@src/bits/dialog"; 21 + import { create_menu, Menu, MenuItem } from "@src/bits/menu.tsx"; 22 + import { css } from "goober"; 23 + import DebugIcon from "~icons/ph/hammer"; 18 24 import MembersIcon from "~icons/ph/users"; 19 - import { IconButton, PrimaryButton } from "@src/bits/buttons"; 20 - import { ChatBuffer } from "tubes_core/buffer"; 25 + import InfoIcon from "~icons/ph/info"; 26 + import { MembersPanel } from "./members"; 27 + import ChannelInfo from "./channel-info"; 21 28 22 29 async function load_msgs(buffer: ChatBuffer, limit = 100): Promise<IrcMessage[]> { 23 30 let count = 0; ··· 44 51 }); 45 52 } 46 53 47 - const members_panel_open = signal(false); 54 + const panel = signal<"closed" | "members" | "info">("closed"); 48 55 49 56 const BufferPage: FunctionalComponent<{ buffer: ChatBuffer }> = ({ buffer }) => { 50 57 const msgs = useSignal<IrcMessage[]>([]); ··· 137 144 msgs.value = [...msgs.value, msg]; 138 145 }} 139 146 /> 140 - {is_channel && members_panel_open.value ? <MembersPanel channel={buffer} /> : ""} 141 147 </div> 148 + {is_channel && panel.value == "members" ? <MembersPanel channel={buffer} /> : ""} 149 + {is_channel && panel.value == "info" ? <ChannelInfo channel={buffer} /> : ""} 142 150 </WhoisDialogContext.Provider> 143 151 </BufferContext.Provider>; 144 152 } ··· 198 206 {process.env.NODE_ENV == "development" && 199 207 <IconButton onClick={debug_menu.open}><DebugIcon /></IconButton> 200 208 } 209 + <ChannelInfoButton /> 201 210 <MemberCount count={channel.$members.value.length} /> 202 211 </header> 203 212 213 + const ChannelInfoButton: FunctionComponent = () => { 214 + return <IconButton 215 + onClick={() => panel.value = panel.value != "info" ? "info" : "closed"} 216 + class={panel.value == "info" ? "active" : ""} 217 + > 218 + <InfoIcon /> 219 + </IconButton> 220 + } 204 221 const MemberCount: FunctionalComponent<{ count: number }> = ({ count }) => { 205 222 return <IconButton 206 - onClick={() => members_panel_open.value = !members_panel_open.value} 207 - class={members_panel_open.value ? "active" : ""} 223 + onClick={() => panel.value = panel.value != "members" ? "members" : "closed"} 224 + class={panel.value == "members" ? "active" : ""} 208 225 > 209 226 <MembersIcon />{count} 210 227 </IconButton> 211 228 } 212 - 213 - import DebugIcon from "~icons/ph/hammer"; 214 - import { create_menu, Menu, MenuItem } from "@src/bits/menu.tsx"; 215 - import { MembersPanel } from "./members"; 216 - import { css } from "goober"; 217 - import { create_dialog, DialogControls } from "@src/bits/dialog"; 218 - import { pick_colour } from "@src/chat/colours"; 219 229 220 230 const StartOfHistory: FunctionalComponent<{ buffer: ChatBuffer }> = ({ buffer }) => { 221 231 const chathistory = buffer.conn.capabilities.has("draft/chathistory");
+17 -7
neo/src/buffer/whois.tsx
··· 1 1 import { Signal, signal, useSignal } from "@preact/signals"; 2 - import { SecondaryButton } from "@src/bits/buttons"; 2 + import { PrimaryButton, SecondaryButton } from "@src/bits/buttons"; 3 3 import { DialogControls, DialogHeader, DialogInnards } from "@src/bits/dialog"; 4 4 import { pick_colour } from "@src/chat/colours"; 5 5 import { css, styled } from "goober"; ··· 31 31 if (!nick.value) return; 32 32 conn.whois(nick.value).then(x => whois.value = x); 33 33 }, [nick]) 34 - 34 + 35 35 if (!whois.value) { 36 36 return <></> 37 37 } 38 38 const colour = useMemo(() => pick_colour(nick.value!), [nick]) 39 39 40 - return <> 40 + return <div style={ 41 + `--colour-accent-700: var(--colour-${colour}-700);` // (for the selection colour) 42 + }> 41 43 <DialogHeader colour={pick_colour(nick.value!)}> 42 44 <HeaderInnards> 43 45 <hgroup> ··· 48 50 </HeaderInnards> 49 51 </DialogHeader> 50 52 <div style="width: 36rem; margin-top: 2rem; padding: 1rem; padding-top: 0rem;"> 51 - {/* {whois_state.value.nick} aaa */} 52 53 <p class="body-small">{JSON.stringify(whois.value)}</p> 53 - <div style="display: flex"> 54 + <div class="button-row"> 55 + <SecondaryButton> 56 + message 57 + </SecondaryButton> 58 + <SecondaryButton> 59 + ignore 60 + </SecondaryButton> 61 + <SecondaryButton> 62 + etc 63 + </SecondaryButton> 54 64 <SecondaryButton style="margin-left: auto;" onClick={() => close()} title="Close"> 55 65 i've seen enough 56 66 </SecondaryButton> 57 67 </div> 58 68 </div> 59 - </> 69 + </div> 60 70 } 61 71 62 72 const HeaderInnards = styled('div')` ··· 73 83 ` 74 84 75 85 const Avatar = ({ url, nick, colour }: { url?: string, nick: string, colour: string }) => { 76 - return <figure class={css` 86 + return <figure class={css` 77 87 width: 6rem; 78 88 height: 6rem; 79 89 background-color: var(--colour-${colour}-500);
+1 -1
neo/src/chat/conns.ts
··· 30 30 31 31 function init_connection(config: ConnectionConfig): WsConnection { 32 32 const conn: WsConnection = new WsConnection(config, { 33 - history_fetcher: chathistory, 33 + history_fetcher: tubes_history, 34 34 handler: tubes_handler, 35 35 on_config_update: (conn, new_config) => { 36 36 Config.open((config, write) => {
+1 -1
neo/src/css/main.css
··· 151 151 --sidebar-border: 1px solid var(--colour-grey-200); 152 152 153 153 display: grid; 154 - grid-template-columns: max-content 1fr; 154 + grid-template-columns: max-content 1fr max-content; 155 155 grid-template-rows: 1fr min-content; 156 156 height: 100vh; 157 157 }
-42
neo/src/css/messages.css
··· 55 55 grid-column: 1; 56 56 } 57 57 58 - .name { 59 - font: inherit; 60 - background-color: transparent; 61 - border: none; 62 - padding: 0; 63 - margin: 0; 64 - display: inline; 65 - align-items: baseline; 66 - gap: .25rem; 67 - justify-self: end; 68 - font-weight: 500; 69 - font-variation-settings: 'GRAD' 100; 70 - color: var(--colour); 71 - 72 - width: max-content; 73 - overflow: hidden; 74 - white-space: nowrap; 75 - text-overflow: ellipsis; 76 - 77 - &:is(button) { 78 - cursor: pointer; 79 - &:hover { 80 - text-decoration: underline; 81 - } 82 - } 83 - 84 - &.squish { 85 - font-stretch: ultra-condensed; 86 - 87 - &:hover { 88 - position: relative; 89 - z-index: 1; 90 - width: 1000%; 91 - overflow: visible; 92 - padding-right: .25rem; 93 - background-color: white; 94 - outline: 1px solid var(--colour-grey-100); 95 - font-stretch: normal; 96 - } 97 - } 98 - } 99 - 100 58 span.action { 101 59 display: block; 102 60 overflow: visible;
+20 -6
neo/src/pages/gallery.tsx
··· 36 36 '2network2furious', 37 37 '小红书', 38 38 'フリークネットワーク', 39 - 'شبكة غريبة' //note: I do not know enough about arabic to tell if this is displaying correctly 39 + 'أعتقد أنني سأتقيأ' //note: I do not know enough about arabic to tell if this is displaying correctly 40 40 ].map(x => 41 41 <div style="display: flex; gap: .25rem; align-items: center; font-size: .8rem;"> 42 42 <NetworkIcon name={x} /> {x} ··· 58 58 import WarningIcon from "~icons/ph/triangle-fill"; 59 59 import FormField from "@src/bits/form/form-field"; 60 60 import NetworkIcon from "@src/bits/network-icon"; 61 + import Picker from "@src/bits/picker"; 61 62 62 63 const AnimatedIconSwitcherExample = () => { 63 64 const Icon = useSignal(<IconA />); ··· 66 67 <div class="panel"> 67 68 <p class="intro body-small">animated icon switcher</p> 68 69 <AnimatedIconSwitcher icon={Icon.value} /> 69 - <div style="grid-column: 1/-1;"> 70 - <PrimaryButton onClick={() => Icon.value = <IconA />}>A</PrimaryButton> 71 - <PrimaryButton onClick={() => Icon.value = <IconB />}>B</PrimaryButton> 72 - <PrimaryButton onClick={() => Icon.value = <IconC />}>C</PrimaryButton> 73 - </div> 70 + <Picker style="grid-column: 1/-1; width: max-content;" onChange={e => { 71 + if (e == "a") Icon.value = <IconA /> 72 + if (e == "b") Icon.value = <IconB /> 73 + if (e == "c") Icon.value = <IconC /> 74 + }}> 75 + <label title="Icon A"> 76 + <input type="radio" name="ex" value="a" /> 77 + <IconA aria-hidden /> 78 + </label> 79 + <label title="Intermingled"> 80 + <input type="radio" name="ex" value="b" /> 81 + <IconB aria-hidden /> 82 + </label> 83 + <label title="Intermingled"> 84 + <input type="radio" name="ex" value="c" /> 85 + <IconC aria-hidden /> 86 + </label> 87 + </Picker> 74 88 </div> 75 89 </> 76 90 }