a tool for shared writing and social publishing

Update/home (#208)

* added nav

* added a floating home header

* made the home layout into a component and applied it to the dahsboard

* delete a few unused files, created a page layout for not found errors

* further componentizing dash layout

* adding background color and padding to the header if theres a bg image

* also this

* some adjustments to the width of the sidebar if always open, removed
page title from head and put into nav

* adjustments to the leaflet grid, new leaflet list format wip

* added style adjustments if card border is on

* made the leaflet list items respect page alpha color

* pulled titles out into grid

* added support for filtering

* added the template indicator back

* added in created dates! also removed a couple stray console logs

* widened the list leaflet preview

* made mobile navigation work

* some small padding

* add contents media and use on dashboard sidebar

* remove unused forwarded ref from actionbutton

* extract out leafletlist component

* add id to publication dashboard layout

* add leaflet draft data to dashboard data query

* fix leaflet fact query on dashboard

* added a toggle to header on mobile

* bunch of type fixes and defaults

* Adding a WIP Dashboard Layout while i wait for jared to finihs his
refactor

* factor out controls and pass as prop

* fix messed up dashboard default state

* added search to home

* fix type error

* fix leaflet query on publish page

* persist dashboard states to db

* add missing action

* debounced search, unified hooks

* pubs get search woot woot

* hide irrelavent filter options

* removed reader and notification buttons, adjusting some spacing

* use speedy link for leaflet list and reposition over preview

* add rkey to publication data key

* properly set pub draft titles

* add back intersection observer refs

* upgrade to tailwind v4

* WIP add basic HomeEmptyState

* rm hardcoded test empty leaflets

* fix some tailwind v4 issues

* use timeAgo for publishedAt

* don't render embed blocks in previews

* use timeago in publication metadata

* moved settings from footer/sidebar to content area in home

* added a bg color to publised list

* Revert "moved settings from footer/sidebar to content area in home"

This reverts commit c5ee9f55d78b7ad53d6a60f9d3d68866b25e0802.

* adjusted nav in mobile footer to be more obvious

* filter out published posts from draft list

* use tailwind to apply hover to links

This gets us their useful media query

* prevent focusing on open nav menu

* prettied up some empty state

* made it work on desktop

* added the banners to the end of the leaflet list, made some buttons work

* some tweaks

* some spacing fixes, and also added layout to the discover to include nav

* wrapped a couple more not found layouts

* added bgs to the draft list

* updated subscriber list, added bg

---------

Co-authored-by: celine <celine@hyperlink.academy>
Co-authored-by: Brendan Schlagel <brendan.schlagel@gmail.com>

authored by awarm.space celine Brendan Schlagel and committed by GitHub 835d56af 22a02305

Changed files
+3591 -2062
actions
app
components
src
utils
+1 -1
actions/getIdentityData.ts
··· 25 25 id, 26 26 root_entity, 27 27 permission_token_rights(*), 28 - leaflets_in_publications(*, publications(*)) 28 + leaflets_in_publications(*, publications(*), documents(*)) 29 29 ) 30 30 ) 31 31 )`,
+16
actions/updateIdentityInterfaceState.ts
··· 1 + "use server"; 2 + 3 + import { InterfaceState } from "components/IdentityProvider"; 4 + import { getIdentityData } from "./getIdentityData"; 5 + import { supabaseServerClient } from "supabase/serverClient"; 6 + 7 + export async function updateIdentityInterfaceState( 8 + interfaceState: InterfaceState, 9 + ) { 10 + let identity = await getIdentityData(); 11 + if (!identity) return; 12 + await supabaseServerClient 13 + .from("identities") 14 + .update({ interface_state: interfaceState }) 15 + .eq("id", identity.id); 16 + }
+1 -1
app/[leaflet_id]/Actions.tsx
··· 27 27 return ( 28 28 <SpeedyLink 29 29 href={`${getBasePublicationURL(props.publication)}/dashboard`} 30 - className="hover:!no-underline" 30 + className="hover:no-underline!" 31 31 > 32 32 <ActionButton 33 33 icon={<GoBackSmall className="shrink-0" />}
+8 -14
app/[leaflet_id]/page.tsx
··· 12 12 import { getPollData } from "actions/pollActions"; 13 13 import { supabaseServerClient } from "supabase/serverClient"; 14 14 import { get_leaflet_data } from "app/api/rpc/[command]/get_leaflet_data"; 15 + import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; 15 16 16 17 export const preferredRegion = ["sfo1"]; 17 18 export const dynamic = "force-dynamic"; ··· 29 30 let rootEntity = res.data?.root_entity; 30 31 if (!rootEntity || !res.data || res.data.blocked_by_admin) 31 32 return ( 32 - <div className="w-screen h-full flex place-items-center bg-bg-leaflet"> 33 - <div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-center gap-1 w-fit"> 34 - <div className="font-bold"> 35 - Hmmm…we couldn&apos;t find that Leaflet. 36 - </div> 37 - <div> 38 - You can{" "} 39 - <a href="mailto:contact@leaflet.pub" target="blank"> 40 - email us 41 - </a>{" "} 42 - for help! 43 - </div> 44 - </div> 45 - </div> 33 + <NotFoundLayout> 34 + <p className="font-bold">Sorry, we can't find this leaflet!</p> 35 + <p> 36 + This may be a glitch on our end. If the issue persists please{" "} 37 + <a href="mailto:contact@leaflet.pub">send us a note</a>. 38 + </p> 39 + </NotFoundLayout> 46 40 ); 47 41 48 42 let [{ data }, rsvp_data, poll_data] = await Promise.all([
-8
app/[leaflet_id]/publish/PublishIllustration/PublishIllustration.tsx
··· 78 78 Math.pow((isPlanet ? y + 14 : y) - (moonPosY + 41), 2), 79 79 ); 80 80 if (moonDistance < 60) { 81 - console.log("moon collision!"); 82 - 83 81 return true; 84 82 } 85 83 ··· 90 88 Math.pow(y - (isPlanet ? star.y + 14 : star.y), 2), 91 89 ); 92 90 if (starDistance < 40) { 93 - console.log("star collision!"); 94 - 95 91 return true; 96 92 } 97 93 } ··· 147 143 y: y, 148 144 rot: Math.random() * 360, 149 145 }); 150 - console.log("planet : " + type); 151 146 } 152 147 // the first, fouth, and seventh stars are large stars 153 148 else if ( ··· 156 151 i === numberOfPlanets + 6 157 152 ) { 158 153 let type = Math.floor(Math.random() * 4); 159 - console.log("big star : " + type); 160 154 stars.push({ 161 155 type: 162 156 type === 0 ··· 172 166 }); 173 167 } else { 174 168 let type = Math.floor(Math.random() * 3); 175 - console.log("small star : " + type); 176 169 177 170 stars.push({ 178 171 type: type === 0 ? "S1" : type === 1 ? "S2" : "S3", ··· 181 174 rot: Math.random() * 360, 182 175 }); 183 176 } 184 - console.log("planetcount : " + JSON.stringify(planetCounter)); 185 177 } 186 178 } 187 179 // animate the last child
+3 -3
app/[leaflet_id]/publish/PublishPost.tsx
··· 133 133 <div 134 134 className={`w-full pl-5 pb-4 ${shareOption !== "bluesky" ? "opacity-50" : ""}`} 135 135 > 136 - <div className="opaque-container p-3 !rounded-lg"> 136 + <div className="opaque-container p-3 rounded-lg!"> 137 137 <div className="flex gap-2"> 138 138 <img 139 139 className="bg-test rounded-full w-[42px] h-[42px] shrink-0" ··· 173 173 </div> 174 174 <div className="flex justify-between"> 175 175 <Link 176 - className="hover:!no-underline font-bold" 176 + className="hover:no-underline! font-bold" 177 177 href={`/${params.leaflet_id}`} 178 178 > 179 179 Back ··· 200 200 <PublishIllustration posts_in_pub={props.posts_in_pub} /> 201 201 <h2 className="pt-2">Published!</h2> 202 202 <Link 203 - className="hover:!no-underline font-bold place-self-center pt-2" 203 + className="hover:no-underline! font-bold place-self-center pt-2" 204 204 href={`/lish/${uri.host}/${encodeURIComponent(props.record?.name || "")}/dashboard`} 205 205 > 206 206 <ButtonPrimary>Back to Dashboard</ButtonPrimary>
+21 -16
app/[leaflet_id]/publish/page.tsx
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { get_leaflet_data } from "app/api/rpc/[command]/get_leaflet_data"; 3 2 import { PublishPost } from "./PublishPost"; 4 3 import { PubLeafletPublication } from "lexicons/api"; 5 4 import { getIdentityData } from "actions/getIdentityData"; ··· 15 14 // this is now a token id not leaflet! Should probs rename 16 15 params: Promise<{ leaflet_id: string }>; 17 16 }; 18 - export default async function LeafletPage(props: Props) { 17 + export default async function PublishLeafletPage(props: Props) { 19 18 let leaflet_id = (await props.params).leaflet_id; 20 - let { result: res } = await get_leaflet_data.handler( 21 - { token_id: leaflet_id }, 22 - { supabase: supabaseServerClient }, 23 - ); 24 - let rootEntity = res.data?.root_entity; 25 - if ( 26 - !rootEntity || 27 - !res.data || 28 - res.data.blocked_by_admin || 29 - !res.data.leaflets_in_publications[0] 30 - ) 19 + let { data } = await supabaseServerClient 20 + .from("permission_tokens") 21 + .select( 22 + `*, 23 + permission_token_rights(*), 24 + leaflets_in_publications( 25 + *, 26 + publications( 27 + *, 28 + documents_in_publications(count) 29 + ), 30 + documents(*))`, 31 + ) 32 + .eq("id", leaflet_id) 33 + .single(); 34 + let rootEntity = data?.root_entity; 35 + if (!data || !rootEntity || !data.leaflets_in_publications[0]) 31 36 return ( 32 37 <div> 33 38 missin something 34 - <pre>{JSON.stringify(res.data, undefined, 2)}</pre> 39 + <pre>{JSON.stringify(data, undefined, 2)}</pre> 35 40 </div> 36 41 ); 37 42 38 43 let identity = await getIdentityData(); 39 44 if (!identity || !identity.atp_did) return null; 40 - let pub = res.data.leaflets_in_publications[0]; 45 + let pub = data.leaflets_in_publications[0]; 41 46 let agent = new AtpAgent({ service: "https://public.api.bsky.app" }); 42 47 43 48 let profile = await agent.getProfile({ actor: identity.atp_did }); 44 49 return ( 45 50 <ReplicacheProvider 46 51 rootEntity={rootEntity} 47 - token={res.data} 52 + token={data} 48 53 name={rootEntity} 49 54 initialFacts={[]} 50 55 >
+37 -8
app/api/rpc/[command]/getFactsFromHomeLeaflets.ts
··· 3 3 import type { Attribute } from "src/replicache/attributes"; 4 4 import { makeRoute } from "../lib"; 5 5 import type { Env } from "./route"; 6 + import { scanIndexLocal } from "src/replicache/utils"; 7 + import { getBlocksWithTypeLocal } from "src/hooks/queries/useBlocks"; 8 + import * as base64 from "base64-js"; 9 + import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment"; 10 + import { applyUpdate, Doc } from "yjs"; 6 11 7 12 export const getFactsFromHomeLeaflets = makeRoute({ 8 13 route: "getFactsFromHomeLeaflets", ··· 16 21 }); 17 22 18 23 if (all_facts.data) { 24 + let titles = {} as { [key: string]: string }; 25 + 26 + let facts = all_facts.data.reduce( 27 + (acc, fact) => { 28 + if (!acc[fact.root_id]) acc[fact.root_id] = []; 29 + acc[fact.root_id].push(fact as unknown as Fact<Attribute>); 30 + return acc; 31 + }, 32 + {} as { [key: string]: Fact<Attribute>[] }, 33 + ); 34 + for (let token of tokens) { 35 + let scan = scanIndexLocal(facts[token]); 36 + let [root] = scan.eav(token, "root/page"); 37 + let rootEntity = root?.data.value || token; 38 + let [title] = getBlocksWithTypeLocal(facts[token], rootEntity).filter( 39 + (b) => b.type === "text" || b.type === "heading", 40 + ); 41 + if (!title) titles[token] = "Untitled"; 42 + else { 43 + let [content] = scan.eav(title.value, "block/text"); 44 + if (!content) titles[token] = "Untitled"; 45 + else { 46 + let doc = new Doc(); 47 + const update = base64.toByteArray(content.data.value); 48 + applyUpdate(doc, update); 49 + let nodes = doc.getXmlElement("prosemirror").toArray(); 50 + let stringValue = YJSFragmentToString(nodes[0]); 51 + titles[token] = stringValue; 52 + } 53 + } 54 + } 19 55 return { 20 - result: all_facts.data.reduce( 21 - (acc, fact) => { 22 - if (!acc[fact.root_id]) acc[fact.root_id] = []; 23 - acc[fact.root_id].push(fact as unknown as Fact<Attribute>); 24 - return acc; 25 - }, 26 - {} as { [key: string]: Fact<Attribute>[] }, 27 - ), 56 + result: { facts, titles }, 28 57 }; 29 58 } 30 59
+1 -1
app/api/rpc/[command]/get_leaflet_data.ts
··· 6 6 ReturnType<(typeof get_leaflet_data)["handler"]> 7 7 >; 8 8 9 - const leaflets_in_publications_query = `leaflets_in_publications(*, publications(*, documents_in_publications(count)), documents(*))`; 9 + const leaflets_in_publications_query = `leaflets_in_publications(*, publications(*), documents(*))`; 10 10 export const get_leaflet_data = makeRoute({ 11 11 route: "get_leaflet_data", 12 12 input: z.object({
+13 -1
app/api/rpc/[command]/get_publication_data.ts
··· 2 2 import { makeRoute } from "../lib"; 3 3 import type { Env } from "./route"; 4 4 import { AtUri } from "@atproto/syntax"; 5 + import { getFactsFromHomeLeaflets } from "./getFactsFromHomeLeaflets"; 5 6 6 7 export type GetPublicationDataReturnType = Awaited< 7 8 ReturnType<(typeof get_publication_data)["handler"]> ··· 36 37 publication_subscriptions(*, identities(bsky_profiles(*))), 37 38 publication_domains(*), 38 39 leaflets_in_publications(*, 40 + documents(*), 39 41 permission_tokens(*, 40 42 permission_token_rights(*), 41 43 custom_domain_routes!custom_domain_routes_edit_permission_token_fkey(*) ··· 46 48 .eq("identity_did", did) 47 49 .single(); 48 50 49 - return { result: publication }; 51 + let leaflet_data = await getFactsFromHomeLeaflets.handler( 52 + { 53 + tokens: 54 + publication?.leaflets_in_publications.map( 55 + (l) => l.permission_tokens?.root_entity!, 56 + ) || [], 57 + }, 58 + { supabase }, 59 + ); 60 + 61 + return { result: { publication, leaflet_data: leaflet_data.result } }; 50 62 }, 51 63 });
+2 -24
app/discover/PubListing.tsx
··· 4 4 import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 5 5 import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api"; 6 6 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 7 + import { timeAgo } from "src/utils/timeAgo"; 7 8 import { Json } from "supabase/database.types"; 8 9 9 10 export const PubListing = (props: { ··· 35 36 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 36 37 backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 37 38 }} 38 - className={`!no-underline flex flex-row gap-2 39 + className={`no-underline! flex flex-row gap-2 39 40 bg-bg-leaflet 40 41 border border-border-light rounded-lg 41 42 px-3 py-3 selected-outline ··· 69 70 </BaseThemeProvider> 70 71 ); 71 72 }; 72 - 73 - export function timeAgo(timestamp: string): string { 74 - const now = new Date(); 75 - const date = new Date(timestamp); 76 - const diffMs = now.getTime() - date.getTime(); 77 - const diffSeconds = Math.floor(diffMs / 1000); 78 - const diffMinutes = Math.floor(diffSeconds / 60); 79 - const diffHours = Math.floor(diffMinutes / 60); 80 - const diffDays = Math.floor(diffHours / 24); 81 - const diffYears = Math.floor(diffDays / 365); 82 - 83 - if (diffYears > 0) { 84 - return `${diffYears} year${diffYears === 1 ? "" : "s"} ago`; 85 - } else if (diffDays > 0) { 86 - return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`; 87 - } else if (diffHours > 0) { 88 - return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; 89 - } else if (diffMinutes > 0) { 90 - return `${diffMinutes} minute${diffMinutes === 1 ? "" : "s"} ago`; 91 - } else { 92 - return "just now"; 93 - } 94 - }
-1
app/discover/SortedPublicationList.tsx
··· 26 26 ?.filter((pub) => pub.documents_in_publications.length > 0) 27 27 ?.sort((a, b) => { 28 28 if (order === "popular") { 29 - console.log("sorting by popularity"); 30 29 return ( 31 30 b.publication_subscriptions[0].count - 32 31 a.publication_subscriptions[0].count
+32 -11
app/discover/page.tsx
··· 2 2 import Link from "next/link"; 3 3 import { SortedPublicationList } from "./SortedPublicationList"; 4 4 import { Metadata } from "next"; 5 + import { DashboardLayout } from "components/PageLayouts/DashboardLayout"; 5 6 6 7 export const dynamic = "force-static"; 7 8 export const revalidate = 60; ··· 36 37 let publications = await getPublications(); 37 38 38 39 return ( 39 - <div className="bg-[#FDFCFA] w-full h-full overflow-scroll"> 40 - <div className="max-w-prose mx-auto sm:py-6 py-4 px-4"> 41 - <div className="discoverHeader flex flex-col items-center px-4"> 42 - <h1>Discover</h1> 43 - <p className="text-lg text-secondary italic mb-2"> 44 - Explore publications on Leaflet ✨ Or{" "} 45 - <Link href="/lish/createPub">make your own</Link>! 46 - </p> 47 - </div> 48 - <SortedPublicationList publications={publications} order={order} /> 40 + <div className="w-full h-full mx-auto bg-[#FDFCFA]"> 41 + <DashboardLayout 42 + id="discover" 43 + hasBackgroundImage={false} 44 + currentPage="discover" 45 + defaultTab="default" 46 + actions={null} 47 + tabs={{ 48 + default: { 49 + controls: null, 50 + content: <DiscoverContent order={order} />, 51 + }, 52 + }} 53 + /> 54 + </div> 55 + ); 56 + } 57 + 58 + const DiscoverContent = async (props: { order: string }) => { 59 + let publications = await getPublications(); 60 + 61 + return ( 62 + <div className="max-w-prose mx-auto"> 63 + <div className="discoverHeader flex flex-col items-center text-center pt-2 px-4"> 64 + <h1>Discover</h1> 65 + <p className="text-lg text-secondary italic mb-2"> 66 + Explore publications on Leaflet ✨ Or{" "} 67 + <Link href="/lish/createPub">make your own</Link>! 68 + </p> 49 69 </div> 70 + <SortedPublicationList publications={publications} order={props.order} /> 50 71 </div> 51 72 ); 52 - } 73 + };
+105 -23
app/globals.css
··· 1 - @tailwind base; 2 - @tailwind components; 3 - @tailwind utilities; 1 + @import "tailwindcss"; 2 + 3 + @theme inline { 4 + --breakpoint-*: initial; 5 + --breakpoint-sm: 640px; 6 + --breakpoint-md: 960px; 7 + --breakpoint-lg: 1280px; 8 + 9 + --radius-*: initial; 10 + --radius-none: 0; 11 + --radius-md: 0.25rem; 12 + --radius-lg: 0.5rem; 13 + --radius-full: 9999px; 14 + 15 + --color-*: initial; 16 + --color-inherit: inherit; 17 + --color-transparent: transparent; 18 + --color-current: currentColor; 19 + --color-primary: rgb(var(--primary)); 20 + --color-secondary: color-mix( 21 + in oklab, 22 + rgb(var(--primary)), 23 + rgb(var(--bg-page)) 25% 24 + ); 25 + --color-tertiary: color-mix( 26 + in oklab, 27 + rgb(var(--primary)), 28 + rgb(var(--bg-page)) 55% 29 + ); 30 + --color-border: color-mix( 31 + in oklab, 32 + rgb(var(--primary)), 33 + rgb(var(--bg-page)) 75% 34 + ); 35 + --color-border-light: color-mix( 36 + in oklab, 37 + rgb(var(--primary)), 38 + rgb(var(--bg-page)) 85% 39 + ); 40 + --color-white: #ffffff; 41 + --color-accent-1: rgb(var(--accent-1)); 42 + --color-accent-2: rgb(var(--accent-2)); 43 + --color-accent-contrast: rgb(var(--accent-contrast)); 44 + --color-bg-leaflet: rgb(var(--bg-leaflet)); 45 + --color-bg-page: rgb(var(--bg-page)); 46 + --color-highlight-1: var(--highlight-1); 47 + --color-highlight-2: rgb(var(--highlight-2)); 48 + --color-highlight-3: rgb(var(--highlight-3)); 49 + --color-test: #e18181; 50 + --color-test-blue: #48d1ef; 51 + 52 + --text-*: initial; 53 + --text-xs: 0.75rem; 54 + --text-sm: 0.875rem; 55 + --text-base: 1rem; 56 + --text-lg: 1.125rem; 57 + --text-xl: 1.625rem; 58 + --text-2xl: 2rem; 59 + 60 + --shadow-sm: 0.9px 1.5px 1.7px -1.8px rgba(var(--primary), 0.2), 61 + 4.2px 6.9px 7.8px -3.5px rgba(var(--primary), 0.15); 62 + --shadow-md: 1.2px 2.5px 2.7px -1.8px rgba(var(--primary), 0.1), 63 + 5.6px 11.6px 12.5px -3.5px rgba(var(--primary), 0.15); 64 + 65 + --font-sans: var(--font-quattro); 66 + --font-serif: Garamond; 67 + } 68 + 69 + /* 70 + The default border color has changed to `currentcolor` in Tailwind CSS v4, 71 + so we've added these compatibility styles to make sure everything still 72 + looks the same as it did with Tailwind CSS v3. 73 + 74 + If we ever want to remove these styles, we need to add an explicit border 75 + color utility to any element that depends on these defaults. 76 + */ 77 + @layer base { 78 + *, 79 + ::after, 80 + ::before, 81 + ::backdrop, 82 + ::file-selector-button { 83 + border-color: var(--color-gray-200, currentcolor); 84 + } 85 + } 4 86 5 87 @layer base { 6 88 :root { ··· 103 185 @apply text-accent-contrast; 104 186 @apply hover:cursor-pointer; 105 187 @apply no-underline; 106 - } 107 - 108 - a:hover { 109 - @apply underline; 188 + @apply hover:underline; 110 189 } 111 190 112 191 pre { ··· 126 205 /* START GLOBAL STYLING */ 127 206 128 207 /* END GLOBAL STYLING */ 208 + } 209 + button:hover { 210 + cursor: pointer; 129 211 } 130 212 131 213 blockquote { ··· 165 247 font-size: 1em; 166 248 @apply bg-border-light; 167 249 @apply font-mono; 168 - @apply px-[1px]; 169 - @apply py-[1px]; 170 - @apply -mx-[1px]; 171 - @apply -my-[1px]; 250 + @apply px-px; 251 + @apply py-px; 252 + @apply -mx-px; 253 + @apply -my-px; 172 254 @apply rounded-[4px]; 173 255 @apply box-decoration-clone; 174 256 } ··· 191 273 } 192 274 193 275 .highlight { 194 - @apply px-[1px]; 195 - @apply py-[1px]; 196 - @apply -mx-[1px]; 197 - @apply -my-[1px]; 276 + @apply px-px; 277 + @apply py-px; 278 + @apply -mx-px; 279 + @apply -my-px; 198 280 @apply rounded-[4px]; 199 281 @apply box-decoration-clone; 200 282 } ··· 213 295 } 214 296 215 297 .transparent-outline { 216 - @apply outline; 298 + @apply outline-solid; 217 299 @apply outline-transparent; 218 300 } 219 301 220 302 .selected-outline { 221 303 @apply border; 222 - @apply focus:outline; 304 + @apply focus:outline-solid; 223 305 @apply focus:outline-2; 224 306 @apply focus:outline-offset-1; 225 - @apply focus-within:outline; 307 + @apply focus-within:outline-solid; 226 308 @apply focus-within:outline-2; 227 309 @apply focus-within:outline-offset-1; 228 - @apply hover:outline; 310 + @apply hover:outline-solid; 229 311 @apply hover:outline-2; 230 312 @apply hover:outline-offset-1; 231 313 } ··· 240 322 @apply hover:border-tertiary; 241 323 242 324 @apply focus:border-tertiary; 243 - @apply focus:outline; 325 + @apply focus:outline-solid; 244 326 @apply focus:outline-tertiary; 245 327 @apply focus:outline-2; 246 328 @apply focus:outline-offset-1; 247 329 248 330 @apply focus-within:border-tertiary; 249 - @apply focus-within:outline; 331 + @apply focus-within:outline-solid; 250 332 @apply focus-within:outline-tertiary; 251 333 @apply focus-within:outline-2; 252 334 @apply focus-within:outline-offset-1; ··· 260 342 @apply border; 261 343 @apply border-border-light; 262 344 @apply rounded-lg; 263 - @apply outline; 345 + @apply outline-solid; 264 346 @apply outline-offset-1; 265 347 @apply outline-2; 266 348 @apply outline-transparent; ··· 271 353 @apply border; 272 354 @apply border-border; 273 355 @apply rounded-lg; 274 - @apply outline; 356 + @apply outline-solid; 275 357 @apply outline-offset-1; 276 358 @apply outline-2; 277 359 @apply outline-border;
app/home/AccountSettings.tsx app/home/Actions/AccountSettings.tsx
+1 -1
app/home/CreateNewButton.tsx app/home/Actions/CreateNewButton.tsx
··· 56 56 id="new-leaflet-button" 57 57 primary 58 58 icon=<AddTiny className="m-1 shrink-0" /> 59 - label="New Doc" 59 + label="New" 60 60 /> 61 61 } 62 62 >
+26
app/home/HomeEmpty/DiscoverIllo.tsx
··· 1 + import { theme } from "tailwind.config"; 2 + export const DiscoverIllo = () => { 3 + return ( 4 + <svg 5 + className="mx-auto" 6 + width="40" 7 + height="48" 8 + viewBox="0 0 40 48" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M27.995 7.21895C28.3796 6.88667 30.0087 5.46507 30.4 2.87748C30.7913 0.289891 32.8883 0.607118 32.497 3.19459C32.1056 5.78206 32.9051 7.53471 33.1993 8.00597C33.4935 8.47722 33.9112 9.87785 36.1378 10.2148C38.3645 10.5518 38.0167 12.8516 35.79 12.5149C33.5633 12.1782 33.0608 13.1244 32.3297 13.7561C31.5986 14.3878 30.6667 15.3456 30.2851 17.8686C29.9036 20.3915 27.7969 20.1388 28.1882 17.5515C28.5795 14.9641 27.5024 13.2044 27.3795 13.0075C27.2565 12.8106 25.9473 11.0264 24.2028 10.7626C22.4582 10.4988 22.8231 8.2013 24.5506 8.46255C26.2782 8.72381 27.6104 7.55122 27.995 7.21895Z" 14 + fill={theme.colors["accent-2"]} 15 + /> 16 + <path 17 + d="M14.0436 10.775C14.8742 10.425 15.789 10.2979 16.6757 10.4806C21.184 11.9238 25.7155 13.2951 30.2265 14.7299C33.7072 15.8375 35.1769 19.9862 33.9774 23.7289C32.7772 27.472 29.1634 30.0064 25.682 28.8996C25.0272 28.6913 24.4433 28.376 23.9352 27.9753C24.0578 29.8258 23.5966 31.7973 22.5415 33.5507C20.1175 37.5776 15.2021 39.3975 11.4706 37.17C10.3639 36.5092 9.64595 35.5804 8.92715 34.5472L2.07416 24.6709C1.57874 23.7422 1.40352 22.6478 1.46822 21.605C0.876338 21.6639 0.331032 21.2463 0.234179 20.6453C0.0915272 19.7582 0.253475 18.7713 0.683459 17.8694C1.10716 16.981 1.75459 16.2504 2.51161 15.8132C3.26248 15.3796 4.24046 15.1818 5.15852 15.6351C5.4442 15.7762 5.64396 16.0231 5.73785 16.3048C6.85704 16.0396 7.98533 16.1226 8.87394 16.3813L9.25524 16.5079L9.2715 16.5153L9.8597 16.7611C10.0213 16.6144 10.2168 16.4391 10.4346 16.2548C10.6319 16.0878 10.8582 15.906 11.0967 15.7234C10.6482 15.6994 10.2675 15.3614 10.21 14.9021C10.0757 13.8176 10.3941 12.7515 10.9253 11.951C11.4368 11.1802 12.2818 10.4694 13.3165 10.4497C13.6068 10.4444 13.8663 10.5731 14.0436 10.775ZM19.2798 23.5847C16.693 22.2682 13.0521 23.4364 11.0819 26.7095C9.04105 30.1008 9.84611 34.0029 12.4431 35.5539C15.0407 37.1046 18.8789 35.9739 20.9203 32.5822C22.8879 29.3123 22.2072 25.5671 19.8281 23.9129L19.2798 23.5847ZM12.5066 27.9296C13.8894 25.2252 16.6958 23.8876 18.7744 24.9417C20.8524 25.9961 21.1485 29.4309 20.0335 31.7462C18.9185 34.0614 15.8458 35.7888 13.7672 34.7356C11.6887 33.6815 11.1239 30.6341 12.5066 27.9296ZM18.863 29.3824C18.4624 29.2737 18.0443 29.5112 17.9305 29.9137C17.5451 31.2786 16.6274 32.2476 15.4816 32.5145C15.0747 32.6093 14.8171 33.0162 14.9067 33.4227C14.9968 33.8287 15.4001 34.0811 15.8067 33.9864C17.6111 33.5659 18.8818 32.0851 19.3832 30.3097C19.4968 29.907 19.2635 29.4916 18.863 29.3824ZM8.3153 18.1814C6.98035 17.7934 5.02731 18.0142 3.96143 19.774C3.02036 21.3284 3.35247 23.0216 3.70427 23.7054L8.0744 30.005C8.19007 28.557 8.65007 27.0839 9.45919 25.7395C10.78 23.5456 12.8405 22.0084 15.0456 21.4225L13.8072 20.8264L13.7998 20.8234L12.0263 19.9506C11.9598 19.9179 11.9009 19.8749 11.846 19.8299L9.65871 18.7201L8.57393 18.2668L8.3153 18.1814ZM19.0995 26.5799C18.8494 26.2497 18.3773 26.1876 18.0443 26.4416C17.7113 26.6963 17.6434 27.1707 17.8935 27.5013C17.9343 27.5552 17.977 27.6287 18.0132 27.7089C18.0501 27.7907 18.07 27.8577 18.0768 27.8914C18.1585 28.2993 18.5552 28.5591 18.9635 28.4728C19.3719 28.3861 19.6373 27.986 19.5562 27.5779C19.4936 27.265 19.3269 26.8805 19.0995 26.5799ZM29.6516 16.5241C27.4753 15.8319 24.7243 17.3802 23.7327 20.4717C22.7415 23.5634 24.082 26.4132 26.2584 27.1054C28.4347 27.7968 31.1846 26.2478 32.1759 23.1564C33.1666 20.0654 31.8272 17.217 29.6516 16.5241ZM25.0864 20.9088C25.8644 18.6948 27.8745 17.3801 29.5763 17.9724C31.2782 18.5655 31.8625 21.1674 31.2492 23.0563C30.6358 24.9452 28.4614 26.5857 26.7594 25.9927C25.0576 25.3994 24.3085 23.1233 25.0864 20.9088ZM30.1039 21.3842C29.7952 21.3437 29.5093 21.5623 29.4654 21.8729C29.3114 22.9627 28.7055 23.8052 27.8486 24.1307C27.5561 24.2419 27.4059 24.5693 27.5131 24.8623C27.6207 25.1552 27.9446 25.3027 28.2373 25.192C29.5689 24.6865 30.387 23.4274 30.5857 22.0201C30.6291 21.7096 30.4126 21.4251 30.1039 21.3842ZM16.2087 12.3087C15.6227 12.192 14.6928 12.3931 13.9963 12.9887C13.5732 13.3506 13.2385 13.8583 13.1584 14.5459C13.1767 14.5393 13.1948 14.5317 13.213 14.5253C13.7448 14.3389 14.2572 14.2221 14.6318 14.1515C14.8204 14.1159 14.9777 14.0907 15.09 14.0749C15.3279 14.0415 15.553 14.0285 15.7831 14.1367L20.1739 16.2018C20.4022 16.3094 20.5779 16.5046 20.6616 16.7419C20.7451 16.9794 20.7295 17.2415 20.6188 17.4676C20.0298 18.6703 19.6797 19.9843 19.6168 21.5873L20.1887 21.9303C20.3029 21.99 20.4164 22.0532 20.5286 22.1202C20.8892 22.3354 21.2207 22.5792 21.5247 22.8458C21.4865 21.8762 21.6184 20.8729 21.9311 19.8976C22.5837 17.8636 23.9491 16.1876 25.6185 15.2583L16.2087 12.3087ZM29.9842 19.2073C29.7647 18.9869 29.4064 18.9891 29.1846 19.2117C28.9637 19.4344 28.9614 19.7935 29.1802 20.0139C29.2181 20.052 29.2604 20.1041 29.2969 20.1626C29.334 20.222 29.3556 20.2728 29.3649 20.2995C29.4667 20.5942 29.7886 20.7483 30.0832 20.6439C30.378 20.5386 30.5364 20.2134 30.4349 19.9182C30.3553 19.6875 30.1879 19.4123 29.9842 19.2073ZM18.1891 18.5259C17.7543 18.6688 17.3359 18.813 17.0378 18.938C16.7617 19.0538 16.3329 19.3654 15.8895 19.7357L17.7842 20.6483C17.8606 19.8991 17.9962 19.1952 18.1891 18.5259ZM14.9836 16.0031C14.6688 16.0624 14.2552 16.1582 13.8411 16.3033C13.6711 16.3629 13.5046 16.4296 13.3475 16.5035C12.8795 16.7239 12.263 17.1843 11.71 17.6486L14.054 18.8379C14.1789 18.7233 14.3197 18.5926 14.4737 18.4596C14.9623 18.0377 15.6797 17.4632 16.3048 17.2012C16.554 17.0967 16.8576 16.9855 17.1753 16.8759L15.2333 15.9604C15.1623 15.9716 15.0778 15.9853 14.9836 16.0031Z" 18 + fill={theme.colors["accent-1"]} 19 + /> 20 + <path 21 + d="M10.8586 38.4374C11.0345 38.0999 11.7756 36.6608 11.3198 34.7706C10.8641 32.8804 12.4238 32.5044 12.8795 34.3945C13.3352 36.2847 14.3902 37.2634 14.7294 37.5041C15.0687 37.7448 15.7567 38.5896 17.4129 38.1905C19.0691 37.7913 19.4742 39.4713 17.818 39.8706C16.1619 40.27 16.0766 41.063 15.7422 41.7045C15.4079 42.346 15.0247 43.2688 15.4691 45.1118C15.9135 46.9548 14.3652 47.3779 13.9094 45.4878C13.4537 43.5978 12.2021 42.6929 12.0603 42.5923C11.9186 42.4917 10.4973 41.6358 9.19972 41.9486C7.90219 42.2615 7.50971 40.5782 8.79465 40.2684C10.0796 39.9586 10.6827 38.7749 10.8586 38.4374Z" 22 + fill={theme.colors["accent-2"]} 23 + /> 24 + </svg> 25 + ); 26 + };
+108
app/home/HomeEmpty/HomeEmpty.tsx
··· 1 + "use client"; 2 + 3 + import { PubListEmptyIllo } from "components/ActionBar/Publications"; 4 + import { ButtonPrimary } from "components/Buttons"; 5 + import { AddSmall } from "components/Icons/AddSmall"; 6 + import { Link } from "react-aria-components"; 7 + import { DiscoverIllo } from "./DiscoverIllo"; 8 + import { WelcomeToLeafletIllo } from "./WelcomeToLeafletIllo"; 9 + import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 10 + import { PublishSmall } from "components/Icons/PublishSmall"; 11 + import { createNewLeaflet } from "actions/createNewLeaflet"; 12 + import { useIsMobile } from "src/hooks/isMobile"; 13 + 14 + export function HomeEmptyState() { 15 + let isMobile = useIsMobile(); 16 + return ( 17 + <div className="flex flex-col gap-4 font-bold"> 18 + <div className="container p-2 flex gap-4"> 19 + <div className="w-[72px]"> 20 + <WelcomeToLeafletIllo /> 21 + </div> 22 + <div className="flex flex-col grow"> 23 + <h3 className="text-xl font-semibold pt-2">Leaflet</h3> 24 + {/*<h3>A platform for social publishing.</h3>*/} 25 + <div className="font-normal text-tertiary italic"> 26 + Write and share delightful documents! 27 + </div> 28 + <ButtonPrimary 29 + className="!text-lg my-3" 30 + onClick={async () => { 31 + let openNewLeaflet = (id: string) => { 32 + if (isMobile) { 33 + window.location.href = `/${id}?focusFirstBlock`; 34 + } else { 35 + window.open(`/${id}?focusFirstBlock`, "_blank"); 36 + } 37 + }; 38 + 39 + let id = await createNewLeaflet({ 40 + pageType: "doc", 41 + redirectUser: false, 42 + }); 43 + openNewLeaflet(id); 44 + }} 45 + > 46 + <AddSmall /> Write a Doc! 47 + </ButtonPrimary> 48 + </div> 49 + </div> 50 + <div className="flex gap-2 w-full items-center text-tertiary font-normal italic"> 51 + <hr className="border-border w-full" /> 52 + <div>or</div> 53 + <hr className="border-border w-full" /> 54 + </div> 55 + 56 + <PublicationBanner /> 57 + <DiscoverBanner /> 58 + <div className="text-tertiary italic text-sm font-normal -mt-2"> 59 + Note: Right now docs and publications are separate. Soon you'll be able 60 + to send docs to pubs! 61 + </div> 62 + </div> 63 + ); 64 + } 65 + 66 + export const PublicationBanner = (props: { small?: boolean }) => { 67 + return ( 68 + <div 69 + className={`accent-container flex sm:py-2 gap-4 items-center ${props.small ? "items-start p-2 text-sm font-normal" : "items-center p-4"}`} 70 + > 71 + {props.small ? ( 72 + <PublishSmall className="shrink-0" /> 73 + ) : ( 74 + <div className="w-[64px] mx-auto"> 75 + <PubListEmptyIllo /> 76 + </div> 77 + )} 78 + <div className="grow"> 79 + <Link href={"/lish/createPub"} className="font-bold"> 80 + Start a Publication 81 + </Link>{" "} 82 + and blog on the Atmosphere 83 + </div> 84 + </div> 85 + ); 86 + }; 87 + 88 + export const DiscoverBanner = (props: { small?: boolean }) => { 89 + return ( 90 + <div 91 + className={`accent-container flex sm:py-2 gap-4 items-center ${props.small ? "items-start p-2 text-sm font-normal" : "items-center p-4"}`} 92 + > 93 + {props.small ? ( 94 + <DiscoverSmall className="shrink-0" /> 95 + ) : ( 96 + <div className="w-[64px] mx-auto"> 97 + <DiscoverIllo /> 98 + </div> 99 + )} 100 + <div className="grow"> 101 + <Link href={"/discover"} className="font-bold"> 102 + Explore Publications 103 + </Link>{" "} 104 + already on the network 105 + </div> 106 + </div> 107 + ); 108 + };
+24
app/home/HomeEmpty/WelcomeToLeafletIllo.tsx
··· 1 + import { theme } from "tailwind.config"; 2 + 3 + export const WelcomeToLeafletIllo = () => { 4 + return ( 5 + <svg 6 + width="73" 7 + height="68" 8 + viewBox="0 0 73 68" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M21.7639 12.9818C39.5065 8.22165 72.8274 1.9906 72.8274 11.684C72.8274 13.2356 72.5131 14.6375 71.9885 15.9193C71.422 15.1994 70.6084 14.987 69.927 14.6595C69.0312 14.3006 68.1186 14.0196 67.219 13.7347C65.9446 13.3234 64.6378 12.9769 63.4944 12.3705C63.0971 12.1482 62.6977 11.8678 62.5598 11.4935C62.4128 11.1213 62.6531 10.6704 62.9592 10.3334C63.598 9.64116 64.4397 9.13591 65.3 8.65466C64.4055 9.0767 63.5312 9.49833 62.7922 10.1713C62.4481 10.4956 62.061 11.0041 62.219 11.5961C62.3834 12.1584 62.8383 12.4935 63.2483 12.7738C64.436 13.5238 65.7311 13.9496 66.9934 14.4086C67.8817 14.7243 68.7738 15.0378 69.6096 15.4095C70.4149 15.7681 71.3704 16.291 71.4895 16.8314C71.4933 16.884 71.4915 16.9 71.3928 17.0043C71.2883 17.1047 71.1421 17.196 70.9524 17.2894C70.6123 17.455 70.1758 17.5978 69.7727 17.7084C68.9351 17.937 68.049 18.1027 67.1721 18.2513C65.4162 18.5448 63.6217 18.7708 61.8323 19.0199C60.0416 19.2716 58.2462 19.5223 56.429 19.933C55.5221 20.1485 54.6197 20.3715 53.6711 20.8383C53.2374 21.0796 52.6446 21.3532 52.2737 22.1595C52.0934 22.554 52.1241 23.1248 52.2629 23.4594C52.4237 23.8666 52.582 24.0666 52.8069 24.3275C55.2093 26.8202 58.4399 26.9744 61.1399 27.4711C61.3262 27.5015 61.5132 27.5331 61.6995 27.5638C60.4533 29.2073 60.129 30.8258 61.717 32.7035C53.3556 36.0694 40.5573 34.9986 30.1731 37.5492C27.0455 38.2875 23.9357 39.315 21.0657 41.3636C19.6745 42.3955 18.2597 43.7123 17.4534 45.7152C16.5587 47.6874 17.1166 50.6073 18.6731 52.1156C19.6636 53.1771 20.8229 53.8313 21.9641 54.3002C22.9895 54.7165 24.0905 54.774 25.1536 54.5228C25.4784 54.4319 25.7983 54.2981 26.054 54.0844C26.3095 53.8704 26.4797 53.5942 26.5325 53.2894C26.5851 52.9847 26.5177 52.6675 26.3489 52.3802C26.1797 52.0928 25.9225 51.8583 25.6467 51.6635C24.8912 51.0971 24.2127 50.692 23.5432 50.4203C22.7927 50.117 22.1473 49.7103 21.7405 49.2709C21.1437 48.5942 21.0342 48.064 21.3293 47.2084C21.6382 46.365 22.4832 45.4184 23.5022 44.6605C25.5969 43.1038 28.3033 42.084 31.094 41.3705C37.0436 39.9193 43.4459 39.2868 49.7786 38.347C52.9572 37.8792 56.1424 37.383 59.342 36.6058C60.9443 36.2072 62.5256 35.7578 64.136 35.0453C64.1883 35.0196 64.2409 34.9932 64.2942 34.9672C72.897 41.9183 73.7798 46.9821 71.0959 52.3617C68.9368 56.6893 57.6782 70.1038 45.5647 66.642C17.1652 58.5254 -0.313688 61.8818 0.983643 36.35C1.62827 23.6653 13.1137 15.3026 21.7639 12.9818ZM47.5911 44.0482C47.6939 42.4007 46.335 42.3157 46.2317 43.9633C46.1286 45.6104 45.1675 46.5931 44.9397 46.8236C44.7125 47.0535 43.9263 47.8634 42.8059 47.7933C41.6859 47.7235 41.5833 49.1871 42.7141 49.2582C43.8449 49.329 44.7847 50.3786 44.8752 50.4965C44.9656 50.6142 45.7535 51.6618 45.6506 53.309C45.5476 54.9569 46.9105 55.0009 47.011 53.3939C47.1116 51.7875 47.6534 51.1346 48.0852 50.6976C48.5171 50.2607 48.7857 49.6386 50.2297 49.7289C51.6728 49.8185 51.7638 48.3548 50.3206 48.264C48.8768 48.1734 48.5288 47.3157 48.3137 47.0355C48.0986 46.7552 47.488 45.6961 47.5911 44.0482ZM18.887 18.7377C18.8903 16.7042 17.2118 16.7019 17.2083 18.7357C17.2044 20.7693 16.0954 22.0494 15.8333 22.349C15.5704 22.6494 14.6652 23.703 13.2834 23.7006C11.901 23.6983 11.8845 25.5056 13.2805 25.5082C14.675 25.5108 15.9068 26.7289 16.0286 26.8685C16.1478 27.0058 17.1971 28.2352 17.1936 30.2689C17.1902 32.3024 18.8677 32.2546 18.8713 30.2719C18.8748 28.2891 19.4922 27.4449 19.9905 26.8754C20.4888 26.3059 20.7721 25.5208 22.554 25.5238C24.336 25.5269 24.3396 23.7198 22.5579 23.7162C20.7757 23.7129 20.2846 22.6833 19.9993 22.3549C19.7136 22.0258 18.8835 20.7706 18.887 18.7377ZM70.5559 18.5209C68.5834 21.3602 65.7167 23.5582 63.5715 25.5668C62.8433 25.4447 62.1381 25.3759 61.4397 25.2875C58.698 24.9636 55.7947 24.7314 54.1506 23.0892C53.1448 22.9109 55.2165 21.8481 56.8167 21.475C58.5129 21.0124 60.2807 20.6842 62.0471 20.3597C63.8164 20.0365 65.6066 19.7363 67.384 19.3646C68.2733 19.1761 69.1747 18.9708 70.0657 18.6898C70.2249 18.6387 70.3894 18.5819 70.5559 18.5209Z" 14 + fill="color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)" 15 + /> 16 + <path 17 + fill-rule="evenodd" 18 + clip-rule="evenodd" 19 + d="M33.1936 19.9644C33.8224 20.2936 34.5595 20.6793 35.7586 19.9049C36.5426 19.3985 37.1772 17.1878 37.8852 14.7211C38.9202 11.1155 40.1122 6.96302 42.1573 6.78723C44.6243 6.57519 44.7981 7.92712 44.9319 8.96695C45.0257 9.69631 45.0997 10.2721 45.9315 10.047C46.7788 9.8176 47.3647 8.61374 48.0447 7.2167C48.984 5.28694 50.1027 2.98859 52.3373 2.38086C53.9128 1.95238 54.054 2.64187 54.1973 3.34108C54.2888 3.788 54.3812 4.2389 54.8494 4.40436C55.3107 4.56736 56.2896 4.23303 57.4149 3.84869C59.219 3.23253 61.3994 2.48783 62.4269 3.45769C63.3302 4.31032 62.738 5.52027 62.1704 6.67999C61.6896 7.66228 61.2264 8.60853 61.7046 9.27112C62.0948 9.81171 62.6783 10.0278 63.2502 10.2396C64.1857 10.5861 65.0904 10.9211 65.0681 12.6458C65.0445 14.4695 62.5034 15.5801 60.1214 16.6211C58.126 17.4931 56.2423 18.3164 56.0441 19.4692C55.9197 20.1921 56.464 20.3823 57.0738 20.5954C57.8026 20.85 58.6249 21.1373 58.5116 22.4058C58.2468 25.3711 55.1373 26.1276 52.219 26.8377C49.7141 27.4471 47.3501 28.0223 47.0466 29.9306C46.8714 31.0323 47.5477 31.0548 48.2898 31.0794C49.1981 31.1095 50.205 31.1429 49.869 33.1634C49.2261 37.03 43.6557 38.4183 38.5564 39.6893C36.6866 40.1553 34.8802 40.6056 33.4032 41.1564C33.2347 41.2193 33.1049 41.3564 33.0503 41.5278C32.2094 44.1647 31.8313 46.932 31.428 49.8838C31.3394 50.5323 31.2496 51.1896 31.1534 51.8565C31.0262 52.738 29.2486 55.1385 28.3628 55.2311C27.9656 55.2726 27.8339 52.2775 27.9611 51.3959C28.4347 48.1126 29.1515 45.0249 30.0528 42.1314C31.418 36.9751 34.9339 30.201 39.0683 24.7487C42.7265 19.8134 46.4117 15.8889 50.9826 12.4792C51.5547 12.0524 51.1837 11.3521 50.5642 11.7068C46.3602 14.1137 42.9934 17.6783 39.4855 21.2985C35.6701 25.2361 32.8282 29.9969 31.1838 33.3557C30.8939 33.9479 29.8993 33.7609 29.8756 33.102C29.6905 27.9469 29.636 23.1879 30.7621 21.1685C31.8313 19.2514 32.4334 19.5665 33.1936 19.9644Z" 20 + fill={theme.colors["accent-1"]} 21 + /> 22 + </svg> 23 + ); 24 + };
-25
app/home/HomeFooter.tsx
··· 1 - "use client"; 2 - import { Footer } from "components/ActionBar/Footer"; 3 - import { Media } from "components/Media"; 4 - import { ThemePopover } from "components/ThemeManager/ThemeSetter"; 5 - import { CreateNewLeafletButton } from "./CreateNewButton"; 6 - import { HelpPopover } from "components/HelpPopover"; 7 - import { AccountSettings } from "./AccountSettings"; 8 - import { useIdentityData } from "components/IdentityProvider"; 9 - import { useReplicache } from "src/replicache"; 10 - import { LoginActionButton } from "components/LoginButton"; 11 - 12 - export const HomeFooter = () => { 13 - let { identity } = useIdentityData(); 14 - let { rootEntity } = useReplicache(); 15 - return ( 16 - <Media mobile> 17 - <Footer> 18 - <CreateNewLeafletButton /> 19 - {identity ? <AccountSettings /> : <LoginActionButton />} 20 - <HelpPopover noShortcuts /> 21 - <ThemePopover entityID={rootEntity} home /> 22 - </Footer> 23 - </Media> 24 - ); 25 - };
app/home/HomeHelp.tsx app/home/Actions/HomeHelp.tsx
+334
app/home/HomeLayout.tsx
··· 1 + "use client"; 2 + 3 + import { getHomeDocs, HomeDoc } from "./storage"; 4 + import useSWR from "swr"; 5 + import { 6 + Fact, 7 + PermissionToken, 8 + ReplicacheProvider, 9 + useEntity, 10 + } from "src/replicache"; 11 + import { LeafletListItem } from "./LeafletList/LeafletListItem"; 12 + import { useIdentityData } from "components/IdentityProvider"; 13 + import type { Attribute } from "src/replicache/attributes"; 14 + import { callRPC } from "app/api/rpc/client"; 15 + import { StaticLeafletDataContext } from "components/PageSWRDataProvider"; 16 + import { HomeSmall } from "components/Icons/HomeSmall"; 17 + import { 18 + HomeDashboardControls, 19 + DashboardLayout, 20 + DashboardState, 21 + useDashboardState, 22 + } from "components/PageLayouts/DashboardLayout"; 23 + import { Actions } from "./Actions/Actions"; 24 + import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 25 + import { Json } from "supabase/database.types"; 26 + import { useTemplateState } from "./Actions/CreateNewButton"; 27 + import { CreateNewLeafletButton } from "./Actions/CreateNewButton"; 28 + import { ActionButton } from "components/ActionBar/ActionButton"; 29 + import { AddTiny } from "components/Icons/AddTiny"; 30 + import { 31 + get_leaflet_data, 32 + GetLeafletDataReturnType, 33 + } from "app/api/rpc/[command]/get_leaflet_data"; 34 + import { useEffect, useRef, useState } from "react"; 35 + import { Input } from "components/Input"; 36 + import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 37 + import { 38 + ButtonPrimary, 39 + ButtonSecondary, 40 + ButtonTertiary, 41 + } from "components/Buttons"; 42 + import { AddSmall } from "components/Icons/AddSmall"; 43 + import { PublishIllustration } from "app/[leaflet_id]/publish/PublishIllustration/PublishIllustration"; 44 + import { PubListEmptyIllo } from "components/ActionBar/Publications"; 45 + import { theme } from "tailwind.config"; 46 + import Link from "next/link"; 47 + import { DiscoverIllo } from "./HomeEmpty/DiscoverIllo"; 48 + import { WelcomeToLeafletIllo } from "./HomeEmpty/WelcomeToLeafletIllo"; 49 + import { 50 + DiscoverBanner, 51 + HomeEmptyState, 52 + PublicationBanner, 53 + } from "./HomeEmpty/HomeEmpty"; 54 + 55 + type Leaflet = { 56 + added_at: string; 57 + token: PermissionToken & { 58 + leaflets_in_publications?: Exclude< 59 + GetLeafletDataReturnType["result"]["data"], 60 + null 61 + >["leaflets_in_publications"]; 62 + }; 63 + }; 64 + 65 + export const HomeLayout = (props: { 66 + entityID: string; 67 + titles: { [root_entity: string]: string }; 68 + initialFacts: { 69 + [root_entity: string]: Fact<Attribute>[]; 70 + }; 71 + }) => { 72 + let hasBackgroundImage = !!useEntity( 73 + props.entityID, 74 + "theme/background-image", 75 + ); 76 + let cardBorderHidden = !!useCardBorderHidden(props.entityID); 77 + 78 + let [searchValue, setSearchValue] = useState(""); 79 + let [debouncedSearchValue, setDebouncedSearchValue] = useState(""); 80 + 81 + useDebouncedEffect( 82 + () => { 83 + setDebouncedSearchValue(searchValue); 84 + }, 85 + 200, 86 + [searchValue], 87 + ); 88 + 89 + let { identity } = useIdentityData(); 90 + 91 + let hasPubs = !identity || identity.publications.length === 0 ? false : true; 92 + let hasTemplates = 93 + useTemplateState((s) => s.templates).length === 0 ? false : true; 94 + 95 + return ( 96 + <DashboardLayout 97 + id="home" 98 + hasBackgroundImage={hasBackgroundImage} 99 + currentPage="home" 100 + defaultTab="home" 101 + actions={<Actions />} 102 + tabs={{ 103 + home: { 104 + controls: ( 105 + <HomeDashboardControls 106 + defaultDisplay={"grid"} 107 + searchValue={searchValue} 108 + setSearchValueAction={setSearchValue} 109 + hasBackgroundImage={hasBackgroundImage} 110 + hasPubs={hasPubs} 111 + hasTemplates={hasTemplates} 112 + /> 113 + ), 114 + content: ( 115 + <HomeLeafletList 116 + titles={props.titles} 117 + initialFacts={props.initialFacts} 118 + cardBorderHidden={cardBorderHidden} 119 + searchValue={debouncedSearchValue} 120 + /> 121 + ), 122 + }, 123 + }} 124 + /> 125 + ); 126 + }; 127 + 128 + export function HomeLeafletList(props: { 129 + titles: { [root_entity: string]: string }; 130 + initialFacts: { 131 + [root_entity: string]: Fact<Attribute>[]; 132 + }; 133 + searchValue: string; 134 + cardBorderHidden: boolean; 135 + }) { 136 + let { identity } = useIdentityData(); 137 + let { data: initialFacts } = useSWR( 138 + "home-leaflet-data", 139 + async () => { 140 + if (identity) { 141 + let { result } = await callRPC("getFactsFromHomeLeaflets", { 142 + tokens: identity.permission_token_on_homepage.map( 143 + (ptrh) => ptrh.permission_tokens.root_entity, 144 + ), 145 + }); 146 + let titles = { 147 + ...result.titles, 148 + ...identity.permission_token_on_homepage.reduce( 149 + (acc, tok) => { 150 + let title = 151 + tok.permission_tokens.leaflets_in_publications[0]?.title; 152 + if (title) acc[tok.permission_tokens.root_entity] = title; 153 + return acc; 154 + }, 155 + {} as { [k: string]: string }, 156 + ), 157 + }; 158 + return { ...result, titles }; 159 + } 160 + }, 161 + { fallbackData: { facts: props.initialFacts, titles: props.titles } }, 162 + ); 163 + 164 + let { data: localLeaflets } = useSWR("leaflets", () => getHomeDocs(), { 165 + fallbackData: [], 166 + }); 167 + let leaflets: Leaflet[] = identity 168 + ? identity.permission_token_on_homepage.map((ptoh) => ({ 169 + added_at: ptoh.created_at, 170 + token: ptoh.permission_tokens as PermissionToken, 171 + })) 172 + : localLeaflets 173 + .sort((a, b) => (a.added_at > b.added_at ? -1 : 1)) 174 + .filter((d) => !d.hidden) 175 + .map((ll) => ll); 176 + 177 + return leaflets.length === 0 ? ( 178 + <HomeEmptyState /> 179 + ) : ( 180 + <> 181 + <LeafletList 182 + defaultDisplay="grid" 183 + searchValue={props.searchValue} 184 + leaflets={leaflets} 185 + titles={initialFacts?.titles || {}} 186 + cardBorderHidden={props.cardBorderHidden} 187 + initialFacts={initialFacts?.facts || {}} 188 + showPreview 189 + /> 190 + <div className="spacer h-4 w-full bg-transparent shrink-0 " /> 191 + 192 + {leaflets.filter((l) => !!l.token.leaflets_in_publications).length === 193 + 0 && <PublicationBanner small />} 194 + <DiscoverBanner small /> 195 + <div className="spacer h-8 w-full bg-transparent shrink-0 " /> 196 + </> 197 + ); 198 + } 199 + 200 + export function LeafletList(props: { 201 + leaflets: Leaflet[]; 202 + titles: { [root_entity: string]: string }; 203 + defaultDisplay: Exclude<DashboardState["display"], undefined>; 204 + initialFacts: { 205 + [root_entity: string]: Fact<Attribute>[]; 206 + }; 207 + searchValue: string; 208 + cardBorderHidden: boolean; 209 + showPreview?: boolean; 210 + }) { 211 + let { identity } = useIdentityData(); 212 + let { display } = useDashboardState(); 213 + 214 + display = display || props.defaultDisplay; 215 + 216 + let searchedLeaflets = useSearchedLeaflets( 217 + props.leaflets, 218 + props.titles, 219 + props.searchValue, 220 + ); 221 + 222 + return ( 223 + <div 224 + className={` 225 + leafletList 226 + w-full 227 + ${display === "grid" ? "grid auto-rows-max md:grid-cols-4 sm:grid-cols-3 grid-cols-2 gap-y-4 gap-x-4 sm:gap-x-6 sm:gap-y-5 grow" : "flex flex-col gap-2 pt-2"} `} 228 + > 229 + {props.leaflets.map(({ token: leaflet, added_at }, index) => ( 230 + <ReplicacheProvider 231 + disablePull 232 + initialFactsOnly={!!identity} 233 + key={leaflet.id} 234 + rootEntity={leaflet.root_entity} 235 + token={leaflet} 236 + name={leaflet.root_entity} 237 + initialFacts={props.initialFacts?.[leaflet.root_entity] || []} 238 + > 239 + <StaticLeafletDataContext 240 + value={{ 241 + ...leaflet, 242 + leaflets_in_publications: leaflet.leaflets_in_publications || [], 243 + blocked_by_admin: null, 244 + custom_domain_routes: [], 245 + }} 246 + > 247 + <LeafletListItem 248 + title={props?.titles?.[leaflet.root_entity] || "Untitled"} 249 + token={leaflet} 250 + draft={!!leaflet.leaflets_in_publications?.length} 251 + published={!!leaflet.leaflets_in_publications?.find((l) => l.doc)} 252 + publishedAt={ 253 + leaflet.leaflets_in_publications?.find((l) => l.doc)?.documents 254 + ?.indexed_at 255 + } 256 + leaflet_id={leaflet.root_entity} 257 + loggedIn={!!identity} 258 + display={display} 259 + added_at={added_at} 260 + cardBorderHidden={props.cardBorderHidden} 261 + index={index} 262 + showPreview={props.showPreview} 263 + isHidden={ 264 + !searchedLeaflets.some( 265 + (sl) => sl.token.root_entity === leaflet.root_entity, 266 + ) 267 + } 268 + /> 269 + </StaticLeafletDataContext> 270 + </ReplicacheProvider> 271 + ))} 272 + </div> 273 + ); 274 + } 275 + 276 + function useSearchedLeaflets( 277 + leaflets: Leaflet[], 278 + titles: { [root_entity: string]: string }, 279 + searchValue: string, 280 + ) { 281 + let { sort, filter } = useDashboardState(); 282 + 283 + let sortedLeaflets = leaflets.sort((a, b) => { 284 + if (sort === "alphabetical") { 285 + if (titles[a.token.root_entity] === titles[b.token.root_entity]) { 286 + return a.added_at > b.added_at ? -1 : 1; 287 + } else { 288 + return titles[a.token.root_entity].toLocaleLowerCase() > 289 + titles[b.token.root_entity].toLocaleLowerCase() 290 + ? 1 291 + : -1; 292 + } 293 + } else { 294 + return a.added_at === b.added_at 295 + ? a.token.root_entity > b.token.root_entity 296 + ? -1 297 + : 1 298 + : a.added_at > b.added_at 299 + ? -1 300 + : 1; 301 + } 302 + }); 303 + 304 + let allTemplates = useTemplateState((s) => s.templates); 305 + let filteredLeaflets = sortedLeaflets.filter(({ token: leaflet }) => { 306 + let published = !!leaflet.leaflets_in_publications?.find((l) => l.doc); 307 + let drafts = !!leaflet.leaflets_in_publications?.length && !published; 308 + let docs = !leaflet.leaflets_in_publications?.length; 309 + let templates = !!allTemplates.find((t) => t.id === leaflet.id); 310 + // If no filters are active, show all 311 + if ( 312 + !filter.drafts && 313 + !filter.published && 314 + !filter.docs && 315 + !filter.templates 316 + ) 317 + return true; 318 + 319 + return ( 320 + (filter.drafts && drafts) || 321 + (filter.published && published) || 322 + (filter.docs && docs) || 323 + (filter.templates && templates) 324 + ); 325 + }); 326 + if (searchValue === "") return filteredLeaflets; 327 + let searchedLeaflets = filteredLeaflets.filter(({ token: leaflet }) => { 328 + return titles[leaflet.root_entity] 329 + ?.toLowerCase() 330 + .includes(searchValue.toLowerCase()); 331 + }); 332 + 333 + return searchedLeaflets; 334 + }
+4 -9
app/home/HomeSidebar.tsx app/home/Actions/Actions.tsx
··· 3 3 import { CreateNewLeafletButton } from "./CreateNewButton"; 4 4 import { HelpPopover } from "components/HelpPopover"; 5 5 import { AccountSettings } from "./AccountSettings"; 6 - import { Sidebar } from "components/ActionBar/Sidebar"; 7 6 import { useIdentityData } from "components/IdentityProvider"; 8 7 import { useReplicache } from "src/replicache"; 9 8 import { LoginActionButton } from "components/LoginButton"; 10 - import { MyPublicationList } from "./Publications"; 11 9 12 - export const HomeSidebar = () => { 10 + export const Actions = () => { 13 11 let { identity } = useIdentityData(); 14 12 let { rootEntity } = useReplicache(); 15 - 16 13 return ( 17 - <Sidebar alwaysOpen className="my-6"> 14 + <> 18 15 <CreateNewLeafletButton /> 19 16 {identity ? <AccountSettings /> : <LoginActionButton />} 20 - <HelpPopover noShortcuts /> 17 + {/*<HelpPopover noShortcuts />*/} 21 18 <ThemePopover entityID={rootEntity} home /> 22 - <hr className="border-bg-page" /> 23 - <MyPublicationList /> 24 - </Sidebar> 19 + </> 25 20 ); 26 21 };
-103
app/home/LeafletList.tsx
··· 1 - "use client"; 2 - 3 - import { useEffect, useState } from "react"; 4 - import { getHomeDocs, HomeDoc } from "./storage"; 5 - import useSWR from "swr"; 6 - import { Fact, PermissionToken, ReplicacheProvider } from "src/replicache"; 7 - import { LeafletPreview } from "./LeafletPreview"; 8 - import { useIdentityData } from "components/IdentityProvider"; 9 - import type { Attribute } from "src/replicache/attributes"; 10 - import { callRPC } from "app/api/rpc/client"; 11 - import { StaticLeafletDataContext } from "components/PageSWRDataProvider"; 12 - 13 - export function LeafletList(props: { 14 - initialFacts: { 15 - [root_entity: string]: Fact<Attribute>[]; 16 - }; 17 - }) { 18 - let { data: localLeaflets } = useSWR("leaflets", () => getHomeDocs(), { 19 - fallbackData: [], 20 - }); 21 - let { identity } = useIdentityData(); 22 - let { data: initialFacts } = useSWR( 23 - "home-leaflet-data", 24 - async () => { 25 - if (identity) { 26 - let { result } = await callRPC("getFactsFromHomeLeaflets", { 27 - tokens: identity.permission_token_on_homepage.map( 28 - (ptrh) => ptrh.permission_tokens.root_entity, 29 - ), 30 - }); 31 - return result; 32 - } 33 - }, 34 - { fallbackData: props.initialFacts }, 35 - ); 36 - let leaflets: Array< 37 - PermissionToken & { 38 - leaflets_in_publications?: Array<{ 39 - doc: string; 40 - description: string; 41 - publication: string; 42 - leaflet: string; 43 - title: string; 44 - publications: null; 45 - documents: null; 46 - }>; 47 - } 48 - > = identity 49 - ? identity.permission_token_on_homepage 50 - .sort((a, b) => 51 - a.created_at === b.created_at 52 - ? a.permission_tokens.root_entity > b.permission_tokens.root_entity 53 - ? -1 54 - : 1 55 - : a.created_at > b.created_at 56 - ? -1 57 - : 1, 58 - ) 59 - .map((ptoh) => ptoh.permission_tokens) 60 - : localLeaflets 61 - .sort((a, b) => (a.added_at > b.added_at ? -1 : 1)) 62 - .filter((d) => !d.hidden) 63 - .map((ll) => ll.token); 64 - 65 - return ( 66 - <div className="homeLeafletGrid grow w-full h-full"> 67 - <div className="grid auto-rows-max md:grid-cols-4 sm:grid-cols-3 grid-cols-2 gap-y-8 gap-x-4 sm:gap-x-6 sm:gap-y-8 grow pt-3 pb-28 px-2 sm:pt-6 sm:pb-12 sm:pl-6 sm:pr-1"> 68 - {leaflets.map((leaflet, index) => ( 69 - <ReplicacheProvider 70 - disablePull 71 - initialFactsOnly={!!identity} 72 - key={leaflet.id} 73 - rootEntity={leaflet.root_entity} 74 - token={leaflet} 75 - name={leaflet.root_entity} 76 - initialFacts={initialFacts?.[leaflet.root_entity] || []} 77 - > 78 - <StaticLeafletDataContext 79 - value={{ 80 - ...leaflet, 81 - leaflets_in_publications: 82 - leaflet.leaflets_in_publications || [], 83 - blocked_by_admin: null, 84 - custom_domain_routes: [], 85 - }} 86 - > 87 - <LeafletPreview 88 - index={index} 89 - token={leaflet} 90 - draft={!!leaflet.leaflets_in_publications?.length} 91 - published={ 92 - !!leaflet.leaflets_in_publications?.find((l) => l.doc) 93 - } 94 - leaflet_id={leaflet.root_entity} 95 - loggedIn={!!identity} 96 - /> 97 - </StaticLeafletDataContext> 98 - </ReplicacheProvider> 99 - ))} 100 - </div> 101 - </div> 102 - ); 103 - }
+68
app/home/LeafletList/LeafletContent.tsx
··· 1 + "use client"; 2 + import { BlockPreview } from "components/Blocks/PageLinkBlock"; 3 + import { useEffect, useRef, useState } from "react"; 4 + import { useBlocks } from "src/hooks/queries/useBlocks"; 5 + import { useEntity } from "src/replicache"; 6 + import { CanvasContent } from "components/Canvas"; 7 + import styles from "./LeafletPreview.module.css"; 8 + import { PublicationMetadataPreview } from "components/Pages/PublicationMetadata"; 9 + 10 + export const LeafletContent = (props: { 11 + entityID: string; 12 + isOnScreen: boolean; 13 + }) => { 14 + let type = useEntity(props.entityID, "page/type")?.data.value || "doc"; 15 + let blocks = useBlocks(props.entityID); 16 + let previewRef = useRef<HTMLDivElement | null>(null); 17 + 18 + if (type === "canvas") 19 + return ( 20 + <div 21 + className={`pageLinkBlockPreview shrink-0 h-full overflow-clip relative bg-bg-page shadow-sm rounded-md`} 22 + > 23 + <div 24 + className={`absolute top-0 left-0 origin-top-left pointer-events-none ${styles.scaleLeafletCanvasPreview}`} 25 + style={{ 26 + width: `1272px`, 27 + height: "calc(1272px * 2)", 28 + }} 29 + > 30 + {props.isOnScreen && ( 31 + <CanvasContent entityID={props.entityID} preview /> 32 + )} 33 + </div> 34 + </div> 35 + ); 36 + 37 + return ( 38 + <div 39 + ref={previewRef} 40 + className={`pageLinkBlockPreview h-full overflow-clip flex flex-col gap-0.5 no-underline relative`} 41 + > 42 + <div 43 + className={`absolute top-0 left-0 w-full h-full origin-top-left pointer-events-none ${styles.scaleLeafletDocPreview}`} 44 + style={{ 45 + width: `var(--page-width-units)`, 46 + }} 47 + > 48 + <PublicationMetadataPreview /> 49 + 50 + {props.isOnScreen && 51 + blocks.slice(0, 10).map((b, index, arr) => { 52 + return ( 53 + <BlockPreview 54 + pageType="doc" 55 + entityID={b.value} 56 + previousBlock={arr[index - 1] || null} 57 + nextBlock={arr[index + 1] || null} 58 + nextPosition={""} 59 + previewRef={previewRef} 60 + {...b} 61 + key={b.factID} 62 + /> 63 + ); 64 + })} 65 + </div> 66 + </div> 67 + ); 68 + };
+88
app/home/LeafletList/LeafletInfo.tsx
··· 1 + "use client"; 2 + import { PermissionToken } from "src/replicache"; 3 + import { LeafletOptions } from "./LeafletOptions"; 4 + import Link from "next/link"; 5 + import { useState } from "react"; 6 + import { theme } from "tailwind.config"; 7 + import { TemplateSmall } from "components/Icons/TemplateSmall"; 8 + import { timeAgo } from "src/utils/timeAgo"; 9 + 10 + export const LeafletInfo = (props: { 11 + title?: string; 12 + draft?: boolean; 13 + published?: boolean; 14 + token: PermissionToken; 15 + leaflet_id: string; 16 + loggedIn: boolean; 17 + isTemplate: boolean; 18 + className?: string; 19 + display: "grid" | "list"; 20 + added_at: string; 21 + publishedAt?: string; 22 + }) => { 23 + let [prefetch, setPrefetch] = useState(false); 24 + let prettyCreatedAt = props.added_at ? timeAgo(props.added_at) : ""; 25 + 26 + let prettyPublishedAt = props.publishedAt ? timeAgo(props.publishedAt) : ""; 27 + 28 + return ( 29 + <div 30 + className={`leafletInfo w-full min-w-0 flex flex-col ${props.className}`} 31 + > 32 + <div className="flex justify-between items-center shrink-0 max-w-full gap-2 leading-tight overflow-hidden"> 33 + <Link 34 + onMouseEnter={() => setPrefetch(true)} 35 + onPointerDown={() => setPrefetch(true)} 36 + prefetch={prefetch} 37 + href={`/${props.token.id}`} 38 + className="no-underline sm:hover:no-underline text-primary grow min-w-0" 39 + > 40 + <h3 className="sm:text-lg text-base truncate w-full min-w-0"> 41 + {props.title} 42 + </h3> 43 + </Link> 44 + <div className="flex gap-1 shrink-0"> 45 + {props.isTemplate && props.display === "list" ? ( 46 + <TemplateSmall 47 + fill={theme.colors["bg-page"]} 48 + className="text-tertiary" 49 + /> 50 + ) : null} 51 + <LeafletOptions 52 + leaflet={props.token} 53 + isTemplate={props.isTemplate} 54 + loggedIn={props.loggedIn} 55 + added_at={props.added_at} 56 + /> 57 + </div> 58 + </div> 59 + <Link 60 + onMouseEnter={() => setPrefetch(true)} 61 + onPointerDown={() => setPrefetch(true)} 62 + prefetch={prefetch} 63 + href={`/${props.token.id}`} 64 + className="no-underline sm:hover:no-underline text-primary w-full" 65 + > 66 + {props.draft || props.published ? ( 67 + <div 68 + className={`text-xs ${props.published ? "font-bold text-tertiary" : "text-tertiary"}`} 69 + > 70 + {props.published 71 + ? `Published ${prettyPublishedAt}` 72 + : `Draft ${prettyCreatedAt}`} 73 + </div> 74 + ) : ( 75 + <div className="text-xs text-tertiary">{prettyCreatedAt}</div> 76 + )} 77 + </Link> 78 + {props.isTemplate && props.display === "grid" ? ( 79 + <div className="absolute -top-2 right-1"> 80 + <TemplateSmall 81 + className="text-tertiary" 82 + fill={theme.colors["bg-page"]} 83 + /> 84 + </div> 85 + ) : null} 86 + </div> 87 + ); 88 + };
+102
app/home/LeafletList/LeafletListItem.tsx
··· 1 + "use client"; 2 + import { PermissionToken } from "src/replicache"; 3 + import { useTemplateState } from "../Actions/CreateNewButton"; 4 + import { LeafletListPreview, LeafletGridPreview } from "./LeafletPreview"; 5 + import { LeafletInfo } from "./LeafletInfo"; 6 + import { useState, useRef, useEffect } from "react"; 7 + 8 + export const LeafletListItem = (props: { 9 + token: PermissionToken; 10 + leaflet_id: string; 11 + loggedIn: boolean; 12 + display: "list" | "grid"; 13 + cardBorderHidden: boolean; 14 + added_at: string; 15 + title: string; 16 + draft?: boolean; 17 + published?: boolean; 18 + publishedAt?: string; 19 + index: number; 20 + isHidden: boolean; 21 + showPreview?: boolean; 22 + }) => { 23 + let isTemplate = useTemplateState( 24 + (s) => !!s.templates.find((t) => t.id === props.token.id), 25 + ); 26 + 27 + let [isOnScreen, setIsOnScreen] = useState(props.index < 16 ? true : false); 28 + let previewRef = useRef<HTMLDivElement | null>(null); 29 + 30 + useEffect(() => { 31 + if (!previewRef.current) return; 32 + let observer = new IntersectionObserver( 33 + (entries) => { 34 + entries.forEach((entry) => { 35 + if (entry.isIntersecting) { 36 + setIsOnScreen(true); 37 + } else { 38 + setIsOnScreen(false); 39 + } 40 + }); 41 + }, 42 + { threshold: 0.1, root: null }, 43 + ); 44 + observer.observe(previewRef.current); 45 + return () => observer.disconnect(); 46 + }, [previewRef]); 47 + 48 + if (props.display === "list") 49 + return ( 50 + <> 51 + <div 52 + ref={previewRef} 53 + className={`gap-3 w-full ${props.cardBorderHidden ? "" : "px-2 py-1 block-border hover:outline-border"}`} 54 + style={{ 55 + backgroundColor: props.cardBorderHidden 56 + ? "transparent" 57 + : "rgba(var(--bg-page), var(--bg-page-alpha))", 58 + 59 + display: props.isHidden ? "none" : "flex", 60 + }} 61 + > 62 + {props.showPreview && ( 63 + <LeafletListPreview isVisible={isOnScreen} {...props} /> 64 + )} 65 + <LeafletInfo isTemplate={isTemplate} {...props} /> 66 + </div> 67 + {props.cardBorderHidden && ( 68 + <hr 69 + className="last:hidden border-border-light" 70 + style={{ 71 + display: props.isHidden ? "none" : "block", 72 + }} 73 + /> 74 + )} 75 + </> 76 + ); 77 + return ( 78 + <div 79 + ref={previewRef} 80 + className={`leafletGridListItem relative 81 + flex flex-col gap-1 p-1 h-52 82 + block-border border-border! hover:outline-border 83 + `} 84 + style={{ 85 + backgroundColor: props.cardBorderHidden 86 + ? "transparent" 87 + : "rgba(var(--bg-page), var(--bg-page-alpha))", 88 + 89 + display: props.isHidden ? "none" : "flex", 90 + }} 91 + > 92 + <div className="grow"> 93 + <LeafletGridPreview {...props} isVisible={isOnScreen} /> 94 + </div> 95 + <LeafletInfo 96 + isTemplate={isTemplate} 97 + className="px-1 pb-0.5 shrink-0" 98 + {...props} 99 + /> 100 + </div> 101 + ); 102 + };
+187
app/home/LeafletList/LeafletPreview.tsx
··· 1 + "use client"; 2 + import { 3 + ThemeBackgroundProvider, 4 + ThemeProvider, 5 + } from "components/ThemeManager/ThemeProvider"; 6 + import { 7 + PermissionToken, 8 + useEntity, 9 + useReferenceToEntity, 10 + } from "src/replicache"; 11 + import { useTemplateState } from "../Actions/CreateNewButton"; 12 + import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 13 + import { LeafletContent } from "./LeafletContent"; 14 + import { Tooltip } from "components/Tooltip"; 15 + import { useState } from "react"; 16 + import Link from "next/link"; 17 + import { SpeedyLink } from "components/SpeedyLink"; 18 + 19 + export const LeafletListPreview = (props: { 20 + draft?: boolean; 21 + published?: boolean; 22 + isVisible: boolean; 23 + token: PermissionToken; 24 + leaflet_id: string; 25 + loggedIn: boolean; 26 + }) => { 27 + let root = 28 + useReferenceToEntity("root/page", props.leaflet_id)[0]?.entity || 29 + props.leaflet_id; 30 + let firstPage = useEntity(root, "root/page")[0]; 31 + let page = firstPage?.data.value || root; 32 + 33 + let cardBorderHidden = useCardBorderHidden(root); 34 + let rootBackgroundImage = useEntity(root, "theme/card-background-image"); 35 + let rootBackgroundRepeat = useEntity( 36 + root, 37 + "theme/card-background-image-repeat", 38 + ); 39 + let rootBackgroundOpacity = useEntity( 40 + root, 41 + "theme/card-background-image-opacity", 42 + ); 43 + 44 + return ( 45 + <Tooltip 46 + open={true} 47 + delayDuration={0} 48 + side="right" 49 + trigger={ 50 + <div className="w-12 h-full py-1"> 51 + <div className="rounded-md h-full overflow-hidden"> 52 + <ThemeProvider local entityID={root} className=""> 53 + <ThemeBackgroundProvider entityID={root}> 54 + <div className="w-full h-full rounded-md p-1 border border-border"> 55 + <div 56 + className={`w-full h-full rounded-[2px]`} 57 + style={ 58 + cardBorderHidden 59 + ? { 60 + borderWidth: "2px", 61 + borderColor: "rgb(var(--primary))", 62 + } 63 + : { 64 + backgroundColor: 65 + "rgba(var(--bg-page), var(--bg-page-alpha))", 66 + } 67 + } 68 + /> 69 + </div> 70 + </ThemeBackgroundProvider> 71 + </ThemeProvider> 72 + </div> 73 + </div> 74 + } 75 + className="p-1!" 76 + > 77 + <ThemeProvider local entityID={root} className="rounded-sm"> 78 + <ThemeBackgroundProvider entityID={root}> 79 + <div className="leafletPreview grow shrink-0 h-44 w-64 px-2 pt-2 sm:px-3 sm:pt-3 flex items-end pointer-events-none rounded-[2px] "> 80 + <div 81 + className={`leafletContentWrapper h-full sm:w-48 w-40 mx-auto overflow-clip ${!cardBorderHidden && "border border-border-light border-b-0 rounded-t-md"}`} 82 + style={ 83 + cardBorderHidden 84 + ? {} 85 + : { 86 + backgroundImage: rootBackgroundImage 87 + ? `url(${rootBackgroundImage.data.src}), url(${rootBackgroundImage.data.fallback})` 88 + : undefined, 89 + backgroundRepeat: rootBackgroundRepeat 90 + ? "repeat" 91 + : "no-repeat", 92 + backgroundPosition: "center", 93 + backgroundSize: !rootBackgroundRepeat 94 + ? "cover" 95 + : rootBackgroundRepeat?.data.value / 3, 96 + opacity: 97 + rootBackgroundImage?.data.src && rootBackgroundOpacity 98 + ? rootBackgroundOpacity.data.value 99 + : 1, 100 + backgroundColor: 101 + "rgba(var(--bg-page), var(--bg-page-alpha))", 102 + } 103 + } 104 + > 105 + <LeafletContent entityID={page} isOnScreen={props.isVisible} /> 106 + </div> 107 + </div> 108 + </ThemeBackgroundProvider> 109 + </ThemeProvider> 110 + </Tooltip> 111 + ); 112 + }; 113 + 114 + export const LeafletGridPreview = (props: { 115 + draft?: boolean; 116 + published?: boolean; 117 + token: PermissionToken; 118 + leaflet_id: string; 119 + loggedIn: boolean; 120 + isVisible: boolean; 121 + }) => { 122 + let root = 123 + useReferenceToEntity("root/page", props.leaflet_id)[0]?.entity || 124 + props.leaflet_id; 125 + let firstPage = useEntity(root, "root/page")[0]; 126 + let page = firstPage?.data.value || root; 127 + 128 + let cardBorderHidden = useCardBorderHidden(root); 129 + let rootBackgroundImage = useEntity(root, "theme/card-background-image"); 130 + let rootBackgroundRepeat = useEntity( 131 + root, 132 + "theme/card-background-image-repeat", 133 + ); 134 + let rootBackgroundOpacity = useEntity( 135 + root, 136 + "theme/card-background-image-opacity", 137 + ); 138 + return ( 139 + <ThemeProvider local entityID={root} className="w-full!"> 140 + <div className="border border-border-light rounded-md w-full h-full overflow-hidden relative"> 141 + <div className="relative w-full h-full"> 142 + <ThemeBackgroundProvider entityID={root}> 143 + <div className="leafletPreview relative grow shrink-0 h-full w-full px-2 pt-2 sm:px-3 sm:pt-3 flex items-end pointer-events-none"> 144 + <div 145 + className={`leafletContentWrapper h-full sm:w-48 w-40 mx-auto overflow-clip ${!cardBorderHidden && "border border-border-light border-b-0 rounded-t-md"}`} 146 + style={ 147 + cardBorderHidden 148 + ? {} 149 + : { 150 + backgroundImage: rootBackgroundImage 151 + ? `url(${rootBackgroundImage.data.src}), url(${rootBackgroundImage.data.fallback})` 152 + : undefined, 153 + backgroundRepeat: rootBackgroundRepeat 154 + ? "repeat" 155 + : "no-repeat", 156 + backgroundPosition: "center", 157 + backgroundSize: !rootBackgroundRepeat 158 + ? "cover" 159 + : rootBackgroundRepeat?.data.value / 3, 160 + opacity: 161 + rootBackgroundImage?.data.src && rootBackgroundOpacity 162 + ? rootBackgroundOpacity.data.value 163 + : 1, 164 + backgroundColor: 165 + "rgba(var(--bg-page), var(--bg-page-alpha))", 166 + } 167 + } 168 + > 169 + <LeafletContent entityID={page} isOnScreen={props.isVisible} /> 170 + </div> 171 + </div> 172 + </ThemeBackgroundProvider> 173 + </div> 174 + <LeafletPreviewLink id={props.token.id} /> 175 + </div> 176 + </ThemeProvider> 177 + ); 178 + }; 179 + 180 + const LeafletPreviewLink = (props: { id: string }) => { 181 + return ( 182 + <SpeedyLink 183 + href={`/${props.id}`} 184 + className={`hello no-underline sm:hover:no-underline text-primary absolute inset-0 w-full h-full bg-bg-test`} 185 + /> 186 + ); 187 + };
+72 -8
app/home/LeafletOptions.tsx app/home/LeafletList/LeafletOptions.tsx
··· 1 1 "use client"; 2 2 3 3 import { Menu, MenuItem } from "components/Layout"; 4 - import type { PermissionToken } from "src/replicache"; 5 - import { hideDoc } from "./storage"; 4 + import { useReplicache, type PermissionToken } from "src/replicache"; 5 + import { hideDoc } from "../storage"; 6 6 import { useState } from "react"; 7 7 import { ButtonPrimary } from "components/Buttons"; 8 - import { useTemplateState } from "./CreateNewButton"; 9 - import { Item } from "@radix-ui/react-dropdown-menu"; 10 - import { useSmoker } from "components/Toast"; 8 + import { useTemplateState } from "../Actions/CreateNewButton"; 9 + import { useSmoker, useToaster } from "components/Toast"; 11 10 import { removeLeafletFromHome } from "actions/removeLeafletFromHome"; 12 11 import { useIdentityData } from "components/IdentityProvider"; 13 12 import { HideSmall } from "components/Icons/HideSmall"; 14 13 import { MoreOptionsTiny } from "components/Icons/MoreOptionsTiny"; 15 14 import { TemplateRemoveSmall } from "components/Icons/TemplateRemoveSmall"; 16 15 import { TemplateSmall } from "components/Icons/TemplateSmall"; 16 + import { MoreOptionsVerticalTiny } from "components/Icons/MoreOptionsVerticalTiny"; 17 + import { addLeafletToHome } from "actions/addLeafletToHome"; 17 18 18 19 export const LeafletOptions = (props: { 19 20 leaflet: PermissionToken; 20 21 isTemplate: boolean; 21 22 loggedIn: boolean; 23 + added_at: string; 22 24 }) => { 23 25 let { mutate: mutateIdentity } = useIdentityData(); 24 26 let [state, setState] = useState<"normal" | "template">("normal"); 25 27 let [open, setOpen] = useState(false); 26 28 let smoker = useSmoker(); 29 + let toaster = useToaster(); 27 30 return ( 28 31 <> 29 32 <Menu ··· 34 37 setState("normal"); 35 38 }} 36 39 trigger={ 37 - <div className="bg-accent-1 text-accent-2 px-2 py-1 border border-accent-2 rounded-md"> 38 - <MoreOptionsTiny /> 40 + <div 41 + className="text-secondary shrink-0" 42 + onClick={(e) => { 43 + e.preventDefault; 44 + e.stopPropagation; 45 + }} 46 + > 47 + <MoreOptionsVerticalTiny /> 39 48 </div> 40 49 } 41 50 > ··· 93 102 } else { 94 103 hideDoc(props.leaflet); 95 104 } 105 + toaster({ 106 + content: ( 107 + <div className="font-bold"> 108 + Doc removed!{" "} 109 + <UndoRemoveFromHomeButton 110 + leaflet={props.leaflet} 111 + added_at={props.added_at} 112 + /> 113 + </div> 114 + ), 115 + type: "success", 116 + }); 96 117 }} 97 118 > 98 119 <HideSmall /> ··· 110 131 ); 111 132 }; 112 133 134 + const UndoRemoveFromHomeButton = (props: { 135 + leaflet: PermissionToken; 136 + added_at: string | undefined; 137 + }) => { 138 + let toaster = useToaster(); 139 + let { mutate } = useIdentityData(); 140 + return ( 141 + <button 142 + onClick={async (e) => { 143 + await mutate( 144 + (identity) => { 145 + if (!identity) return; 146 + return { 147 + ...identity, 148 + permission_token_on_homepage: [ 149 + ...identity.permission_token_on_homepage, 150 + { 151 + created_at: props.added_at || new Date().toISOString(), 152 + permission_tokens: { 153 + ...props.leaflet, 154 + leaflets_in_publications: [], 155 + }, 156 + }, 157 + ], 158 + }; 159 + }, 160 + { revalidate: false }, 161 + ); 162 + await addLeafletToHome(props.leaflet.id); 163 + await mutate(); 164 + 165 + toaster({ 166 + content: <div className="font-bold">Recovered Doc!</div>, 167 + type: "success", 168 + }); 169 + }} 170 + className="underline" 171 + > 172 + Undo? 173 + </button> 174 + ); 175 + }; 176 + 113 177 const AddTemplateForm = (props: { 114 178 leaflet: PermissionToken; 115 179 close: () => void; ··· 124 188 value={name} 125 189 onChange={(e) => setName(e.target.value)} 126 190 type="text" 127 - className=" text-primary font-normal border border-border rounded-md outline-none px-2 py-1 w-64" 191 + className=" text-primary font-normal border border-border rounded-md outline-hidden px-2 py-1 w-64" 128 192 /> 129 193 </label> 130 194
app/home/LeafletPreview.module.css app/home/LeafletList/LeafletPreview.module.css
-255
app/home/LeafletPreview.tsx
··· 1 - "use client"; 2 - import { BlockPreview, PagePreview } from "components/Blocks/PageLinkBlock"; 3 - import { 4 - ThemeBackgroundProvider, 5 - ThemeProvider, 6 - } from "components/ThemeManager/ThemeProvider"; 7 - import { useEffect, useRef, useState } from "react"; 8 - import { useBlocks } from "src/hooks/queries/useBlocks"; 9 - import { 10 - PermissionToken, 11 - useEntity, 12 - useReferenceToEntity, 13 - useReplicache, 14 - } from "src/replicache"; 15 - import { deleteLeaflet } from "actions/deleteLeaflet"; 16 - import { removeDocFromHome } from "./storage"; 17 - import { mutate } from "swr"; 18 - import { ButtonPrimary } from "components/Buttons"; 19 - import { LeafletOptions } from "./LeafletOptions"; 20 - import { CanvasContent } from "components/Canvas"; 21 - import { theme } from "tailwind.config"; 22 - import { useTemplateState } from "./CreateNewButton"; 23 - import styles from "./LeafletPreview.module.css"; 24 - import { useRouter } from "next/navigation"; 25 - import Link from "next/link"; 26 - import { TemplateSmall } from "components/Icons/TemplateSmall"; 27 - import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 28 - import { 29 - PublicationMetadata, 30 - PublicationMetadataPreview, 31 - } from "components/Pages/PublicationMetadata"; 32 - import { SpeedyLink } from "components/SpeedyLink"; 33 - 34 - export const LeafletPreview = (props: { 35 - draft?: boolean; 36 - published?: boolean; 37 - index: number; 38 - token: PermissionToken; 39 - leaflet_id: string; 40 - loggedIn: boolean; 41 - }) => { 42 - let [state, setState] = useState<"normal" | "deleting">("normal"); 43 - let isTemplate = useTemplateState( 44 - (s) => !!s.templates.find((t) => t.id === props.token.id), 45 - ); 46 - let root = 47 - useReferenceToEntity("root/page", props.leaflet_id)[0]?.entity || 48 - props.leaflet_id; 49 - let firstPage = useEntity(root, "root/page")[0]; 50 - let page = firstPage?.data.value || root; 51 - 52 - let cardBorderHidden = useCardBorderHidden(root); 53 - let rootBackgroundImage = useEntity(root, "theme/card-background-image"); 54 - let rootBackgroundRepeat = useEntity( 55 - root, 56 - "theme/card-background-image-repeat", 57 - ); 58 - let rootBackgroundOpacity = useEntity( 59 - root, 60 - "theme/card-background-image-opacity", 61 - ); 62 - 63 - return ( 64 - <div className="relative max-h-40 h-40"> 65 - <ThemeProvider local entityID={root} className="!w-full"> 66 - <div className="rounded-lg sm:hover:shadow-sm overflow-clip border border-border outline outline-2 outline-transparent outline-offset-1 sm:hover:outline-border bg-bg-leaflet grow w-full h-full"> 67 - {state === "normal" ? ( 68 - <div className="relative w-full h-full"> 69 - <ThemeBackgroundProvider entityID={root}> 70 - <div className="leafletPreview grow shrink-0 h-full w-full px-2 pt-2 sm:px-3 sm:pt-3 flex items-end pointer-events-none"> 71 - <div 72 - className={`leafletContentWrapper h-full sm:w-48 w-40 mx-auto overflow-clip ${!cardBorderHidden && "border border-border-light border-b-0 rounded-t-md"}`} 73 - style={ 74 - cardBorderHidden 75 - ? {} 76 - : { 77 - backgroundImage: rootBackgroundImage 78 - ? `url(${rootBackgroundImage.data.src}), url(${rootBackgroundImage.data.fallback})` 79 - : undefined, 80 - backgroundRepeat: rootBackgroundRepeat 81 - ? "repeat" 82 - : "no-repeat", 83 - backgroundPosition: "center", 84 - backgroundSize: !rootBackgroundRepeat 85 - ? "cover" 86 - : rootBackgroundRepeat?.data.value / 3, 87 - opacity: 88 - rootBackgroundImage?.data.src && 89 - rootBackgroundOpacity 90 - ? rootBackgroundOpacity.data.value 91 - : 1, 92 - backgroundColor: 93 - "rgba(var(--bg-page), var(--bg-page-alpha))", 94 - } 95 - } 96 - > 97 - <LeafletContent entityID={page} index={props.index} /> 98 - </div> 99 - </div> 100 - </ThemeBackgroundProvider> 101 - <SpeedyLink 102 - href={`/${props.token.id}`} 103 - className={`no-underline sm:hover:no-underline text-primary absolute inset-0 w-full h-full`} 104 - /> 105 - </div> 106 - ) : ( 107 - <LeafletAreYouSure token={props.token} setState={setState} /> 108 - )} 109 - </div> 110 - <div className="flex justify-between pt-1 shrink-0 w-full gap-2"> 111 - {props.draft || props.published ? ( 112 - <div 113 - className={`text-xs container !border-none !w-fit px-0.5 italic ${props.published ? "font-bold text-tertiary" : "text-tertiary"}`} 114 - > 115 - {props.published ? "Published!" : "Draft"} 116 - </div> 117 - ) : ( 118 - <div /> 119 - )} 120 - <LeafletOptions 121 - leaflet={props.token} 122 - isTemplate={isTemplate} 123 - loggedIn={props.loggedIn} 124 - /> 125 - </div> 126 - <LeafletTemplateIndicator isTemplate={isTemplate} /> 127 - </ThemeProvider> 128 - </div> 129 - ); 130 - }; 131 - 132 - const LeafletContent = (props: { entityID: string; index: number }) => { 133 - let type = useEntity(props.entityID, "page/type")?.data.value || "doc"; 134 - let blocks = useBlocks(props.entityID); 135 - let previewRef = useRef<HTMLDivElement | null>(null); 136 - let [isVisible, setIsVisible] = useState(props.index > 16 ? false : true); 137 - useEffect(() => { 138 - if (!previewRef.current) return; 139 - let observer = new IntersectionObserver( 140 - (entries) => { 141 - entries.forEach((entry) => { 142 - if (entry.isIntersecting) { 143 - setIsVisible(true); 144 - } else { 145 - setIsVisible(false); 146 - } 147 - }); 148 - }, 149 - { threshold: 0.1, root: null }, 150 - ); 151 - observer.observe(previewRef.current); 152 - return () => observer.disconnect(); 153 - }, [previewRef]); 154 - 155 - if (type === "canvas") 156 - return ( 157 - <div 158 - className={`pageLinkBlockPreview shrink-0 h-full overflow-clip relative bg-bg-page shadow-sm rounded-md`} 159 - > 160 - <div 161 - className={`absolute top-0 left-0 origin-top-left pointer-events-none ${styles.scaleLeafletCanvasPreview}`} 162 - style={{ 163 - width: `1272px`, 164 - height: "calc(1272px * 2)", 165 - }} 166 - > 167 - {isVisible && <CanvasContent entityID={props.entityID} preview />} 168 - </div> 169 - </div> 170 - ); 171 - 172 - return ( 173 - <div 174 - ref={previewRef} 175 - className={`pageLinkBlockPreview h-full overflow-clip flex flex-col gap-0.5 no-underline relative`} 176 - > 177 - <div 178 - className={`absolute top-0 left-0 w-full h-full origin-top-left pointer-events-none ${styles.scaleLeafletDocPreview}`} 179 - style={{ 180 - width: `var(--page-width-units)`, 181 - }} 182 - > 183 - <PublicationMetadataPreview /> 184 - 185 - {isVisible && 186 - blocks.slice(0, 10).map((b, index, arr) => { 187 - return ( 188 - <BlockPreview 189 - pageType="doc" 190 - entityID={b.value} 191 - previousBlock={arr[index - 1] || null} 192 - nextBlock={arr[index + 1] || null} 193 - nextPosition={""} 194 - previewRef={previewRef} 195 - {...b} 196 - key={b.factID} 197 - /> 198 - ); 199 - })} 200 - </div> 201 - </div> 202 - ); 203 - }; 204 - 205 - const LeafletAreYouSure = (props: { 206 - token: PermissionToken; 207 - setState: (s: "normal" | "deleting") => void; 208 - }) => { 209 - return ( 210 - <div 211 - className="leafletContentWrapper w-full h-full px-1 pt-1 sm:px-[6px] sm:pt-2 flex flex-col gap-2 justify-center items-center " 212 - style={{ 213 - backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 214 - }} 215 - > 216 - <div className="font-bold text-center"> 217 - Permanently delete this Leaflet? 218 - </div> 219 - <div className="flex gap-2 font-bold "> 220 - <ButtonPrimary 221 - compact 222 - onMouseDown={(e) => { 223 - e.stopPropagation(); 224 - e.preventDefault(); 225 - deleteLeaflet(props.token); 226 - removeDocFromHome(props.token); 227 - mutate("leaflets"); 228 - }} 229 - > 230 - Delete 231 - </ButtonPrimary> 232 - <button 233 - className="text-accent-1" 234 - onMouseDown={(e) => { 235 - e.stopPropagation(); 236 - e.preventDefault(); 237 - props.setState("normal"); 238 - }} 239 - > 240 - Nevermind 241 - </button> 242 - </div> 243 - </div> 244 - ); 245 - }; 246 - 247 - const LeafletTemplateIndicator = (props: { isTemplate: boolean }) => { 248 - if (!props.isTemplate) return; 249 - 250 - return ( 251 - <div className="absolute -top-3 right-1"> 252 - <TemplateSmall fill={theme.colors["bg-page"]} /> 253 - </div> 254 - ); 255 - };
+89 -109
app/home/Publications.tsx components/ActionBar/Publications.tsx
··· 1 1 "use client"; 2 - import { ButtonSecondary } from "components/Buttons"; 3 2 import Link from "next/link"; 4 3 5 4 import { useIdentityData } from "components/IdentityProvider"; 6 5 import { theme } from "tailwind.config"; 7 - import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 8 - import { AddTiny } from "components/Icons/AddTiny"; 9 - import { 10 - getBasePublicationURL, 11 - getPublicationURL, 12 - } from "app/lish/createPub/getPublicationURL"; 6 + import { getBasePublicationURL } from "app/lish/createPub/getPublicationURL"; 13 7 import { Json } from "supabase/database.types"; 14 8 import { PubLeafletPublication } from "lexicons/api"; 15 9 import { AtUri } from "@atproto/syntax"; 16 - import { blobRefToSrc } from "src/utils/blobRefToSrc"; 17 - import { PublicationThemeProvider } from "components/ThemeManager/PublicationThemeProvider"; 10 + import { ActionButton } from "./ActionButton"; 18 11 import { SpeedyLink } from "components/SpeedyLink"; 12 + import { PublishSmall } from "components/Icons/PublishSmall"; 19 13 20 - export const MyPublicationList = () => { 14 + export const PublicationButtons = (props: { 15 + currentPubUri: string | undefined; 16 + }) => { 21 17 let { identity } = useIdentityData(); 18 + 19 + // don't show pub list button if not logged in or no pub list 20 + // we show a "start a pub" banner instead 22 21 if (!identity || !identity.atp_did) return <PubListEmpty />; 23 22 return ( 24 - <div className="pubListWrapper w-full sm:w-[200px] flex flex-col sm:gap-1 container py-2 sm:p-0 sm:bg-transparent sm:border-0"> 25 - <div className="flex justify-between items-center font-bold text-tertiary text-sm px-2 "> 26 - Publications 27 - <SpeedyLink 28 - href={"/lish/createPub"} 29 - className="pubListCreateNew text-accent-contrast font-bold hover:text-accent-contrast" 30 - > 31 - <span className="sr-only">Create new publication</span> 32 - <AddTiny aria-hidden /> 33 - </SpeedyLink> 34 - </div> 35 - <PublicationList publications={identity.publications} /> 23 + <div className="pubListWrapper w-full flex flex-col gap-1 sm:bg-transparent sm:border-0"> 24 + {identity.publications?.map((d) => { 25 + // console.log("thisURI : " + d.uri); 26 + // console.log("currentURI : " + props.currentPubUri); 27 + console.log(d.uri === props.currentPubUri); 28 + 29 + return ( 30 + <PublicationOption 31 + {...d} 32 + key={d.uri} 33 + record={d.record} 34 + asActionButton 35 + current={d.uri === props.currentPubUri} 36 + /> 37 + ); 38 + })} 39 + <Link 40 + href={"./lish/createPub"} 41 + className="pubListCreateNew text-accent-contrast text-sm place-self-end hover:text-accent-contrast" 42 + > 43 + New 44 + </Link> 36 45 </div> 37 46 ); 38 47 }; 39 48 40 - const PublicationList = (props: { 41 - publications: { 42 - identity_did: string; 43 - indexed_at: string; 44 - name: string; 45 - record: Json; 46 - uri: string; 47 - }[]; 49 + export const PublicationOption = (props: { 50 + uri: string; 51 + name: string; 52 + record: Json; 53 + asActionButton?: boolean; 54 + current?: boolean; 48 55 }) => { 49 - return ( 50 - <div className="pubList w-full flex flex-row sm:flex-col gap-0 overflow-auto items-stretch"> 51 - {props.publications?.map((d) => ( 52 - <Publication {...d} key={d.uri} record={d.record} /> 53 - ))} 54 - </div> 55 - ); 56 - }; 57 - 58 - function Publication(props: { uri: string; name: string; record: Json }) { 59 56 let record = props.record as PubLeafletPublication.Record | null; 60 - let pub_creator = new AtUri(props.uri).host; 61 - let backgroundImage = record?.theme?.backgroundImage?.image?.ref 62 - ? blobRefToSrc(record?.theme?.backgroundImage?.image?.ref, pub_creator) 63 - : null; 57 + if (!record) return; 64 58 65 - let backgroundImageRepeat = record?.theme?.backgroundImage?.repeat; 66 - let backgroundImageSize = record?.theme?.backgroundImage?.width || 500; 67 - let showPageBackground = !!record?.theme?.showPageBackground; 68 59 return ( 69 - <PublicationThemeProvider record={record} local pub_creator={pub_creator}> 70 - <SpeedyLink 71 - className="pubListItem sm:w-full sm:min-w-0 min-w-40 w-36 px-1 sm:px-2 py-1 sm:h-max hover:no-underline " 72 - href={`${getBasePublicationURL(props)}/dashboard`} 73 - > 74 - <div 75 - style={{ 76 - backgroundImage: `url(${backgroundImage})`, 77 - backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 78 - backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 79 - }} 80 - className="py-3 px-2 h-full w-full flex flex-col gap-1 place-items-center bg-bg-leaflet border border-border-light rounded-lg text-secondary text-center transparent-outline outline-2 outline-offset-1 hover:outline-border grow " 81 - > 82 - {record?.icon && ( 83 - <div 84 - style={{ 85 - backgroundRepeat: "no-repeat", 86 - backgroundPosition: "center", 87 - backgroundSize: "cover", 88 - backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(record.icon?.ref as unknown as { $link: string })["$link"]})`, 89 - }} 90 - className="w-6 h-6 rounded-full" 91 - /> 92 - )} 93 - <h4 94 - className={`font-bold w-full px-1 rounded-md truncate my-auto ${showPageBackground ? "bg-[rgba(var(--bg-page),0.8)]" : ""}`} 95 - > 96 - {props.name} 97 - </h4> 98 - </div> 99 - </SpeedyLink> 100 - </PublicationThemeProvider> 60 + <SpeedyLink 61 + href={`${getBasePublicationURL(props)}/dashboard`} 62 + className="flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full" 63 + > 64 + {props.asActionButton ? ( 65 + <ActionButton 66 + label={record.name} 67 + icon={<PubIcon record={record} uri={props.uri} />} 68 + nav 69 + className={props.current ? "bg-bg-page! border-border!" : ""} 70 + /> 71 + ) : ( 72 + <> 73 + <PubIcon record={record} uri={props.uri} /> 74 + <div className="truncate">{record.name}</div> 75 + </> 76 + )} 77 + </SpeedyLink> 101 78 ); 102 - } 79 + }; 103 80 104 81 const PubListEmpty = () => { 105 - let { identity } = useIdentityData(); 106 82 return ( 107 - <div className="pubListEmpty accent-container text-sm sm:text-center py-4 px-3 w-full sm:max-w-[200px] flex flex-row sm:flex-col items-start sm:place-items-center justify-center gap-3 sm:gap-1"> 108 - <div className="shrink-0"> 109 - <PubListEmptyIllo /> 110 - </div> 111 - <div className="flex flex-col sm:place-items-center"> 112 - <strong>Publish with Leaflet!</strong> 113 - <div className="text-tertiary"> 114 - {!identity 115 - ? "Link a Bluesky account to publish your writing to a blog or newletter" 116 - : identity && !!identity.atp_did 117 - ? "Publish your writing to a blog or newletter on the ATmosphere" 118 - : ""} 119 - </div> 83 + <SpeedyLink href={`lish/createPub`} className=" hover:no-underline!"> 84 + <ActionButton 85 + label="Publish on ATP" 86 + icon={<PublishSmall />} 87 + nav 88 + subtext="Start a blog or newletter on the Atmosphere" 89 + /> 90 + </SpeedyLink> 91 + ); 92 + }; 93 + 94 + export const PubIcon = (props: { 95 + record: PubLeafletPublication.Record; 96 + uri: string; 97 + }) => { 98 + if (!props.record) return; 120 99 121 - {identity && !!identity.atp_did ? ( 122 - <Link href="/lish/createPub"> 123 - <ButtonSecondary compact className="text-sm mt-3"> 124 - Start a Publication! 125 - </ButtonSecondary> 126 - </Link> 127 - ) : ( 128 - <form action="/api/oauth/login?redirect_url=/" method="GET"> 129 - <ButtonSecondary compact className="text-sm mt-3"> 130 - <BlueskyTiny /> Link Bluesky 131 - </ButtonSecondary> 132 - </form> 133 - )} 100 + return props.record.icon ? ( 101 + <div 102 + style={{ 103 + backgroundRepeat: "no-repeat", 104 + backgroundPosition: "center", 105 + backgroundSize: "cover", 106 + backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(props.record.icon?.ref as unknown as { $link: string })["$link"]})`, 107 + }} 108 + className="w-6 h-6 rounded-full" 109 + /> 110 + ) : ( 111 + <div className="w-6 h-6 rounded-full bg-accent-1 relative"> 112 + <div className="font-bold text-sm absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-accent-2"> 113 + {props.record?.name.slice(0, 1)} 134 114 </div> 135 115 </div> 136 116 ); 137 117 }; 138 118 139 - const PubListEmptyIllo = () => { 119 + export const PubListEmptyIllo = () => { 140 120 return ( 141 121 <svg 142 122 width="59" ··· 152 132 /> 153 133 <path 154 134 d="M44.0793 31.0064C44.7178 30.7014 45.4832 30.9723 45.7882 31.6108L48.1886 36.6362C48.4936 37.2747 48.2236 38.0402 47.5851 38.3452C46.9466 38.6502 46.1811 38.3792 45.8761 37.7407L43.4757 32.7153C43.1708 32.0769 43.4409 31.3114 44.0793 31.0064ZM47.6769 29.396C48.1531 28.8729 48.9632 28.8351 49.4865 29.311L56.049 35.2827C56.5724 35.7589 56.6111 36.5689 56.1349 37.0923C55.6587 37.6156 54.8487 37.6534 54.3254 37.1772L47.7629 31.2065C47.2395 30.7303 47.2007 29.9194 47.6769 29.396ZM39.717 5.68116C42.0981 4.90017 44.5808 4.94473 46.7375 6.12647C49.3605 7.5638 50.8245 10.3133 51.1896 13.4243C51.5549 16.538 50.8421 20.109 49.0216 23.4312C47.2012 26.7534 44.5751 29.2764 41.7541 30.644C38.9355 32.0105 35.8302 32.2552 33.2072 30.8179C30.9031 29.5552 29.4864 27.1999 28.9464 24.6831C28.8301 24.2474 28.6926 23.8222 28.5343 23.3853C27.9734 24.0541 27.3971 24.5221 26.7863 24.7798C25.6169 25.2729 24.5945 24.8983 23.8361 24.3579C22.7923 23.614 21.7765 22.8095 20.9679 21.9312C20.1616 21.0553 19.5008 20.0411 19.2707 18.8745L19.2629 18.8345C19.1678 18.3542 19.054 17.7817 19.2599 16.8921C19.4384 16.1213 19.8478 15.1379 20.6183 13.6987C19.832 13.0689 19.5569 12.0994 19.592 11.1099C19.6232 10.2278 19.8849 9.24332 20.3722 8.35401C20.8366 7.50662 21.4472 6.74544 22.1593 6.22315C22.8689 5.70275 23.7789 5.35579 24.7511 5.57959C25.3946 5.72772 26.0287 5.9727 26.6593 6.16846C27.3433 5.97648 28.0776 5.95162 28.8185 6.16455C29.2306 6.28299 29.6269 6.46238 30.0373 6.58936C30.7081 6.79695 31.4724 6.98507 32.0539 7.01319C35.195 7.16504 36.6776 6.71358 39.6769 5.69385C39.6901 5.68936 39.7037 5.6851 39.717 5.68116ZM45.7111 7.99951C43.9938 7.05853 41.9923 7.16313 40.1877 7.77588C39.8344 7.90062 39.4783 8.04953 39.1222 8.22217C36.7445 9.37492 34.4283 11.5618 32.7961 14.5405C31.1639 17.5192 30.5672 20.6477 30.8752 23.272C30.9368 23.7972 31.0577 24.2812 31.1691 24.7886C31.6931 26.6779 32.7517 28.1338 34.2336 28.9458C36.091 29.9635 38.4422 29.877 40.8224 28.7231C43.2002 27.5704 45.5163 25.3836 47.1486 22.4048C48.7809 19.4259 49.3775 16.2968 49.0695 13.6724C48.7611 11.0452 47.5685 9.0173 45.7111 7.99951ZM49.9005 26.5269C50.1565 25.8672 50.8991 25.5395 51.5587 25.7954L54.547 26.9556C55.2066 27.2116 55.5333 27.9532 55.2775 28.6128C55.0216 29.2724 54.2799 29.6 53.6203 29.3442L50.632 28.1851C49.9724 27.9291 49.6448 27.1865 49.9005 26.5269ZM39.1652 9.26807C41.2542 8.13629 43.5696 7.82566 45.505 8.88623C47.0749 9.74653 48.0237 11.3117 48.4123 13.1314C48.5106 13.5926 48.2163 14.0465 47.755 14.145C47.2939 14.2433 46.8399 13.949 46.7414 13.4878C46.4268 12.0152 45.7071 10.9451 44.6837 10.3843C43.4283 9.69643 41.7566 9.8068 39.9787 10.77C39.5506 11.0019 39.123 11.2806 38.7023 11.603C40.0255 12.7843 41.0004 14.0292 41.5402 15.2642C42.1439 16.6456 42.2208 18.0723 41.5373 19.3198C40.8549 20.5651 39.5891 21.2565 38.1007 21.4917C36.7655 21.7027 35.1903 21.5621 33.4992 21.1011C33.4542 21.6278 33.4497 22.1372 33.4845 22.6216C33.6295 24.6386 34.4361 26.1074 35.6916 26.7954C36.9471 27.4832 38.6187 27.3719 40.3966 26.4087C42.1654 25.4504 43.9291 23.6955 45.217 21.3452C45.7016 20.4607 46.0801 19.562 46.3556 18.6772C46.496 18.227 46.9746 17.9755 47.425 18.1157C47.8752 18.256 48.1266 18.7347 47.9865 19.1851C47.6767 20.1801 47.2535 21.1839 46.715 22.1665C45.2929 24.7616 43.3083 26.7749 41.2101 27.9116C39.1212 29.0432 36.8056 29.354 34.8703 28.2935C32.9348 27.2329 31.9507 25.1135 31.7804 22.7437C31.6095 20.3635 32.2393 17.6083 33.6613 15.0132C35.0834 12.418 37.067 10.4048 39.1652 9.26807ZM22.297 15.0933C21.7014 16.2428 21.4438 16.9252 21.34 17.3735C21.2375 17.8161 21.2789 18.0251 21.3586 18.4282L21.3654 18.4614C21.4906 19.0963 21.8766 19.7651 22.5392 20.4849C23.1994 21.202 24.0755 21.9061 25.0754 22.6187C25.4732 22.9022 25.706 22.918 25.9572 22.812C26.2951 22.6693 26.8456 22.2264 27.5851 21.0552C27.0094 19.7746 26.4315 18.8074 26.0109 18.3853C25.5581 17.9307 24.9198 17.457 24.3625 17.0815C24.0174 16.8492 23.6334 16.6577 23.3009 16.4087C22.8302 16.056 22.5072 15.5915 22.297 15.0933ZM37.4191 12.7485C36.5848 13.607 35.8111 14.6442 35.1593 15.8335C34.5066 17.0247 34.0481 18.237 33.7736 19.4038C35.3748 19.8612 36.7608 19.9738 37.8341 19.8042C38.9745 19.624 39.6854 19.1451 40.0392 18.4995C40.4026 17.8364 40.4284 16.9858 39.9748 15.9478C39.5441 14.9626 38.6986 13.8621 37.4191 12.7485ZM34.2082 9.14893C33.8924 9.16621 33.5635 9.17537 33.2189 9.17627C33.1388 9.23151 33.0511 9.29794 32.9562 9.37647C32.6169 9.65722 32.2522 10.0395 31.8879 10.479C31.1588 11.3585 30.4955 12.3901 30.1144 13.0855C29.6886 13.8625 29.21 14.8403 28.8732 15.8257C28.5838 16.6724 28.4237 17.4545 28.4445 18.0923C28.6141 18.3652 28.7815 18.6595 28.9445 18.9683C29.2882 17.1514 29.9462 15.2969 30.923 13.5142C31.8396 11.8415 32.9612 10.371 34.2082 9.14893ZM28.2306 8.22315C27.9953 8.13158 27.6238 8.07071 27.3918 8.1919C26.8031 8.49937 25.6057 9.41 25.0587 10.4204C24.7889 10.919 24.5664 11.6366 24.3986 12.355C24.273 12.8924 24.0375 13.5361 24.1984 14.0757C24.2869 14.3723 24.4121 14.5578 24.5441 14.6704C24.8879 14.8728 25.225 15.0872 25.5558 15.3101C25.9699 15.589 26.4729 15.9497 26.9435 16.3472C27.0299 15.9811 27.1376 15.6205 27.256 15.2739C27.6363 14.1611 28.164 13.0878 28.6154 12.2642C29.0414 11.4867 29.7649 10.3634 30.5724 9.38916C30.6783 9.26141 30.7871 9.1351 30.8976 9.01123C30.3592 8.90738 29.8338 8.76163 29.4064 8.6294C28.8588 8.45993 28.422 8.2968 28.2306 8.22315ZM24.2443 7.65479C24.0745 7.62582 23.8022 7.66603 23.422 7.94483C23.0226 8.23774 22.6009 8.7314 22.2453 9.38037C21.9128 9.98712 21.7449 10.6469 21.7257 11.1851C21.7078 11.6907 21.8192 11.8972 21.842 11.939C21.8441 11.9429 21.8456 11.9461 21.8459 11.9468L22.6076 12.5562C22.6451 12.3681 22.687 12.1695 22.7345 11.9663C22.9122 11.206 23.1764 10.3097 23.5568 9.60694C23.909 8.95632 24.4229 8.35989 24.9357 7.86866L24.2443 7.65479Z" 155 - fill={theme.colors["accent-contrast"]} 135 + fill={theme.colors["accent-1"]} 156 136 /> 157 137 </g> 158 138 <defs>
+27 -25
app/home/page.tsx
··· 1 1 import { cookies } from "next/headers"; 2 - import { Fact, ReplicacheProvider } from "src/replicache"; 2 + import { Fact, ReplicacheProvider, useEntity } from "src/replicache"; 3 3 import type { Attribute } from "src/replicache/attributes"; 4 4 import { 5 5 ThemeBackgroundProvider, ··· 9 9 import { createIdentity } from "actions/createIdentity"; 10 10 import { drizzle } from "drizzle-orm/node-postgres"; 11 11 import { IdentitySetter } from "./IdentitySetter"; 12 - import { LeafletList } from "./LeafletList"; 12 + 13 13 import { getIdentityData } from "actions/getIdentityData"; 14 14 import { getFactsFromHomeLeaflets } from "app/api/rpc/[command]/getFactsFromHomeLeaflets"; 15 - import { HomeSidebar } from "./HomeSidebar"; 16 - import { HomeFooter } from "./HomeFooter"; 17 - import { Media } from "components/Media"; 18 - import { MyPublicationList } from "./Publications"; 19 15 import { supabaseServerClient } from "supabase/serverClient"; 20 16 import { pool } from "supabase/pool"; 17 + 18 + import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; 19 + import { HomeLayout } from "./HomeLayout"; 21 20 22 21 export default async function Home() { 23 22 let cookieStore = await cookies(); ··· 59 58 60 59 if (!permission_token) 61 60 return ( 62 - <div className="p-4 text-lg text-center flex flex-col gap-4"> 63 - <p>Sorry, home page not found!</p> 61 + <NotFoundLayout> 62 + <p className="font-bold">Sorry, we can't find this home!</p> 64 63 <p> 65 64 This may be a glitch on our end. If the issue persists please{" "} 66 65 <a href="mailto:contact@leaflet.pub">send us a note</a>. 67 66 </p> 68 - </div> 67 + </NotFoundLayout> 69 68 ); 70 69 let [homeLeafletFacts, allLeafletFacts] = await Promise.all([ 71 70 supabaseServerClient.rpc("get_facts", { ··· 87 86 88 87 let root_entity = permission_token.root_entity; 89 88 let home_docs_initialFacts = allLeafletFacts?.result || {}; 89 + 90 90 return ( 91 91 <ReplicacheProvider 92 92 rootEntity={root_entity} ··· 99 99 set={permission_token.permission_token_rights[0].entity_set} 100 100 > 101 101 <ThemeProvider entityID={root_entity}> 102 - <div className="homeWrapper flex h-full bg-bg-leaflet pwa-padding"> 103 - <ThemeBackgroundProvider entityID={root_entity}> 104 - <div className="home relative max-w-screen-lg w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6 "> 105 - <HomeSidebar /> 106 - <div className={`h-full overflow-y-scroll`}> 107 - <Media mobile> 108 - <div className="pubListWrapper p-2 "> 109 - <MyPublicationList /> 110 - </div> 111 - </Media> 112 - <LeafletList initialFacts={home_docs_initialFacts} /> 113 - </div> 114 - <HomeFooter /> 115 - </div> 116 - </ThemeBackgroundProvider> 117 - </div> 102 + <ThemeBackgroundProvider entityID={root_entity}> 103 + <HomeLayout 104 + titles={{ 105 + ...home_docs_initialFacts.titles, 106 + ...auth_res?.permission_token_on_homepage.reduce( 107 + (acc, tok) => { 108 + let title = 109 + tok.permission_tokens.leaflets_in_publications[0]?.title; 110 + if (title) acc[tok.permission_tokens.root_entity] = title; 111 + return acc; 112 + }, 113 + {} as { [k: string]: string }, 114 + ), 115 + }} 116 + entityID={root_entity} 117 + initialFacts={home_docs_initialFacts.facts || {}} 118 + /> 119 + </ThemeBackgroundProvider> 118 120 </ThemeProvider> 119 121 </EntitySetProvider> 120 122 </ReplicacheProvider>
+1 -1
app/legal/content.tsx
··· 36 36 </button> 37 37 </div> 38 38 <div 39 - className={`no-scrollbar border border-border rounded-md bg-bg-page sm:px-4 px-3 pt-2 pb-6 -mt-[1px] h-full overflow-y-scroll rounded-tl-none `} 39 + className={`no-scrollbar border border-border rounded-md bg-bg-page sm:px-4 px-3 pt-2 pb-6 -mt-px h-full overflow-y-scroll rounded-tl-none `} 40 40 > 41 41 {state === "terms" ? <Terms /> : <Privacy />} 42 42 </div>
+7 -7
app/lish/Subscribe.tsx
··· 43 43 <div className="flex relative w-full max-w-sm"> 44 44 <Input 45 45 type="email" 46 - className="input-with-border !pr-[104px] !py-1 grow w-full" 46 + className="input-with-border pr-[104px]! py-1! grow w-full" 47 47 placeholder={ 48 48 props.compact ? "subscribe with email..." : "email here..." 49 49 } ··· 55 55 /> 56 56 <ButtonPrimary 57 57 compact 58 - className="absolute right-1 top-1 !outline-0" 58 + className="absolute right-1 top-1 outline-0!" 59 59 onClick={async () => { 60 60 if (identity?.email) { 61 61 await subscribeToPublicationWithEmail(props.publication); ··· 151 151 <Input 152 152 type="text" 153 153 pattern="[0-9]" 154 - className="input-with-border !pr-[88px] !py-1 max-w-[156px]" 154 + className="input-with-border pr-[88px]! py-1! max-w-[156px]" 155 155 placeholder="000000" 156 156 value={props.codeInputValue} 157 157 onChange={(e) => { ··· 160 160 /> 161 161 <ButtonPrimary 162 162 compact 163 - className="absolute right-1 top-1 !outline-0" 163 + className="absolute right-1 top-1 outline-0!" 164 164 onClick={async () => { 165 165 console.log( 166 166 await confirmEmailAuthToken(props.token, props.codeInputValue), ··· 356 356 <Dialog.Root open={open} onOpenChange={setOpen}> 357 357 <Dialog.Trigger asChild></Dialog.Trigger> 358 358 <Dialog.Portal> 359 - <Dialog.Overlay className="fixed inset-0 bg-primary data-[state=open]:animate-overlayShow opacity-10 blur-sm" /> 359 + <Dialog.Overlay className="fixed inset-0 bg-primary data-[state=open]:animate-overlayShow opacity-10 blur-xs" /> 360 360 <Dialog.Content 361 361 className={` 362 362 z-20 opaque-container 363 363 fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 364 364 w-96 px-3 py-4 365 - max-w-[var(--radix-popover-content-available-width)] 366 - max-h-[var(--radix-popover-content-available-height)] 365 + max-w-(--radix-popover-content-available-width) 366 + max-h-(--radix-popover-content-available-height) 367 367 overflow-y-scroll no-scrollbar 368 368 flex flex-col gap-1 text-center justify-center 369 369 `}
+1 -1
app/lish/[did]/[publication]/[rkey]/Interactions/Comments/CommentBox.tsx
··· 226 226 <div className="w-full relative group"> 227 227 <pre 228 228 ref={mountRef} 229 - className={`border whitespace-pre-wrap input-with-border min-h-32 h-fit !px-2 !py-[6px]`} 229 + className={`border whitespace-pre-wrap input-with-border min-h-32 h-fit px-2! py-[6px]!`} 230 230 /> 231 231 <IOSBS view={view} /> 232 232 </div>
+4 -2
app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx
··· 13 13 import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 14 14 import { Popover } from "components/Popover"; 15 15 import { AppBskyActorProfile, AtUri } from "@atproto/api"; 16 - import { timeAgo } from "app/discover/PubListing"; 17 16 import { BlueskyLogin } from "app/login/LoginForm"; 18 17 import { usePathname } from "next/navigation"; 19 18 import { QuoteContent } from "../Quotes"; 19 + import { timeAgo } from "src/utils/timeAgo"; 20 20 21 21 export type Comment = { 22 22 record: Json; ··· 44 44 Comments 45 45 <button 46 46 className="text-tertiary" 47 - onClick={() => setInteractionState(props.document_uri, { drawerOpen: false })} 47 + onClick={() => 48 + setInteractionState(props.document_uri, { drawerOpen: false }) 49 + } 48 50 > 49 51 <CloseTiny /> 50 52 </button>
+2 -2
app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx
··· 21 21 return ( 22 22 <> 23 23 <div className="sm:pr-4 pr-[6px] snap-center"> 24 - <div className="shrink-0 w-[calc(var(--page-width-units)-12px)] sm:w-[var(--page-width-units)] h-full flex z-10"> 24 + <div className="shrink-0 w-[calc(var(--page-width-units)-12px)] sm:w-(--page-width-units) h-full flex z-10"> 25 25 <div 26 26 id="interaction-drawer" 27 - className="opaque-container !rounded-lg h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll " 27 + className="opaque-container rounded-lg! h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll " 28 28 > 29 29 {currentDrawer === "quotes" ? ( 30 30 <Quotes {...props} />
+1 -1
app/lish/[did]/[publication]/[rkey]/Interactions/Quotes.tsx
··· 129 129 blocks={content} 130 130 did={props.did} 131 131 preview 132 - className="!py-0" 132 + className="py-0!" 133 133 /> 134 134 </div> 135 135 </div>
+8 -8
app/lish/[did]/[publication]/[rkey]/PostContent.tsx
··· 109 109 postBlockWrapper 110 110 min-h-7 111 111 pt-1 pb-2 112 - ${isList && "isListItem !pb-0 "} 112 + ${isList && "isListItem pb-0! "} 113 113 ${alignment} 114 114 `; 115 115 ··· 137 137 } 138 138 case PubLeafletBlocksUnorderedList.isMain(b.block): { 139 139 return ( 140 - <ul className="-ml-[1px] sm:ml-[9px] pb-2"> 140 + <ul className="-ml-px sm:ml-[9px] pb-2"> 141 141 {b.block.children.map((child, i) => ( 142 142 <ListItem 143 143 bskyPostData={bskyPostData} ··· 175 175 <div className="pt-2 pb-2 px-3 grow min-w-0"> 176 176 <div className="flex flex-col w-full min-w-0 h-full grow "> 177 177 <div 178 - className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-none resize-none align-top border h-[24px] line-clamp-1`} 178 + className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-hidden resize-none align-top border h-[24px] line-clamp-1`} 179 179 style={{ 180 180 overflow: "hidden", 181 181 textOverflow: "ellipsis", ··· 186 186 </div> 187 187 188 188 <div 189 - className={`linkBlockDescription text-sm bg-transparent border-none outline-none resize-none align-top grow line-clamp-2`} 189 + className={`linkBlockDescription text-sm bg-transparent border-none outline-hidden resize-none align-top grow line-clamp-2`} 190 190 > 191 191 {b.block.description} 192 192 </div> ··· 217 217 alt={b.block.alt} 218 218 height={b.block.aspectRatio?.height} 219 219 width={b.block.aspectRatio?.width} 220 - className={`!pt-3 sm:!pt-4 rounded-md ${className}`} 220 + className={`pt-3! sm:pt-4! rounded-md ${className}`} 221 221 src={blobRefToSrc(b.block.image.ref, did)} 222 222 /> 223 223 {b.block.alt && ( ··· 240 240 return ( 241 241 // highly unfortunate hack so that the border-l on blockquote is the height of just the text rather than the height of the block, which includes padding. 242 242 <blockquote 243 - className={` blockquote !py-0 !mb-2 last:!mb-3 last:sm:!mb-4 first:!mt-2 sm:first:pt-3 ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "!-mt-2" : "!mt-1"}`} 243 + className={` blockquote py-0! mb-2! last:mb-3! sm:last:mb-4! first:mt-2! sm:first:pt-3 ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "-mt-2!" : "mt-1!"}`} 244 244 {...blockProps} 245 245 > 246 246 <TextBlock ··· 318 318 ) : null; 319 319 320 320 return ( 321 - <li className={`!pb-0 flex flex-row gap-2`}> 321 + <li className={`pb-0! flex flex-row gap-2`}> 322 322 <div 323 - className={`listMarker shrink-0 mx-2 z-[1] mt-[14px] h-[5px] w-[5px] ${props.item.content?.$type !== "null" ? "rounded-full bg-secondary" : ""}`} 323 + className={`listMarker shrink-0 mx-2 z-1 mt-[14px] h-[5px] w-[5px] ${props.item.content?.$type !== "null" ? "rounded-full bg-secondary" : ""}`} 324 324 /> 325 325 <div className="flex flex-col w-full"> 326 326 <Block
+2 -2
app/lish/[did]/[publication]/[rkey]/PostPage.tsx
··· 55 55 <div 56 56 id="post-page" 57 57 className={`postPageWrapper relative overflow-y-auto sm:mx-0 mx-[6px] w-full 58 - ${drawerOpen || hasPageBackground ? "max-w-[var(--page-width-units)] shrink-0 snap-center " : "w-full"} 58 + ${drawerOpen || hasPageBackground ? "max-w-(--page-width-units) shrink-0 snap-center " : "w-full"} 59 59 ${ 60 60 hasPageBackground 61 61 ? "h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] rounded-lg border border-border " ··· 89 89 ?.identity_did ? ( 90 90 <a 91 91 href={`https://leaflet.pub/${document.leaflets_in_publications[0].leaflet}`} 92 - className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1 mx-auto" 92 + className="flex gap-2 items-center hover:no-underline! selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg border-accent-1! outline-accent-1! mx-auto" 93 93 > 94 94 <EditTiny /> Edit Post 95 95 </a>
+1 -1
app/lish/[did]/[publication]/[rkey]/QuoteHandler.tsx
··· 152 152 <div className="">Share via</div> 153 153 154 154 <a 155 - className="flex gap-1 items-center hover:font-bold px-1 hover:!no-underline" 155 + className="flex gap-1 items-center hover:font-bold px-1 hover:no-underline!" 156 156 role="link" 157 157 href={`https://bsky.app/intent/compose?text=${encodeURIComponent(url)}`} 158 158 target="_blank"
+2 -2
app/lish/[did]/[publication]/[rkey]/StaticPostContent.tsx
··· 157 157 className?: string; 158 158 }) { 159 159 return ( 160 - <li className={`!pb-0 flex flex-row gap-2`}> 160 + <li className={`pb-0! flex flex-row gap-2`}> 161 161 <div 162 - className={`listMarker shrink-0 mx-2 z-[1] mt-[14px] h-[5px] w-[5px] rounded-full bg-secondary`} 162 + className={`listMarker shrink-0 mx-2 z-1 mt-[14px] h-[5px] w-[5px] rounded-full bg-secondary`} 163 163 /> 164 164 <div className="flex flex-col"> 165 165 <Block block={{ block: props.item.content }} did={props.did} isList />
+11 -14
app/lish/[did]/[publication]/[rkey]/page.tsx
··· 20 20 import { PostPage } from "./PostPage"; 21 21 import { PageLayout } from "./PageLayout"; 22 22 import { extractCodeBlocks } from "./extractCodeBlocks"; 23 + import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; 23 24 24 25 export async function generateMetadata(props: { 25 26 params: Promise<{ publication: string; did: string; rkey: string }>; ··· 51 52 let did = decodeURIComponent((await props.params).did); 52 53 if (!did) 53 54 return ( 54 - <div className="p-4 text-lg text-center flex flex-col gap-4"> 55 - <p>Sorry, can&apos;t resolve handle.</p> 55 + <NotFoundLayout> 56 + <p className="font-bold">Sorry, can&apos;t resolve handle.</p> 56 57 <p> 57 58 This may be a glitch on our end. If the issue persists please{" "} 58 59 <a href="mailto:contact@leaflet.pub">send us a note</a>. 59 60 </p> 60 - </div> 61 + </NotFoundLayout> 61 62 ); 62 63 let agent = new AtpAgent({ 63 64 service: "https://public.api.bsky.app", ··· 80 81 ]); 81 82 if (!document?.data || !document.documents_in_publications[0].publications) 82 83 return ( 83 - <div className="bg-bg-leaflet h-full p-3 text-center relative"> 84 - <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-md w-full"> 85 - <div className=" px-3 py-4 opaque-container flex flex-col gap-1 mx-2 "> 86 - <h3>Sorry, post not found!</h3> 87 - <p> 88 - This may be a glitch on our end. If the issue persists please{" "} 89 - <a href="mailto:contact@leaflet.pub">send us a note</a>. 90 - </p> 91 - </div> 92 - </div> 93 - </div> 84 + <NotFoundLayout> 85 + <p className="font-bold">Sorry, we can't find this post!</p> 86 + <p> 87 + This may be a glitch on our end. If the issue persists please{" "} 88 + <a href="mailto:contact@leaflet.pub">send us a note</a>. 89 + </p> 90 + </NotFoundLayout> 94 91 ); 95 92 let record = document.data as PubLeafletDocument.Record; 96 93 let bskyPosts = record.pages.flatMap((p) => {
+3 -23
app/lish/[did]/[publication]/dashboard/Actions.tsx
··· 22 22 export const Actions = (props: { publication: string }) => { 23 23 return ( 24 24 <> 25 - <Media mobile> 26 - <SpeedyLink 27 - href="/home" 28 - className="hover:no-underline" 29 - style={{ textDecorationLine: "none !important" }} 30 - > 31 - <ActionButton icon={<HomeSmall />} label="Go Home" /> 32 - </SpeedyLink> 33 - </Media> 34 25 <NewDraftActionButton publication={props.publication} /> 35 26 <PublicationShareButton /> 36 27 <PublicationThemeButton /> 37 28 <PublicationSettingsButton publication={props.publication} /> 38 - <hr className="border-border-light" /> 39 - <Media mobile={false}> 40 - <SpeedyLink 41 - href="/home" 42 - className="hover:no-underline" 43 - style={{ textDecorationLine: "none !important" }} 44 - > 45 - <ActionButton icon={<HomeSmall />} label="Go Home" /> 46 - </SpeedyLink> 47 - </Media> 48 29 </> 49 30 ); 50 31 }; ··· 64 45 <ActionButton 65 46 id="pub-share-button" 66 47 icon=<ShareSmall /> 67 - secondary 68 48 label="Share" 69 49 onClick={() => {}} 70 50 /> ··· 72 52 > 73 53 <MenuItem onSelect={() => {}}> 74 54 <SpeedyLink 75 - href={getPublicationURL(pub!)} 55 + href={getPublicationURL(pub?.publication!)} 76 56 className="text-secondary hover:no-underline" 77 57 > 78 58 <div>Viewer Mode</div> ··· 85 65 onSelect={(e) => { 86 66 e.preventDefault(); 87 67 let rect = (e.currentTarget as Element)?.getBoundingClientRect(); 88 - navigator.clipboard.writeText(getPublicationURL(pub!)); 68 + navigator.clipboard.writeText(getPublicationURL(pub?.publication!)); 89 69 smoker({ 90 70 position: { 91 71 x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, ··· 133 113 return ( 134 114 <Popover 135 115 asChild 136 - className="max-w-xs pb-0 !bg-white" 116 + className="max-w-xs pb-0 bg-white!" 137 117 side={isMobile ? "top" : "right"} 138 118 align={isMobile ? "center" : "start"} 139 119 trigger={
+49 -104
app/lish/[did]/[publication]/dashboard/DraftList.tsx
··· 1 1 "use client"; 2 2 3 3 import { NewDraftSecondaryButton } from "./NewDraftButton"; 4 - import React, { useState } from "react"; 4 + import React from "react"; 5 5 import { usePublicationData } from "./PublicationSWRProvider"; 6 - import { Menu, MenuItem } from "components/Layout"; 7 - import { deleteDraft } from "./deleteDraft"; 8 - import { DeleteSmall } from "components/Icons/DeleteSmall"; 9 - import { ButtonPrimary } from "components/Buttons"; 10 - import { MoreOptionsVerticalTiny } from "components/Icons/MoreOptionsVerticalTiny"; 11 - import { SpeedyLink } from "components/SpeedyLink"; 6 + import { LeafletList } from "app/home/HomeLayout"; 12 7 13 - export function DraftList() { 8 + export function DraftList(props: { 9 + searchValue: string; 10 + showPageBackground: boolean; 11 + }) { 14 12 let { data: pub_data } = usePublicationData(); 15 - if (!pub_data) return null; 13 + if (!pub_data?.publication) return null; 14 + let { leaflets_in_publications, ...publication } = pub_data.publication; 16 15 return ( 17 - <div className="flex flex-col gap-4 pb-4"> 18 - <NewDraftSecondaryButton fullWidth publication={pub_data?.uri} /> 19 - {pub_data.leaflets_in_publications 20 - .filter((d) => !d.doc) 21 - .map((d) => { 22 - return ( 23 - <React.Fragment key={d.leaflet}> 24 - <Draft id={d.leaflet} {...d} /> 25 - <hr className="last:hidden border-border-light" /> 26 - </React.Fragment> 27 - ); 28 - })} 29 - </div> 30 - ); 31 - } 16 + <div className="flex flex-col gap-4"> 17 + <NewDraftSecondaryButton 18 + fullWidth 19 + publication={pub_data?.publication?.uri} 20 + /> 32 21 33 - function Draft(props: { id: string; title: string; description: string }) { 34 - let [open, setOpen] = useState(false); 35 - return ( 36 - <div className="flex flex-row gap-2 items-start"> 37 - <SpeedyLink 38 - key={props.id} 39 - href={`/${props.id}`} 40 - className="flex flex-col gap-0 hover:!no-underline grow" 41 - > 42 - {props.title ? ( 43 - <h3 className="text-primary">{props.title}</h3> 44 - ) : ( 45 - <h3 className="text-tertiary italic">Untitled</h3> 46 - )} 47 - <div className="text-secondary italic">{props.description}</div> 48 - </SpeedyLink> 49 - <Menu 50 - open={open} 51 - onOpenChange={(o) => setOpen(o)} 52 - align="end" 53 - asChild 54 - trigger={ 55 - <button className="text-secondary rounded-md selected-outline !border-transparent hover:!border-border "> 56 - <MoreOptionsVerticalTiny /> 57 - </button> 58 - } 59 - > 60 - <> 61 - <DeleteDraft id={props.id} /> 62 - </> 63 - </Menu> 22 + <LeafletList 23 + searchValue={props.searchValue} 24 + showPreview={false} 25 + defaultDisplay="list" 26 + cardBorderHidden={!props.showPageBackground} 27 + leaflets={leaflets_in_publications 28 + .filter((l) => !l.documents) 29 + .map((l) => { 30 + return { 31 + token: { 32 + ...l.permission_tokens!, 33 + leaflets_in_publications: [ 34 + { 35 + ...l, 36 + publications: { 37 + ...publication, 38 + }, 39 + }, 40 + ], 41 + }, 42 + added_at: "", 43 + }; 44 + })} 45 + initialFacts={pub_data.leaflet_data.facts || {}} 46 + titles={{ 47 + ...leaflets_in_publications.reduce( 48 + (acc, leaflet) => { 49 + if (leaflet.title && leaflet.permission_tokens) 50 + acc[leaflet.permission_tokens.root_entity] = leaflet.title; 51 + return acc; 52 + }, 53 + {} as { [l: string]: string }, 54 + ), 55 + }} 56 + /> 57 + <div className="spacer h-12 w-full bg-transparent shrink-0 " /> 64 58 </div> 65 59 ); 66 60 } 67 - 68 - export function DeleteDraft(props: { id: string }) { 69 - let { mutate } = usePublicationData(); 70 - let [state, setState] = useState<"normal" | "confirm">("normal"); 71 - 72 - if (state === "normal") { 73 - return ( 74 - <MenuItem 75 - className="justify-end" 76 - onSelect={(e) => { 77 - if (state === "normal") { 78 - e.preventDefault(); 79 - return setState("confirm"); 80 - } 81 - }} 82 - > 83 - Delete Draft 84 - <DeleteSmall /> 85 - </MenuItem> 86 - ); 87 - } 88 - if (state === "confirm") { 89 - return ( 90 - <div className="flex flex-col items-center font-bold text-secondary px-2 py-1"> 91 - Are you sure? 92 - <div className="text-sm text-tertiary font-normal"> 93 - This action cannot be undone! 94 - </div> 95 - <ButtonPrimary 96 - className="mt-2" 97 - onClick={async () => { 98 - await mutate((data) => { 99 - if (!data) return data; 100 - return { 101 - ...data, 102 - leaflets_in_publications: data.leaflets_in_publications.filter( 103 - (d) => d.leaflet !== props.id, 104 - ), 105 - }; 106 - }, false); 107 - await deleteDraft(props.id); 108 - }} 109 - > 110 - Delete 111 - </ButtonPrimary> 112 - </div> 113 - ); 114 - } 115 - }
+1 -1
app/lish/[did]/[publication]/dashboard/NewDraftButton.tsx
··· 17 17 router.push(`/${newLeaflet}`); 18 18 }} 19 19 icon=<AddTiny className="m-1 shrink-0" /> 20 - label="New Draft" 20 + label="New" 21 21 /> 22 22 ); 23 23 }
+74 -71
app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx
··· 1 1 "use client"; 2 - import { BlobRef } from "@atproto/lexicon"; 3 - import { useState } from "react"; 4 - import { useIsMobile } from "src/hooks/isMobile"; 5 - import { theme } from "tailwind.config"; 6 - import { usePublicationData } from "./PublicationSWRProvider"; 2 + 3 + import { DraftList } from "./DraftList"; 4 + import { GetPublicationDataReturnType } from "app/api/rpc/[command]/get_publication_data"; 5 + import { Actions } from "./Actions"; 6 + import React, { useState } from "react"; 7 + import { PublishedPostsList } from "./PublishedPostsLists"; 7 8 import { PubLeafletPublication } from "lexicons/api"; 9 + import { PublicationSubscribers } from "./PublicationSubscribers"; 10 + import { AtUri } from "@atproto/syntax"; 11 + import { 12 + HomeDashboardControls, 13 + DashboardLayout, 14 + PublicationDashboardControls, 15 + } from "components/PageLayouts/DashboardLayout"; 16 + import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 8 17 9 - type Tabs = { [tabName: string]: React.ReactNode }; 10 - export function PublicationDashboard<T extends Tabs>(props: { 11 - name: string; 12 - tabs: T; 13 - defaultTab: keyof T; 14 - icon: BlobRef | null; 15 - did: string; 18 + export default function PublicationDashboard({ 19 + publication, 20 + record, 21 + }: { 22 + record: PubLeafletPublication.Record; 23 + publication: Exclude< 24 + GetPublicationDataReturnType["result"]["publication"], 25 + null 26 + >; 16 27 }) { 17 - let { data: pub } = usePublicationData(); 18 - let showPageBackground = !!(pub?.record as PubLeafletPublication.Record) 19 - ?.theme?.showPageBackground; 20 - let [tab, setTab] = useState(props.defaultTab); 21 - let content = props.tabs[tab]; 28 + let [searchValue, setSearchValue] = useState(""); 29 + let [debouncedSearchValue, setDebouncedSearchValue] = useState(""); 22 30 23 - return ( 24 - <> 25 - <div className="pubDashHeader flex flex-row gap-2 w-full justify-between border-b border-border text-secondary items-center "> 26 - <div className="max-w-full w-[1000px] h-full "> 27 - <div 28 - className={`flex gap-2 h-fit py-0.5 pl-1 pr-2 w-fit rounded-md ${showPageBackground ? "bg-none sm:bg-[rgba(var(--bg-page),0.8)]" : ""}`} 29 - > 30 - {props.icon && ( 31 - <div 32 - className="pubDashLogo shrink-0 w-6 h-6 rounded-full border-2 border-bg-page " 33 - style={{ 34 - backgroundImage: `url(/api/atproto_images?did=${props.did}&cid=${(props.icon.ref as unknown as { $link: string })["$link"]})`, 35 - backgroundRepeat: "no-repeat", 36 - backgroundPosition: "center", 37 - backgroundSize: "cover", 38 - }} 39 - /> 40 - )} 41 - <div className="pubDashName font-bold grow text-tertiary max-w-full truncate sm:block hidden"> 42 - {props.name} 43 - </div> 44 - </div> 45 - </div> 46 - <div className="pubDashTabs flex flex-row gap-1"> 47 - {Object.keys(props.tabs).map((t) => ( 48 - <Tab 49 - key={t} 50 - name={t} 51 - selected={t === tab} 52 - onSelect={() => setTab(t)} 53 - showPageBackground={showPageBackground} 54 - /> 55 - ))} 56 - </div> 57 - </div> 58 - <div 59 - className={`pubDashContent py-4 px-3 sm:px-4 h-full overflow-auto ${showPageBackground ? "rounded-b-md border border-border border-t-0 bg-[rgba(var(--bg-page),var(--bg-page-alpha))]" : ""}`} 60 - > 61 - {content} 62 - </div> 63 - </> 31 + useDebouncedEffect( 32 + () => { 33 + setDebouncedSearchValue(searchValue); 34 + }, 35 + 200, 36 + [searchValue], 64 37 ); 65 - } 66 38 67 - function Tab(props: { 68 - name: string; 69 - selected: boolean; 70 - showPageBackground: boolean; 71 - onSelect: () => void; 72 - }) { 73 39 return ( 74 - <div 75 - className={`pubTabs border border-b-0 px-2 pt-1 pb-0.5 rounded-t-md border-border hover:cursor-pointer ${props.selected ? "text-accent-contrast font-bold -mb-[1px]" : ""} ${props.showPageBackground ? "bg-[rgba(var(--bg-page),var(--bg-page-alpha))]" : ""}`} 76 - onClick={() => props.onSelect()} 77 - > 78 - {props.name} 79 - </div> 40 + <DashboardLayout 41 + id={publication.uri} 42 + hasBackgroundImage={!!record?.theme?.backgroundImage} 43 + defaultTab="Drafts" 44 + tabs={{ 45 + Drafts: { 46 + content: ( 47 + <DraftList 48 + searchValue={debouncedSearchValue} 49 + showPageBackground={!!record.theme?.showPageBackground} 50 + /> 51 + ), 52 + controls: ( 53 + <PublicationDashboardControls 54 + defaultDisplay={"list"} 55 + hasBackgroundImage={!!record?.theme?.backgroundImage} 56 + searchValue={searchValue} 57 + setSearchValueAction={setSearchValue} 58 + /> 59 + ), 60 + }, 61 + Published: { 62 + content: ( 63 + <PublishedPostsList 64 + searchValue={debouncedSearchValue} 65 + showPageBackground={!!record.theme?.showPageBackground} 66 + /> 67 + ), 68 + controls: null, 69 + }, 70 + Subscribers: { 71 + content: ( 72 + <PublicationSubscribers 73 + showPageBackground={!!record.theme?.showPageBackground} 74 + /> 75 + ), 76 + controls: null, 77 + }, 78 + }} 79 + actions={<Actions publication={publication.uri} />} 80 + currentPage="pub" 81 + publication={publication.uri} 82 + /> 80 83 ); 81 84 }
+2 -2
app/lish/[did]/[publication]/dashboard/PublicationSWRProvider.tsx
··· 12 12 publication_data: GetPublicationDataReturnType["result"]; 13 13 children: React.ReactNode; 14 14 }) { 15 - let key = `publication-data-${props.publication_did}`; 15 + let key = `publication-data-${props.publication_did}-${props.publication_rkey}`; 16 16 return ( 17 17 <PublicationContext 18 18 value={{ name: props.publication_rkey, did: props.publication_did }} ··· 32 32 33 33 export function usePublicationData() { 34 34 let { name, did } = useContext(PublicationContext); 35 - let key = `publication-data-${did}`; 35 + let key = `publication-data-${did}-${name}`; 36 36 let { data, mutate } = useSWR( 37 37 key, 38 38 async () =>
+188 -40
app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
··· 1 1 "use client"; 2 2 import { AppBskyActorProfile } from "lexicons/api"; 3 3 import { usePublicationData } from "./PublicationSWRProvider"; 4 - import { blobRefToSrc } from "src/utils/blobRefToSrc"; 5 4 import { ButtonPrimary } from "components/Buttons"; 6 5 import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 7 6 import { useSmoker } from "components/Toast"; 7 + import { Menu, MenuItem, Separator } from "components/Layout"; 8 + import { MoreOptionsVerticalTiny } from "components/Icons/MoreOptionsVerticalTiny"; 9 + import { Checkbox } from "components/Checkbox"; 10 + import { useEffect, useState } from "react"; 8 11 9 - export function PublicationSubscribers() { 12 + type subscriber = { email: string | undefined; did: string | undefined }; 13 + 14 + // HELLO! THIS FILE HAS CHECKBOXES AND A HEADER COMMENTED OUT 15 + // the checkboxes would let you select users and and the eader provided a count and let you select all and do actions 16 + // I also removed some props from SubscriberListItem around emails since we dont have them yet. 17 + // WHen we get emails in, we can uncomment some of this stuff 18 + 19 + export function PublicationSubscribers(props: { 20 + showPageBackground?: boolean; 21 + }) { 22 + let smoker = useSmoker(); 10 23 let { data: publication } = usePublicationData(); 11 - let smoker = useSmoker(); 24 + // let [checkedSubscribers, setCheckedSubscribers] = useState<subscriber[]>([]); 25 + // let [checkAll, setCheckAll] = useState(false); 12 26 13 27 if (!publication) return <div>null</div>; 14 - if (publication.publication_subscriptions.length === 0) 28 + let subscribers = publication.publication?.publication_subscriptions || []; 29 + 30 + // useEffect(() => { 31 + // const allSubscribersSelected = subscribers.every((subscriber) => 32 + // checkedSubscribers.some( 33 + // (checked) => 34 + // checked.email === "dummyemail@email.com" && 35 + // checked.did === subscriber.identities?.bsky_profiles?.did, 36 + // ), 37 + // ); 38 + 39 + // if (allSubscribersSelected && subscribers.length > 0) { 40 + // setCheckAll(true); 41 + // } else { 42 + // setCheckAll(false); 43 + // } 44 + // }, [checkedSubscribers]); 45 + 46 + if (subscribers.length === 0) 15 47 return ( 16 - <div className="italic text-tertiary flex flex-col gap-0 text-center justify-center pt-4"> 48 + <div 49 + className={`italic text-tertiary flex flex-col gap-0 text-center justify-center mt-4 border rounded-md ${props.showPageBackground ? "border-border-light p-2" : "border-transparent"}`} 50 + style={ 51 + props.showPageBackground 52 + ? { 53 + backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha)) ", 54 + } 55 + : { backgroundColor: "transparent" } 56 + } 57 + > 17 58 <p className="font-bold"> No subscribers yet </p> 18 59 <p>Start sharing your publication!</p> 19 60 <ButtonPrimary ··· 21 62 onClick={(e) => { 22 63 e.preventDefault(); 23 64 let rect = (e.currentTarget as Element)?.getBoundingClientRect(); 24 - navigator.clipboard.writeText(getPublicationURL(publication!)); 65 + navigator.clipboard.writeText( 66 + getPublicationURL(publication.publication!), 67 + ); 25 68 smoker({ 26 69 position: { 27 70 x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, ··· 37 80 ); 38 81 39 82 return ( 40 - <div> 41 - <h2>{publication.publication_subscriptions.length} Subscribers </h2> 42 - <div className="flex gap-2 flex-col"> 43 - {publication.publication_subscriptions 83 + <div 84 + className={`rounded-md ${props.showPageBackground ? "border-border-light p-2" : "border-transparent"}`} 85 + style={ 86 + props.showPageBackground 87 + ? { 88 + backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha)) ", 89 + } 90 + : { backgroundColor: "transparent" } 91 + } 92 + > 93 + {/*<div className="subscriberListHeader flex gap-2 "> 94 + <Checkbox 95 + checked={checkAll} 96 + onChange={() => { 97 + if (checkAll === false) { 98 + const allSubscribers = subscribers.map((subscriber) => ({ 99 + email: "dummyemail@email.com", 100 + did: subscriber.identities?.bsky_profiles?.did, 101 + })); 102 + setCheckedSubscribers(allSubscribers); 103 + } else { 104 + setCheckedSubscribers([]); 105 + } 106 + }} 107 + className="!font-bold text-secondary mb-1" 108 + > 109 + {subscribers.length} Subscriber{subscribers.length !== 1 && "s"} 110 + </Checkbox> 111 + {checkedSubscribers.length !== 0 && ( 112 + <SubscriberOptions 113 + checkedSubscribers={checkedSubscribers} 114 + allSelected={checkedSubscribers.length === subscribers.length} 115 + /> 116 + )} 117 + </div>*/} 118 + <div className="!font-bold text-secondary mb-1"> 119 + {subscribers.length} Subscriber{subscribers.length !== 1 && "s"} 120 + </div> 121 + <hr className="mb-2 border-border " /> 122 + <div className="subscriberListContent flex gap-3 flex-col "> 123 + {subscribers 44 124 .sort((a, b) => { 45 125 return b.created_at.localeCompare(a.created_at); 46 126 }) 47 127 .map((subscriber, index) => { 48 128 if (!subscriber.identities?.bsky_profiles) return null; 49 129 let handle = subscriber.identities?.bsky_profiles.handle; 130 + let did = subscriber.identities?.bsky_profiles.did; 50 131 let profile = subscriber.identities?.bsky_profiles 51 132 ?.record as AppBskyActorProfile.Record | null; 52 133 if (!profile) return null; 53 134 return ( 54 - <div key={subscriber.identities.bsky_profiles?.did}> 55 - <a 56 - target="_blank" 57 - href={`https://bsky.app/profile/${subscriber.identities.bsky_profiles?.did}`} 58 - className="flex text-primary p-2 gap-1" 59 - > 60 - <div className="flex flex-row gap-2"> 61 - {profile.avatar && ( 62 - <img 63 - className="rounded-full w-8 h-8" 64 - src={ 65 - profile?.avatar && 66 - blobRefToSrc( 67 - profile.avatar.ref, 68 - subscriber.identities.bsky_profiles?.did, 69 - ) 70 - } 71 - alt={profile.displayName} 72 - /> 73 - )} 74 - <div className="flex flex-col gap-1"> 75 - <h3>{profile.displayName}</h3> 76 - <p>@{handle}</p> 77 - </div> 78 - </div> 79 - </a> 80 - {index !== publication.publication_subscriptions.length - 1 && ( 81 - <hr className="border-border" /> 82 - )} 83 - </div> 135 + <SubscriberListItem 136 + key={`${subscriber.identities.bsky_profiles?.did}`} 137 + handle={handle ? handle : undefined} 138 + did={`${subscriber.identities.bsky_profiles?.did}`} 139 + createdAt={subscriber.created_at} 140 + /> 84 141 ); 85 142 })} 86 143 </div> 87 144 </div> 88 145 ); 89 146 } 147 + 148 + const SubscriberListItem = (props: { 149 + handle: string | undefined; 150 + did: string | undefined; 151 + createdAt: string; 152 + }) => { 153 + return ( 154 + // <Checkbox 155 + // className="!font-normal" 156 + // checked={props.checked} 157 + // onChange={() => { 158 + // if (props.checked === false) { 159 + // const newCheckedSubscribers = [ 160 + // ...props.checkedSubscribers, 161 + // { 162 + // email: props.email, 163 + // did: props.did, 164 + // }, 165 + // ]; 166 + // props.setCheckedSubscribers(newCheckedSubscribers); 167 + // } else { 168 + // const newCheckedSubscribers = props.checkedSubscribers.filter( 169 + // (subscriber) => 170 + // !( 171 + // subscriber.email === props.email && subscriber.did === props.did 172 + // ), 173 + // ); 174 + // props.setCheckedSubscribers(newCheckedSubscribers); 175 + // } 176 + // console.log(props.checkedSubscribers); 177 + // }} 178 + // > 179 + <> 180 + <div className="flex items-end flex-row gap-2 w-full"> 181 + {/*<a 182 + target="_blank" 183 + href={`mailto:${props.email}`} 184 + className="font-bold text-primary" 185 + > 186 + {props.email} 187 + </a> 188 + 189 + {props.handle && props.email && ( 190 + <Separator classname="sm:block hidden" /> 191 + )}*/} 192 + {props.handle && ( 193 + <a 194 + target="_blank" 195 + href={`https://bsky.app/profile/${props.did}`} 196 + className={"font-bold"} 197 + > 198 + @{props.handle} 199 + </a> 200 + )} 201 + <div className="px-1 py-0 h-max rounded-md text-sm italic text-tertiary"> 202 + {new Date(props.createdAt).toLocaleString(undefined, { 203 + year: "2-digit", 204 + month: "2-digit", 205 + day: "2-digit", 206 + })} 207 + </div> 208 + </div> 209 + </> 210 + // </Checkbox> 211 + ); 212 + }; 213 + 214 + const SubscriberOptions = (props: { 215 + checkedSubscribers: subscriber[]; 216 + allSelected: boolean; 217 + }) => { 218 + return ( 219 + <Menu 220 + asChild 221 + className="" 222 + trigger={ 223 + <ButtonPrimary compact className="-mt-[1px]"> 224 + {props.allSelected ? "All" : props.checkedSubscribers.length} Selected{" "} 225 + <MoreOptionsVerticalTiny /> 226 + </ButtonPrimary> 227 + } 228 + > 229 + <MenuItem className="justify-center" onSelect={() => {}}> 230 + Export {props.allSelected ? "All" : "Selected"} 231 + </MenuItem> 232 + <MenuItem className="justify-center" onSelect={() => {}}> 233 + Remove {props.allSelected ? "All" : "Selected"} 234 + </MenuItem> 235 + </Menu> 236 + ); 237 + };
+39 -22
app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
··· 18 18 import { QuoteTiny } from "components/Icons/QuoteTiny"; 19 19 import { CommentTiny } from "components/Icons/CommentTiny"; 20 20 21 - export function PublishedPostsList() { 22 - let { data: publication } = usePublicationData(); 21 + export function PublishedPostsList(props: { 22 + searchValue: string; 23 + showPageBackground: boolean; 24 + }) { 25 + let { data } = usePublicationData(); 23 26 let params = useParams(); 27 + let { publication } = data!; 24 28 if (!publication) return null; 25 29 if (publication.documents_in_publications.length === 0) 26 30 return ( ··· 29 33 </div> 30 34 ); 31 35 return ( 32 - <div className="publishedList w-full flex flex-col gap-4 pb-4"> 36 + <div className="publishedList w-full flex flex-col gap-2 pb-4"> 33 37 {publication.documents_in_publications 34 38 .sort((a, b) => { 35 39 let aRecord = a.documents?.data! as PubLeafletDocument.Record; ··· 49 53 ); 50 54 let uri = new AtUri(doc.documents.uri); 51 55 let record = doc.documents.data as PubLeafletDocument.Record; 52 - let quotes = 53 - doc.documents.document_mentions_in_bsky[0]?.count || 0; 54 - let comments = 55 - doc.documents.comments_on_documents[0]?.count || 0; 56 + let quotes = doc.documents.document_mentions_in_bsky[0]?.count || 0; 57 + let comments = doc.documents.comments_on_documents[0]?.count || 0; 56 58 57 59 return ( 58 60 <Fragment key={doc.documents?.uri}> 59 61 <div className="flex gap-2 w-full "> 60 - <div className="publishedPost grow flex flex-col hover:!no-underline"> 62 + <div 63 + className={`publishedPost grow flex flex-col hover:no-underline! rounded-lg border ${props.showPageBackground ? "border-border-light py-1 px-2" : "border-transparent px-1"}`} 64 + style={{ 65 + backgroundColor: props.showPageBackground 66 + ? "rgba(var(--bg-page), var(--bg-page-alpha))" 67 + : "transparent", 68 + }} 69 + > 61 70 <div className="flex justify-between gap-2"> 62 71 <a 63 - className="hover:!no-underline" 72 + className="hover:no-underline!" 64 73 target="_blank" 65 74 href={`${getPublicationURL(publication)}/${uri.rkey}`} 66 75 > ··· 100 109 )} 101 110 </p> 102 111 ) : null} 103 - {(comments > 0 || quotes > 0) && record.publishedAt ? " | " : ""} 112 + {(comments > 0 || quotes > 0) && record.publishedAt 113 + ? " | " 114 + : ""} 104 115 {quotes > 0 && ( 105 116 <SpeedyLink 106 117 href={`${getPublicationURL(publication)}/${uri.rkey}?interactionDrawer=quotes`} ··· 121 132 </div> 122 133 </div> 123 134 </div> 124 - <hr className="last:hidden border-border-light" /> 135 + {!props.showPageBackground && ( 136 + <hr className="last:hidden border-border-light" /> 137 + )} 125 138 </Fragment> 126 139 ); 127 140 })} ··· 136 149 alignOffset={20} 137 150 asChild 138 151 trigger={ 139 - <button className="text-secondary rounded-md selected-outline !border-transparent hover:!border-border h-min"> 152 + <button className="text-secondary rounded-md selected-outline border-transparent! hover:border-border! h-min"> 140 153 <MoreOptionsVerticalTiny /> 141 154 </button> 142 155 } ··· 149 162 }; 150 163 151 164 function OptionsMenu(props: { document_uri: string }) { 152 - let { mutate, data: publication } = usePublicationData(); 165 + let { mutate, data } = usePublicationData(); 153 166 let [state, setState] = useState<"normal" | "confirm">("normal"); 154 167 155 - let postLink = publication 156 - ? `${getPublicationURL(publication)}/${new AtUri(props.document_uri).rkey}` 168 + let postLink = data?.publication 169 + ? `${getPublicationURL(data?.publication)}/${new AtUri(props.document_uri).rkey}` 157 170 : null; 158 171 159 172 if (state === "normal") { ··· 203 216 if (!data) return data; 204 217 return { 205 218 ...data, 206 - leaflets_in_publications: data.leaflets_in_publications.filter( 207 - (l) => l.doc !== props.document_uri, 208 - ), 209 - documents_in_publications: 210 - data.documents_in_publications.filter( 211 - (d) => d.documents?.uri !== props.document_uri, 212 - ), 219 + publication: { 220 + ...data.publication!, 221 + leaflets_in_publications: 222 + data.publication?.leaflets_in_publications.filter( 223 + (l) => l.doc !== props.document_uri, 224 + ) || [], 225 + documents_in_publications: 226 + data.publication?.documents_in_publications.filter( 227 + (d) => d.documents?.uri !== props.document_uri, 228 + ) || [], 229 + }, 213 230 }; 214 231 }, false); 215 232 await deletePost(props.document_uri);
+20 -63
app/lish/[did]/[publication]/dashboard/page.tsx
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 2 import { Metadata } from "next"; 3 - 4 - import { Sidebar } from "components/ActionBar/Sidebar"; 5 - 6 - import { Media } from "components/Media"; 7 - import { Footer } from "components/ActionBar/Footer"; 8 - import { PublicationDashboard } from "./PublicationDashboard"; 9 - import { DraftList } from "./DraftList"; 10 3 import { getIdentityData } from "actions/getIdentityData"; 11 - import { Actions } from "./Actions"; 12 - import React from "react"; 13 4 import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; 14 5 import { PublicationSWRDataProvider } from "./PublicationSWRProvider"; 15 - import { PublishedPostsList } from "./PublishedPostsLists"; 16 - import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api"; 17 - import { PublicationSubscribers } from "./PublicationSubscribers"; 18 - import { 19 - PublicationThemeProvider, 20 - PublicationThemeProviderDashboard, 21 - } from "components/ThemeManager/PublicationThemeProvider"; 22 - import { blobRefToSrc } from "src/utils/blobRefToSrc"; 6 + import { PubLeafletPublication } from "lexicons/api"; 7 + import { PublicationThemeProviderDashboard } from "components/ThemeManager/PublicationThemeProvider"; 23 8 import { AtUri } from "@atproto/syntax"; 9 + import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; 10 + import PublicationDashboard from "./PublicationDashboard"; 11 + import Link from "next/link"; 24 12 25 13 export async function generateMetadata(props: { 26 14 params: Promise<{ publication: string; did: string }>; ··· 28 16 let did = decodeURIComponent((await props.params).did); 29 17 if (!did) return { title: "Publication 404" }; 30 18 31 - let { result: publication } = await get_publication_data.handler( 19 + let { result: publication_data } = await get_publication_data.handler( 32 20 { 33 21 did, 34 22 publication_name: decodeURIComponent((await props.params).publication), 35 23 }, 36 24 { supabase: supabaseServerClient }, 37 25 ); 26 + let { publication } = publication_data; 38 27 let record = 39 28 (publication?.record as PubLeafletPublication.Record) || undefined; 40 29 if (!publication) return { title: "404 Publication" }; ··· 49 38 let identity = await getIdentityData(); 50 39 if (!identity || !identity.atp_did) 51 40 return ( 52 - <div className="p-4 text-lg text-center flex flex-col gap-4"> 53 - <p>Sorry, looks like you&apos;re not logged in.</p> 41 + <NotFoundLayout> 42 + <p>Looks like you&apos;re not logged in.</p> 54 43 <p> 55 - This may be a glitch on our end. If the issue persists please{" "} 44 + If the issue persists please{" "} 56 45 <a href="mailto:contact@leaflet.pub">send us a note</a>. 57 46 </p> 58 - </div> 47 + </NotFoundLayout> 59 48 ); 60 49 let did = decodeURIComponent(params.did); 61 50 if (!did) return <PubNotFound />; 62 - let { result: publication } = await get_publication_data.handler( 51 + let { result: publication_data } = await get_publication_data.handler( 63 52 { 64 53 did, 65 54 publication_name: decodeURIComponent((await props.params).publication), 66 55 }, 67 56 { supabase: supabaseServerClient }, 68 57 ); 58 + let { publication, leaflet_data } = publication_data; 59 + let record = publication?.record as PubLeafletPublication.Record | null; 69 60 70 - if (!publication || identity.atp_did !== publication.identity_did) 61 + if (!publication || identity.atp_did !== publication.identity_did || !record) 71 62 return <PubNotFound />; 72 - let record = publication?.record as PubLeafletPublication.Record | null; 73 63 let uri = new AtUri(publication.uri); 74 - 75 - let showPageBackground = !!record?.theme?.showPageBackground; 76 64 77 65 try { 78 66 return ( 79 67 <PublicationSWRDataProvider 80 68 publication_did={did} 81 69 publication_rkey={uri.rkey} 82 - publication_data={publication} 70 + publication_data={publication_data} 83 71 > 84 72 <PublicationThemeProviderDashboard record={record}> 85 - <div className="pubDashWrapper relative w-max h-full flex items-stretch pwa-padding"> 86 - <div className="flex sm:flex-row flex-col max-h-full h-full"> 87 - <div 88 - className="pubDashSidebarWrapper flex justify-end items-start " 89 - style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 90 - > 91 - <div className="pubDashSidebar relative w-16 justify-items-end"> 92 - <Sidebar className="mt-6 p-2 "> 93 - <Actions publication={publication.uri} /> 94 - </Sidebar> 95 - </div> 96 - </div> 97 - <div 98 - className={`pubDash grow sm:h-full h-32 w-full flex flex-col items-stretch pt-2 sm:pt-6 ml-[6px] sm:ml-0 max-w-[var(--page-width-units)] ${showPageBackground ? "sm:pb-8 pb-1" : "pb-0"}`} 99 - > 100 - <PublicationDashboard 101 - did={did} 102 - icon={record?.icon ? record.icon : null} 103 - name={publication.name} 104 - tabs={{ 105 - Drafts: <DraftList />, 106 - Published: <PublishedPostsList />, 107 - Subscribers: <PublicationSubscribers />, 108 - }} 109 - defaultTab={"Drafts"} 110 - /> 111 - </div> 112 - <Footer> 113 - <Actions publication={publication.uri} /> 114 - </Footer> 115 - </div> 116 - </div> 73 + <PublicationDashboard publication={publication} record={record} /> 117 74 </PublicationThemeProviderDashboard> 118 75 </PublicationSWRDataProvider> 119 76 ); ··· 125 82 126 83 const PubNotFound = () => { 127 84 return ( 128 - <div className="p-4 text-lg text-center flex flex-col gap-4"> 129 - <p>Sorry, publication not found!</p> 85 + <NotFoundLayout> 86 + <p className="font-bold">Sorry, we can't find this publication!</p> 130 87 <p> 131 88 This may be a glitch on our end. If the issue persists please{" "} 132 89 <a href="mailto:contact@leaflet.pub">send us a note</a>. 133 90 </p> 134 - </div> 91 + </NotFoundLayout> 135 92 ); 136 93 };
+19 -8
app/lish/[did]/[publication]/generateFeed.ts
··· 7 7 } from "lexicons/api"; 8 8 import { createElement } from "react"; 9 9 import { StaticPostContent } from "./[rkey]/StaticPostContent"; 10 - import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; 11 10 import { supabaseServerClient } from "supabase/serverClient"; 12 11 import { NextResponse } from "next/server"; 13 12 ··· 18 17 let renderToReadableStream = await import("react-dom/server").then( 19 18 (module) => module.renderToReadableStream, 20 19 ); 21 - let { result: publication } = await get_publication_data.handler( 22 - { 23 - did: did, 24 - publication_name: publication_name, 25 - }, 26 - { supabase: supabaseServerClient }, 27 - ); 20 + let uri; 21 + if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(publication_name)) { 22 + uri = AtUri.make( 23 + did, 24 + "pub.leaflet.publication", 25 + publication_name, 26 + ).toString(); 27 + } 28 + let { data: publication } = await supabaseServerClient 29 + .from("publications") 30 + .select( 31 + `*, 32 + publication_subscriptions(*), 33 + documents_in_publications(documents(*)) 34 + `, 35 + ) 36 + .eq("identity_did", did) 37 + .or(`name.eq."${publication_name}", uri.eq."${uri}"`) 38 + .single(); 28 39 29 40 let pubRecord = publication?.record as PubLeafletPublication.Record; 30 41 if (!publication || !pubRecord)
+23 -8
app/lish/[did]/[publication]/layout.tsx
··· 1 1 import { PubLeafletPublication } from "lexicons/api"; 2 - import { get_publication_data } from "app/api/rpc/[command]/get_publication_data"; 3 2 import { supabaseServerClient } from "supabase/serverClient"; 4 3 import { Metadata } from "next"; 4 + import { AtUri } from "@atproto/syntax"; 5 5 6 6 export default async function PublicationLayout(props: { 7 7 children: React.ReactNode; ··· 16 16 }>; 17 17 }): Promise<Metadata> { 18 18 let params = await props.params; 19 + let did = decodeURIComponent(params.did); 19 20 if (!params.did || !params.publication) return { title: "Publication 404" }; 20 - let { result: publication } = await get_publication_data.handler( 21 - { 22 - did: decodeURIComponent(params.did), 23 - publication_name: decodeURIComponent(params.publication), 24 - }, 25 - { supabase: supabaseServerClient }, 26 - ); 21 + 22 + let uri; 23 + let publication_name = decodeURIComponent(params.publication); 24 + if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(publication_name)) { 25 + uri = AtUri.make( 26 + did, 27 + "pub.leaflet.publication", 28 + publication_name, 29 + ).toString(); 30 + } 31 + let { data: publication } = await supabaseServerClient 32 + .from("publications") 33 + .select( 34 + `*, 35 + publication_subscriptions(*), 36 + documents_in_publications(documents(*)) 37 + `, 38 + ) 39 + .eq("identity_did", did) 40 + .or(`name.eq."${publication_name}", uri.eq."${uri}"`) 41 + .single(); 27 42 if (!publication) return { title: "Publication 404" }; 28 43 29 44 let pubRecord = publication?.record as PubLeafletPublication.Record;
+6 -5
app/lish/[did]/[publication]/page.tsx
··· 10 10 PublicationBackgroundProvider, 11 11 PublicationThemeProvider, 12 12 } from "components/ThemeManager/PublicationThemeProvider"; 13 + import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; 13 14 import { SpeedyLink } from "components/SpeedyLink"; 14 15 import { QuoteTiny } from "components/Icons/QuoteTiny"; 15 16 import { CommentTiny } from "components/Icons/CommentTiny"; ··· 68 69 className={`pubWrapper flex flex-col sm:py-6 h-full ${showPageBackground ? "max-w-prose mx-auto sm:px-0 px-[6px] py-2" : "w-full overflow-y-scroll"}`} 69 70 > 70 71 <div 71 - className={`pub sm:max-w-prose max-w-[var(--page-width-units)] w-[1000px] mx-auto px-3 sm:px-4 py-5 ${showPageBackground ? "overflow-auto h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] border border-border rounded-lg" : "h-fit"}`} 72 + className={`pub sm:max-w-prose max-w-(--page-width-units) w-[1000px] mx-auto px-3 sm:px-4 py-5 ${showPageBackground ? "overflow-auto h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] border border-border rounded-lg" : "h-fit"}`} 72 73 > 73 74 <div className="pubHeader flex flex-col pb-8 w-full text-center justify-center "> 74 75 {record?.icon && ( ··· 141 142 <div className="flex w-full grow flex-col "> 142 143 <SpeedyLink 143 144 href={`${getPublicationURL(publication)}/${uri.rkey}`} 144 - className="publishedPost hover:!no-underline flex flex-col" 145 + className="publishedPost hover:no-underline! flex flex-col" 145 146 > 146 147 <h3 className="text-primary">{doc_record.title}</h3> 147 148 <p className="italic text-secondary"> ··· 198 199 199 200 const PubNotFound = () => { 200 201 return ( 201 - <div className="p-4 text-lg text-center flex flex-col gap-4"> 202 - <p>Sorry, publication not found!</p> 202 + <NotFoundLayout> 203 + <p className="font-bold">Sorry, we can't find this publication!</p> 203 204 <p> 204 205 This may be a glitch on our end. If the issue persists please{" "} 205 206 <a href="mailto:contact@leaflet.pub">send us a note</a>. 206 207 </p> 207 - </div> 208 + </NotFoundLayout> 208 209 ); 209 210 };
+2 -2
app/lish/createPub/CreatePubForm.tsx
··· 207 207 208 208 return ( 209 209 <div className="flex flex-col gap-1"> 210 - <label className=" input-with-border flex flex-col text-sm text-tertiary font-bold italic leading-tight !py-1 !px-[6px]"> 210 + <label className=" input-with-border flex flex-col text-sm text-tertiary font-bold italic leading-tight py-1! px-[6px]!"> 211 211 <div>Choose your domain</div> 212 212 <div className="flex flex-row items-center"> 213 213 <Input 214 214 minLength={3} 215 215 maxLength={63} 216 216 placeholder="domain" 217 - className="appearance-none w-full font-normal bg-transparent text-base text-primary focus:outline-0 outline-none" 217 + className="appearance-none w-full font-normal bg-transparent text-base text-primary focus:outline-0 outline-hidden" 218 218 value={props.domain} 219 219 onChange={(e) => props.setDomain(e.currentTarget.value)} 220 220 />
+6 -4
app/lish/createPub/UpdatePubForm.tsx
··· 21 21 import { Checkbox } from "components/Checkbox"; 22 22 23 23 export const EditPubForm = () => { 24 - let { data: pubData } = usePublicationData(); 24 + let { data } = usePublicationData(); 25 + let { publication: pubData } = data || {}; 25 26 let record = pubData?.record as PubLeafletPublication.Record; 26 27 let [formState, setFormState] = useState<"normal" | "loading">("normal"); 27 28 ··· 180 181 }; 181 182 182 183 export function CustomDomainForm() { 183 - let { data: pubData } = usePublicationData(); 184 + let { data } = usePublicationData(); 185 + let { publication: pubData } = data || {}; 184 186 if (!pubData) return null; 185 187 let record = pubData?.record as PubLeafletPublication.Record; 186 188 let [state, setState] = useState< ··· 325 327 <div className="absolute right-0 top-0 bottom-0 flex justify-end items-center w-4 "> 326 328 {pending ? ( 327 329 <button 328 - className="group/pending px-1 py-0.5 flex gap-1 items-center rounded-full hover:bg-accent-1 hover:text-accent-2 hover:outline-accent-1 border-transparent outline outline-transparent selected-outline" 330 + className="group/pending px-1 py-0.5 flex gap-1 items-center rounded-full hover:bg-accent-1 hover:text-accent-2 hover:outline-accent-1 border-transparent outline-solid outline-transparent selected-outline" 329 331 onClick={() => { 330 332 if (data?.error === "Verification_needed") { 331 333 props.setDomain(data.verification); ··· 356 358 }); 357 359 mutate("publication-data"); 358 360 }} 359 - className="group/domain flex gap-1 items-center rounded-full bg-none w-max font-bold px-1 py-0.5 hover:bg-accent-1 hover:text-accent-2 border-transparent outline outline-transparent hover:outline-accent-1 selected-outline" 361 + className="group/domain flex gap-1 items-center rounded-full bg-none w-max font-bold px-1 py-0.5 hover:bg-accent-1 hover:text-accent-2 border-transparent outline-solid outline-transparent hover:outline-accent-1 selected-outline" 360 362 > 361 363 <p className="group-hover/domain:block hidden w-max pl-1"> 362 364 set as default
+1 -1
app/login/LoginForm.tsx
··· 153 153 154 154 <ButtonPrimary 155 155 type="submit" 156 - className="place-self-end !px-[2px] absolute right-1 bottom-1" 156 + className="place-self-end px-[2px]! absolute right-1 bottom-1" 157 157 > 158 158 <ArrowRightTiny />{" "} 159 159 </ButtonPrimary>
+2 -2
app/templates/TemplateList.tsx
··· 43 43 target="_blank" 44 44 className="no-underline hover:no-underline" 45 45 > 46 - <ButtonPrimary className="bg-primary hover:!outline-none hover:scale-105 hover:rotate-3 transition-all"> 46 + <ButtonPrimary className="bg-primary hover:outline-hidden! hover:scale-105 hover:rotate-3 transition-all"> 47 47 Preview 48 48 </ButtonPrimary> 49 49 </Link> 50 50 <ButtonPrimary 51 - className=" hover:!outline-none hover:scale-105 hover:-rotate-2 transition-all" 51 + className=" hover:outline-hidden! hover:scale-105 hover:-rotate-2 transition-all" 52 52 onClick={async () => { 53 53 let id = await createNewLeafletFromTemplate( 54 54 props.templateID,
+1 -1
app/templates/page.tsx
··· 11 11 export default function Templates() { 12 12 return ( 13 13 <div className="flex h-full bg-bg-leaflet"> 14 - <div className="home relative max-w-screen-lg w-full h-full mx-auto flex sm:flex-row flex-col-reverse px-4 sm:px-6 "> 14 + <div className="home relative max-w-(--breakpoint-lg) w-full h-full mx-auto flex sm:flex-row flex-col-reverse px-4 sm:px-6 "> 15 15 <div className="homeOptions z-10 shrink-0 sm:static absolute bottom-0 place-self-end sm:place-self-start flex sm:flex-col flex-row-reverse gap-2 sm:w-fit w-full items-center pb-2 pt-1 sm:pt-7"> 16 16 {/* NOT using <HomeButton /> b/c it does a permission check we don't need */} 17 17 <Link href="/home">
+25 -18
components/ActionBar/ActionButton.tsx
··· 1 1 "use client"; 2 + 2 3 import { useContext, useEffect } from "react"; 3 4 import { SidebarContext } from "./Sidebar"; 4 5 import React, { forwardRef, type JSX } from "react"; ··· 6 7 7 8 type ButtonProps = Omit<JSX.IntrinsicElements["button"], "content">; 8 9 9 - export const ActionButton = forwardRef< 10 - HTMLButtonElement, 11 - ButtonProps & { 10 + export const ActionButton = ( 11 + props: ButtonProps & { 12 12 id?: string; 13 13 icon: React.ReactNode; 14 14 label: React.ReactNode; 15 15 primary?: boolean; 16 16 secondary?: boolean; 17 - } 18 - >((props, ref) => { 19 - let { id, icon, label, primary, secondary, ...buttonProps } = props; 17 + nav?: boolean; 18 + className?: string; 19 + subtext?: string; 20 + }, 21 + ) => { 22 + let { id, icon, label, primary, secondary, nav, ...buttonProps } = props; 20 23 let sidebar = useContext(SidebarContext); 21 24 let inOpenPopover = useContext(PopoverOpenContext); 22 25 useEffect(() => { ··· 30 33 return ( 31 34 <button 32 35 {...buttonProps} 33 - ref={ref} 34 - id={props.id} 35 36 className={` 36 37 actionButton relative font-bold 37 38 rounded-md border 38 - flex gap-2 items-center sm:justify-start justify-center 39 + flex gap-2 items-start sm:justify-start justify-center 39 40 p-1 sm:mx-0 40 41 ${ 41 - props.primary 42 + primary 42 43 ? "w-full bg-accent-1 border-accent-1 text-accent-2 transparent-outline sm:hover:outline-accent-contrast focus:outline-accent-1 outline-offset-1 mx-1 first:ml-0" 43 - : props.secondary 44 + : secondary 44 45 ? "sm:w-full w-max bg-bg-page border-accent-contrast text-accent-contrast transparent-outline focus:outline-accent-contrast sm:hover:outline-accent-contrast outline-offset-1 mx-1 first:ml-0" 45 - : "sm:w-full w-max border-transparent text-accent-contrast sm:hover:border-accent-contrast" 46 + : nav 47 + ? "w-full border-transparent text-secondary sm:hover:border-border justify-start!" 48 + : "sm:w-full border-transparent text-accent-contrast sm:hover:border-accent-contrast" 46 49 } 50 + ${props.className} 47 51 `} 48 52 > 49 - <div className="shrink-0">{props.icon}</div> 53 + <div className="shrink-0">{icon}</div> 50 54 <div 51 - className={`pr-1 w-max ${sidebar.open ? "block" : props.primary || props.secondary ? "sm:hidden block" : "hidden"}`} 55 + className={`flex flex-col pr-1 leading-snug max-w-full min-w-0 ${sidebar.open ? "block" : primary || secondary || nav ? "sm:hidden block" : "hidden"}`} 52 56 > 53 - {props.label} 57 + <div className="truncate text-left pt-[1px]">{label}</div> 58 + {props.subtext && ( 59 + <div className="text-xs text-tertiary font-normal text-left"> 60 + {props.subtext} 61 + </div> 62 + )} 54 63 </div> 55 64 </button> 56 65 ); 57 - }); 58 - 59 - ActionButton.displayName = "ActionButton"; 66 + };
+140
components/ActionBar/Navigation.tsx
··· 1 + import { HomeSmall } from "components/Icons/HomeSmall"; 2 + import { ActionButton } from "./ActionButton"; 3 + import { Sidebar } from "./Sidebar"; 4 + import { useIdentityData } from "components/IdentityProvider"; 5 + import Link from "next/link"; 6 + import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 7 + import { PublicationButtons } from "./Publications"; 8 + import { Popover } from "components/Popover"; 9 + import { MenuSmall } from "components/Icons/MenuSmall"; 10 + 11 + export type navPages = "home" | "reader" | "pub" | "discover"; 12 + 13 + export const DesktopNavigation = (props: { 14 + currentPage: navPages; 15 + publication?: string; 16 + }) => { 17 + let unreadNotifications = true; 18 + 19 + return ( 20 + <div className="flex flex-col gap-4"> 21 + <Sidebar alwaysOpen> 22 + <NavigationOptions 23 + currentPage={props.currentPage} 24 + publication={props.publication} 25 + /> 26 + </Sidebar> 27 + {/*<Sidebar alwaysOpen> 28 + <ActionButton 29 + icon={ 30 + unreadNotifications ? ( 31 + <NotificationsUnreadSmall /> 32 + ) : ( 33 + <NotificationsReadSmall /> 34 + ) 35 + } 36 + label="Notifications" 37 + /> 38 + </Sidebar>*/} 39 + </div> 40 + ); 41 + }; 42 + 43 + export const MobileNavigation = (props: { 44 + currentPage: navPages; 45 + publication?: string; 46 + }) => { 47 + let { identity } = useIdentityData(); 48 + let thisPublication = identity?.publications?.find( 49 + (pub) => pub.uri === props.publication, 50 + ); 51 + return ( 52 + <Popover 53 + onOpenAutoFocus={(e) => e.preventDefault()} 54 + asChild 55 + className="px-2! !max-w-[256px]" 56 + trigger={ 57 + <div className="shrink-0 p-1 pr-2 text-accent-contrast h-full flex gap-2 font-bold items-center"> 58 + <MenuSmall /> 59 + <div className="truncate max-w-[72px]"> 60 + {props.currentPage === "home" ? ( 61 + <>Home</> 62 + ) : props.currentPage === "reader" ? ( 63 + <>Reader</> 64 + ) : props.currentPage === "discover" ? ( 65 + <>Discover</> 66 + ) : props.currentPage === "pub" ? ( 67 + thisPublication && <>{thisPublication.name}</> 68 + ) : null} 69 + </div> 70 + </div> 71 + } 72 + > 73 + <NavigationOptions 74 + currentPage={props.currentPage} 75 + publication={props.publication} 76 + /> 77 + </Popover> 78 + ); 79 + }; 80 + 81 + const NavigationOptions = (props: { 82 + currentPage: navPages; 83 + publication?: string; 84 + }) => { 85 + let { identity } = useIdentityData(); 86 + let thisPublication = identity?.publications?.find( 87 + (pub) => pub.uri === props.publication, 88 + ); 89 + return ( 90 + <> 91 + <HomeButton current={props.currentPage === "home"} /> 92 + <ReaderButton current={props.currentPage === "reader"} /> 93 + <hr className="border-border-light my-1" /> 94 + <PublicationButtons currentPubUri={thisPublication?.uri} /> 95 + </> 96 + ); 97 + }; 98 + 99 + const HomeButton = (props: { current?: boolean }) => { 100 + return ( 101 + <Link href={"/home"} className="hover:!no-underline"> 102 + <ActionButton 103 + nav 104 + icon={<HomeSmall />} 105 + label="Home" 106 + className={props.current ? "bg-bg-page! border-border-light!" : ""} 107 + /> 108 + </Link> 109 + ); 110 + }; 111 + 112 + const ReaderButton = (props: { current?: boolean }) => { 113 + // let readerUnreads = true; 114 + // let subs = false; 115 + 116 + // if (subs) 117 + // return ( 118 + // <ActionButton 119 + // nav 120 + // icon={readerUnreads ? <ReaderUnreadSmall /> : <ReaderReadSmall />} 121 + // label="Reader" 122 + // className={` 123 + // ${readerUnreads ? "text-accent-contrast! border-accent-contrast" : props.current ? "bg-border-light! border-border" : ""} 124 + // `} 125 + // /> 126 + // ); 127 + return ( 128 + <Link href={"/discover"} className="hover:no-underline!"> 129 + <ActionButton 130 + nav 131 + icon={<DiscoverSmall />} 132 + label="Discover" 133 + subtext={ 134 + !props.current ? "Check out what others are writing!" : undefined 135 + } 136 + className={props.current ? "bg-border-light! border-border" : ""} 137 + /> 138 + </Link> 139 + ); 140 + };
+3 -3
components/ActionBar/Sidebar.tsx
··· 27 27 <div 28 28 className={` 29 29 actionSidebar 30 - ${!props.alwaysOpen && "absolute top-0 left-0 z-10"} 31 - h-fit w-max p-[6px] 32 - flex flex-col gap-2 justify-start border 30 + ${!props.alwaysOpen ? "absolute top-0 left-0 z-10 w-max" : "w-[192px] max-w-[192px]"} 31 + h-fit p-[6px] 32 + flex flex-col gap-1 justify-start border 33 33 rounded-md bg-bg-page ${open && !props.alwaysOpen ? "border-border-light" : "container"} 34 34 ${props.className} 35 35 `}
+2 -2
components/Blocks/Block.tsx
··· 253 253 if (!BlockTypeComponent) return <div>unknown block</div>; 254 254 return ( 255 255 <div 256 - className={`blockContentWrapper w-full grow flex gap-2 z-[1] ${alignmentStyle}`} 256 + className={`blockContentWrapper w-full grow flex gap-2 z-1 ${alignmentStyle}`} 257 257 > 258 258 {props.listData && <ListMarker {...props} />} 259 259 {props.areYouSure ? ( ··· 385 385 let { rep } = useReplicache(); 386 386 return ( 387 387 <div 388 - className={`shrink-0 flex justify-end items-center h-3 z-[1] 388 + className={`shrink-0 flex justify-end items-center h-3 z-1 389 389 ${props.className} 390 390 ${ 391 391 props.type === "heading"
+1 -1
components/Blocks/BlockCommandBar.tsx
··· 124 124 `} 125 125 > 126 126 <NestedCardThemeProvider> 127 - <div className="commandMenuResults w-full max-h-[var(--radix-popover-content-available-height)] overflow-auto flex flex-col group-data-[side=top]/cmd-menu:flex-col-reverse bg-bg-page py-1 gap-0.5 border border-border rounded-md shadow-md"> 127 + <div className="commandMenuResults w-full max-h-(--radix-popover-content-available-height) overflow-auto flex flex-col group-data-[side=top]/cmd-menu:flex-col-reverse bg-bg-page py-1 gap-0.5 border border-border rounded-md shadow-md"> 128 128 {commandResults.length === 0 ? ( 129 129 <div className="w-full text-tertiary text-center italic py-2 px-2 "> 130 130 No blocks found
+3 -3
components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx
··· 31 31 className={` 32 32 overflow-hidden w-full object-cover 33 33 ${imageEmbed.images.length === 1 && "h-auto max-h-[800px]"} 34 - ${imageEmbed.images.length === 2 && "basis-1/2 aspect-1/1"} 34 + ${imageEmbed.images.length === 2 && "basis-1/2 aspect-square"} 35 35 ${imageEmbed.images.length === 3 && "basis-1/3 aspect-2/3"} 36 36 ${ 37 37 imageEmbed.images.length === 4 38 - ? "basis-1/2 aspect-[3/2]" 38 + ? "basis-1/2 aspect-3/2" 39 39 : `basis-1/${imageEmbed.images.length} ` 40 40 } 41 41 `} ··· 198 198 <a 199 199 href={props.postUrl} 200 200 target="_blank" 201 - className={`block-border flex flex-col p-3 font-normal !rounded-md border text-tertiary italic text-center hover:no-underline hover:border-accent-contrast ${props.postUrl === undefined && "pointer-events-none"} `} 201 + className={`block-border flex flex-col p-3 font-normal rounded-md! border text-tertiary italic text-center hover:no-underline hover:border-accent-contrast ${props.postUrl === undefined && "pointer-events-none"} `} 202 202 > 203 203 <div> This media is not supported... </div>{" "} 204 204 {props.postUrl === undefined ? null : (
+1 -1
components/Blocks/BlueskyPostBlock/BlueskyEmpty.tsx
··· 88 88 <Separator /> 89 89 <Input 90 90 type="text" 91 - className="w-full grow border-none outline-none bg-transparent " 91 + className="w-full grow border-none outline-hidden bg-transparent " 92 92 placeholder="bsky.app/post-url" 93 93 value={urlValue} 94 94 disabled={isLocked}
+4 -4
components/Blocks/ButtonBlock.tsx
··· 34 34 <a 35 35 href={url?.data.value} 36 36 target="_blank" 37 - className={`hover:outline-accent-contrast !rounded-md ${isSelected ? "block-border-selected !border-0" : "block-border !border-transparent !border-0"}`} 37 + className={`hover:outline-accent-contrast rounded-md! ${isSelected ? "block-border-selected border-0!" : "block-border border-transparent! border-0!"}`} 38 38 > 39 39 <ButtonPrimary role="link" type="submit"> 40 40 {text?.data.value} ··· 117 117 w-full bg-bg-page 118 118 text-tertiary hover:text-accent-contrast hover:cursor-pointer hover:p-0 119 119 flex flex-col gap-2 items-center justify-center hover:border-2 border-dashed rounded-lg 120 - ${isSelected ? "border-2 border-tertiary p-0" : "border border-border p-[1px]"} 120 + ${isSelected ? "border-2 border-tertiary p-0" : "border border-border p-px"} 121 121 `} 122 122 onSubmit={(e) => { 123 123 e.preventDefault(); ··· 169 169 <Input 170 170 type="text" 171 171 autoFocus 172 - className="w-full grow border-none outline-none bg-transparent" 172 + className="w-full grow border-none outline-hidden bg-transparent" 173 173 placeholder="button text" 174 174 value={textValue} 175 175 disabled={isLocked} ··· 192 192 <Input 193 193 type="text" 194 194 id="button-block-url-input" 195 - className="w-full grow border-none outline-none bg-transparent" 195 + className="w-full grow border-none outline-hidden bg-transparent" 196 196 placeholder="www.example.com" 197 197 value={urlValue} 198 198 disabled={isLocked}
+2 -2
components/Blocks/CodeBlock.tsx
··· 123 123 data-entityid={props.entityID} 124 124 id={elementId.block(props.entityID).input} 125 125 block={props} 126 - className="codeBlockEditor !whitespace-nowrap !overflow-auto font-mono p-2" 126 + className="codeBlockEditor whitespace-nowrap! overflow-auto! font-mono p-2" 127 127 value={content?.data.value} 128 128 onChange={async (e) => { 129 129 // Update the entity with the new value ··· 138 138 <pre 139 139 onClick={onClick} 140 140 onMouseDown={(e) => e.stopPropagation()} 141 - className="codeBlockRendered !overflow-auto font-mono p-2 w-full h-full" 141 + className="codeBlockRendered overflow-auto! font-mono p-2 w-full h-full" 142 142 > 143 143 {content?.data.value} 144 144 </pre>
+5 -5
components/Blocks/DateTimeBlock.tsx
··· 24 24 if (!isClient && !initialPageLoad) 25 25 return ( 26 26 <div 27 - className={`flex flex-row gap-2 group/date w-64 z-[1] border border-transparent`} 27 + className={`flex flex-row gap-2 group/date w-64 z-1 border border-transparent`} 28 28 > 29 29 <BlockCalendarSmall className="text-tertiary" /> 30 30 </div> ··· 119 119 return ( 120 120 <Popover 121 121 disabled={isLocked || !permissions.write} 122 - className="w-64 z-10 !px-2" 122 + className="w-64 z-10 px-2!" 123 123 trigger={ 124 124 <div 125 - className={`flex flex-row gap-2 group/date w-64 z-[1] 126 - ${isSelected ? "block-border-selected !border-transparent" : "border border-transparent"} 125 + className={`flex flex-row gap-2 group/date w-64 z-1 126 + ${isSelected ? "block-border-selected border-transparent!" : "border border-transparent"} 127 127 ${alignment === "center" ? "justify-center" : alignment === "right" ? "justify-end" : "justify-start"} 128 128 `} 129 129 > ··· 182 182 chevron: "text-inherit", 183 183 month_grid: "w-full table-fixed", 184 184 weekdays: "text-secondary text-sm", 185 - selected: "!bg-accent-1 text-accent-2 rounded-md font-bold", 185 + selected: "bg-accent-1! text-accent-2 rounded-md font-bold", 186 186 187 187 day: "h-[34px] text-center rounded-md sm:hover:bg-border-light", 188 188 outside: "text-border",
+3 -2
components/Blocks/EmbedBlock.tsx
··· 74 74 </label> 75 75 ); 76 76 } 77 + if (props.preview) return null; 77 78 78 79 return ( 79 80 <div ··· 111 112 className={`resizeHandle 112 113 cursor-ns-resize shrink-0 z-10 w-6 h-[5px] 113 114 absolute bottom-2 right-1/2 translate-x-1/2 translate-y-[2px] 114 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,_inset_0_0_0_1px_white] 115 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white] 115 116 ${isCanvasBlock ? "hidden group-hover/canvas-block:block" : ""}`} 116 117 {...heightHandle.handlers} 117 118 /> ··· 202 203 <Separator /> 203 204 <Input 204 205 type="text" 205 - className="w-full grow border-none outline-none bg-transparent " 206 + className="w-full grow border-none outline-hidden bg-transparent " 206 207 placeholder="www.example.com" 207 208 value={linkValue} 208 209 disabled={isLocked}
+4 -4
components/Blocks/ExternalLinkBlock.tsx
··· 70 70 externalLinkBlock flex relative group/linkBlock 71 71 h-[104px] w-full bg-bg-page overflow-hidden text-primary hover:no-underline no-underline 72 72 hover:border-accent-contrast shadow-sm 73 - ${isSelected ? "block-border-selected !outline-accent-contrast !border-accent-contrast" : "block-border"} 73 + ${isSelected ? "block-border-selected outline-accent-contrast! border-accent-contrast!" : "block-border"} 74 74 75 75 `} 76 76 > 77 77 <div className="pt-2 pb-2 px-3 grow min-w-0"> 78 78 <div className="flex flex-col w-full min-w-0 h-full grow "> 79 79 <div 80 - className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-none resize-none align-top border h-[24px] line-clamp-1`} 80 + className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-hidden resize-none align-top border h-[24px] line-clamp-1`} 81 81 style={{ 82 82 overflow: "hidden", 83 83 textOverflow: "ellipsis", ··· 88 88 </div> 89 89 90 90 <div 91 - className={`linkBlockDescription text-sm bg-transparent border-none outline-none resize-none align-top grow line-clamp-2`} 91 + className={`linkBlockDescription text-sm bg-transparent border-none outline-hidden resize-none align-top grow line-clamp-2`} 92 92 > 93 93 {description?.data.value} 94 94 </div> ··· 172 172 } 173 173 type="url" 174 174 disabled={isLocked} 175 - className="w-full grow border-none outline-none bg-transparent " 175 + className="w-full grow border-none outline-hidden bg-transparent " 176 176 placeholder="www.example.com" 177 177 value={linkValue} 178 178 onChange={(e) => setLinkValue(e.target.value)}
+1 -1
components/Blocks/HorizontalRule.tsx
··· 8 8 return ( 9 9 <hr 10 10 className={`my-2 w-full border-border-light 11 - ${isSelected ? "block-border-selected !outline-offset-[3px]" : ""} 11 + ${isSelected ? "block-border-selected outline-offset-[3px]!" : ""} 12 12 `} 13 13 /> 14 14 );
+4 -4
components/Blocks/ImageBlock.tsx
··· 114 114 let className = isFullBleed 115 115 ? "" 116 116 : isSelected 117 - ? "block-border-selected !border-transparent " 118 - : "block-border !border-transparent"; 117 + ? "block-border-selected border-transparent! " 118 + : "block-border border-transparent!"; 119 119 120 120 let isLocalUpload = localImages.get(image.data.src); 121 121 ··· 156 156 export const FullBleedSelectionIndicator = () => { 157 157 return ( 158 158 <div 159 - className={`absolute top-3 sm:top-4 bottom-3 sm:bottom-4 left-3 sm:left-4 right-3 sm:right-4 border-2 border-bg-page rounded-lg outline-offset-1 outline outline-2 outline-tertiary`} 159 + className={`absolute top-3 sm:top-4 bottom-3 sm:bottom-4 left-3 sm:left-4 right-3 sm:right-4 border-2 border-bg-page rounded-lg outline-offset-1 outline-solid outline-2 outline-tertiary`} 160 160 /> 161 161 ); 162 162 }; ··· 194 194 > 195 195 {entity_set.permissions.write ? ( 196 196 <AsyncValueAutosizeTextarea 197 - className="text-sm text-secondary outline-none bg-transparent min-w-0" 197 + className="text-sm text-secondary outline-hidden bg-transparent min-w-0" 198 198 value={altText} 199 199 onFocus={(e) => { 200 200 e.currentTarget.setSelectionRange(
+2 -2
components/Blocks/MailboxBlock.tsx
··· 316 316 <input 317 317 type="number" 318 318 value={code} 319 - className="appearance-none focus:outline-none focus:border-border w-20 border border-border-light bg-bg-page rounded-md p-1" 319 + className="appearance-none focus:outline-hidden focus:border-border w-20 border border-border-light bg-bg-page rounded-md p-1" 320 320 onChange={(e) => setCode(e.currentTarget.value)} 321 321 /> 322 322 ··· 358 358 value={email} 359 359 type="email" 360 360 onChange={(e) => setEmail(e.target.value)} 361 - className="w-full appearance-none focus:outline-none bg-transparent" 361 + className="w-full appearance-none focus:outline-hidden bg-transparent" 362 362 placeholder="youremail@email.com" 363 363 /> 364 364 </div>
+1 -1
components/Blocks/MathBlock.tsx
··· 35 35 <BaseTextareaBlock 36 36 id={elementId.block(props.entityID).input} 37 37 block={props} 38 - className="bg-border-light rounded-md p-2 w-full min-h-[48px] whitespace-nowrap !overflow-auto border-border-light outline-border-light selected-outline" 38 + className="bg-border-light rounded-md p-2 w-full min-h-[48px] whitespace-nowrap overflow-auto! border-border-light outline-border-light selected-outline" 39 39 placeholder="write some Tex here..." 40 40 value={content?.data.value} 41 41 onChange={async (e) => {
+8 -8
components/Blocks/PageLinkBlock.tsx
··· 35 35 bg-bg-page shadow-sm 36 36 flex overflow-clip 37 37 ${isSelected ? "block-border-selected " : "block-border"} 38 - ${isOpen && "!border-tertiary"} 38 + ${isOpen && "border-tertiary!"} 39 39 `} 40 40 onClick={(e) => { 41 41 if (!page) return; ··· 84 84 <div className="my-2 ml-3 grow min-w-0 text-sm bg-transparent overflow-clip "> 85 85 {leafletMetadata[0] && ( 86 86 <div 87 - className={`pageBlockOne outline-none resize-none align-top flex gap-2 ${leafletMetadata[0].type === "heading" ? "font-bold text-base" : ""}`} 87 + className={`pageBlockOne outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[0].type === "heading" ? "font-bold text-base" : ""}`} 88 88 > 89 89 {leafletMetadata[0].listData && ( 90 90 <ListMarker 91 91 {...leafletMetadata[0]} 92 92 className={ 93 93 leafletMetadata[0].type === "heading" 94 - ? "!pt-[12px]" 95 - : "!pt-[8px]" 94 + ? "pt-[12px]!" 95 + : "pt-[8px]!" 96 96 } 97 97 /> 98 98 )} ··· 104 104 )} 105 105 {leafletMetadata[1] && ( 106 106 <div 107 - className={`pageBlockLineTwo outline-none resize-none align-top flex gap-2 ${leafletMetadata[1].type === "heading" ? "font-bold" : ""}`} 107 + className={`pageBlockLineTwo outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[1].type === "heading" ? "font-bold" : ""}`} 108 108 > 109 109 {leafletMetadata[1].listData && ( 110 - <ListMarker {...leafletMetadata[1]} className="!pt-[8px]" /> 110 + <ListMarker {...leafletMetadata[1]} className="pt-[8px]!" /> 111 111 )} 112 112 <RenderedTextBlock 113 113 entityID={leafletMetadata[1].value} ··· 117 117 )} 118 118 {leafletMetadata[2] && ( 119 119 <div 120 - className={`pageBlockLineThree outline-none resize-none align-top flex gap-2 ${leafletMetadata[2].type === "heading" ? "font-bold" : ""}`} 120 + className={`pageBlockLineThree outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[2].type === "heading" ? "font-bold" : ""}`} 121 121 > 122 122 {leafletMetadata[2].listData && ( 123 - <ListMarker {...leafletMetadata[2]} className="!pt-[8px]" /> 123 + <ListMarker {...leafletMetadata[2]} className="pt-[8px]!" /> 124 124 )} 125 125 <RenderedTextBlock 126 126 entityID={leafletMetadata[2].value}
+2 -2
components/Blocks/QuoteEmbedBlock.tsx
··· 39 39 </div> 40 40 <hr className="border-border-light" /> 41 41 <a 42 - className="quoteEmbedFooter flex max-w-full gap-2 px-3 py-2 hover:!no-underline text-secondary" 42 + className="quoteEmbedFooter flex max-w-full gap-2 px-3 py-2 hover:no-underline! text-secondary" 43 43 href="#" 44 44 > 45 45 <div className="flex flex-col w-[calc(100%-28px)] grow"> ··· 52 52 <div>celine</div> 53 53 </div> 54 54 </div> 55 - <div className=" shrink-0 pt-[1px] bg-test w-5 h-5 rounded-full"></div> 55 + <div className=" shrink-0 pt-px bg-test w-5 h-5 rounded-full"></div> 56 56 </a> 57 57 </div> 58 58 );
+3 -3
components/Blocks/RSVPBlock/ContactDetailsForm.tsx
··· 25 25 }) { 26 26 let { status, entityID, setState, setStatus } = props; 27 27 let focusWithinStyles = 28 - "focus-within:border-tertiary focus-within:outline focus-within:outline-2 focus-within:outline-tertiary focus-within:outline-offset-1"; 28 + "focus-within:border-tertiary focus-within:outline-solid focus-within:outline-2 focus-within:outline-tertiary focus-within:outline-offset-1"; 29 29 let toaster = useToaster(); 30 30 let { data, mutate } = useRSVPData(); 31 31 let [contactFormState, setContactFormState] = useState< ··· 212 212 </div> 213 213 <div className="flex flex-row gap-2 w-full sm:w-32 h-fit"> 214 214 <InputWithLabel 215 - className="!appearance-none" 215 + className="appearance-none!" 216 216 placeholder="0" 217 217 label="Plus ones?" 218 218 type="number" ··· 330 330 <Input 331 331 autoFocus 332 332 placeholder="000000" 333 - className="input-with-border !pt-5 w-full " 333 + className="input-with-border pt-5! w-full " 334 334 value={props.value} 335 335 autoComplete="one-time-code" 336 336 onChange={(e) => props.onChange(e.target.value)}
+3 -3
components/Blocks/RSVPBlock/SendUpdate.tsx
··· 134 134 </small> */} 135 135 <div className="flex gap-4 text-secondary"> 136 136 <Checkbox 137 - className="!w-fit" 137 + className="w-fit!" 138 138 checked={props.checked.GOING} 139 139 onChange={() => { 140 140 props.setChecked({ ··· 146 146 Going 147 147 </Checkbox> 148 148 <Checkbox 149 - className="!w-fit" 149 + className="w-fit!" 150 150 checked={props.checked.MAYBE} 151 151 onChange={() => { 152 152 props.setChecked({ ··· 158 158 Maybe 159 159 </Checkbox> 160 160 <Checkbox 161 - className="!w-fit" 161 + className="w-fit!" 162 162 checked={props.checked.NOT_GOING} 163 163 onChange={() => { 164 164 props.setChecked({
+6 -6
components/Blocks/RSVPBlock/index.tsx
··· 119 119 return ( 120 120 <div className="relative w-full sm:p-6 py-4 px-3 rounded-md border-[1.5px] border-accent-1"> 121 121 <RSVPBackground /> 122 - <div className="relative flex flex-row gap-2 items-center mx-auto z-[1] w-fit"> 122 + <div className="relative flex flex-row gap-2 items-center mx-auto z-1 w-fit"> 123 123 <ButtonSecondary 124 124 type="button" 125 125 className={ 126 126 props.status === "MAYBE" 127 - ? "!text-accent-2 !bg-accent-1 text-lg" 127 + ? "text-accent-2! bg-accent-1! text-lg" 128 128 : "" 129 129 } 130 130 onClick={() => props.setStatus("MAYBE")} ··· 135 135 type="button" 136 136 className={ 137 137 props.status === "GOING" 138 - ? "!text-accent-2 !bg-accent-1 text-lg" 138 + ? "text-accent-2! bg-accent-1! text-lg" 139 139 : props.status === undefined 140 140 ? "text-lg" 141 141 : "" ··· 149 149 type="button" 150 150 className={ 151 151 props.status === "NOT_GOING" 152 - ? "!text-accent-2 !bg-accent-1 text-lg" 152 + ? "text-accent-2! bg-accent-1! text-lg" 153 153 : "" 154 154 } 155 155 onClick={() => props.setStatus("NOT_GOING")} ··· 207 207 className={`relative w-full p-4 pb-5 rounded-md border-[1.5px] border-accent-1 font-bold items-center`} 208 208 > 209 209 <RSVPBackground /> 210 - <div className=" relative flex flex-col gap-1 sm:gap-2 z-[1] justify-center w-fit mx-auto"> 210 + <div className=" relative flex flex-col gap-1 sm:gap-2 z-1 justify-center w-fit mx-auto"> 211 211 <div 212 212 className=" w-fit text-xl text-center text-accent-2" 213 213 style={{ ··· 225 225 </div> 226 226 {existingRSVP?.plus_ones && existingRSVP?.plus_ones > 0 ? ( 227 227 <div className="absolute -top-2 -right-6 rotate-12 h-fit w-10 bg-accent-1 font-bold text-accent-2 rounded-full -z-10"> 228 - <div className="w-full text-center pr-[4px] pb-[1px]"> 228 + <div className="w-full text-center pr-[4px] pb-px"> 229 229 +{existingRSVP?.plus_ones} 230 230 </div> 231 231 </div>
+2 -2
components/Blocks/TextBlock/index.tsx
··· 167 167 ${alignmentClass} 168 168 ${props.type === "blockquote" ? " blockquote " : ""} 169 169 ${props.type === "heading" ? HeadingStyle[headingLevel?.data.value || 1] : ""} 170 - w-full whitespace-pre-wrap outline-none ${props.className} `} 170 + w-full whitespace-pre-wrap outline-hidden ${props.className} `} 171 171 > 172 172 {content} 173 173 </div> ··· 379 379 className={` 380 380 ${alignmentClass} 381 381 grow resize-none align-top whitespace-pre-wrap bg-transparent 382 - outline-none 382 + outline-hidden 383 383 384 384 ${props.type === "heading" ? HeadingStyle[headingLevel?.data.value || 1] : ""} 385 385 ${props.className}`}
+1 -1
components/Blocks/index.tsx
··· 95 95 96 96 return ( 97 97 <div 98 - className={`blocks w-full flex flex-col outline-none h-fit min-h-full`} 98 + className={`blocks w-full flex flex-col outline-hidden h-fit min-h-full`} 99 99 onClick={async (e) => { 100 100 if (!permissions.write) return; 101 101 if (useUIState.getState().selectedBlocks.length > 1) return;
+5 -5
components/Canvas.tsx
··· 55 55 canvasWrapper 56 56 h-full w-fit mx-auto 57 57 max-w-[calc(100vw-12px)] 58 - ${!narrowWidth ? "sm:max-w-[calc(100vw-128px)] lg:max-w-[calc(var(--page-width-units)*2 + 24px))]" : " sm:max-w-[var(--page-width-units)]"} 58 + ${!narrowWidth ? "sm:max-w-[calc(100vw-128px)] lg:max-w-[calc(var(--page-width-units)*2 + 24px))]" : " sm:max-w-(--page-width-units)"} 59 59 rounded-lg 60 60 overflow-y-scroll 61 61 `} ··· 161 161 ${canvasFocused ? "sm:block hidden" : "hidden"} 162 162 w-[8px] h-12 163 163 absolute top-1/2 right-0 -translate-y-1/2 translate-x-[3px] 164 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,_inset_0_0_0_1px_white]`} 164 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white]`} 165 165 /> 166 166 ); 167 167 } ··· 186 186 <div className="font-normal">or double click anywhere</div> 187 187 </div> 188 188 } 189 - className="w-fit p-2 rounded-full bg-accent-1 border-2 outline outline-transparent hover:outline-1 hover:outline-accent-1 border-accent-1 text-accent-2" 189 + className="w-fit p-2 rounded-full bg-accent-1 border-2 outline-solid outline-transparent hover:outline-1 hover:outline-accent-1 border-accent-1 text-accent-2" 190 190 onMouseDown={() => { 191 191 let page = document.getElementById( 192 192 elementId.page(props.entityID).canvasScrollArea, ··· 395 395 hidden group-hover/canvas-block:block 396 396 w-[5px] h-6 -ml-[3px] 397 397 absolute top-1/2 right-3 -translate-y-1/2 translate-x-[2px] 398 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,_inset_0_0_0_1px_white]`} 398 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white]`} 399 399 {...widthHandle.handlers} 400 400 /> 401 401 )} ··· 408 408 w-[8px] h-[8px] 409 409 absolute bottom-0 -right-0 410 410 -translate-y-1/2 -translate-x-1/2 411 - rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,_inset_0_0_0_1px_white]`} 411 + rounded-full bg-white border-2 border-[#8C8C8C] shadow-[0_0_0_1px_white,inset_0_0_0_1px_white]`} 412 412 {...rotateHandle.handlers} 413 413 /> 414 414 )}
+19
components/Icons/DiscoverSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const DiscoverSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + {...props} 12 + > 13 + <path 14 + d="M9.64065 3.31253C10.1895 3.0803 10.794 2.99599 11.3799 3.11722C14.3589 4.07474 17.3532 4.98462 20.3341 5.93656C22.634 6.67144 23.6052 9.42405 22.8126 11.9073C22.0195 14.3908 19.6316 16.0723 17.3311 15.3379C16.8984 15.1997 16.5126 14.9905 16.1768 14.7247C16.2579 15.9524 15.9531 17.2605 15.2559 18.4239C13.6542 21.0957 10.4061 22.3031 7.94044 20.8253C7.20913 20.3868 6.73474 19.7706 6.25977 19.085L1.73143 12.5323C1.40407 11.9161 1.28828 11.19 1.33104 10.4981C0.939931 10.5372 0.579602 10.2601 0.515604 9.86137C0.421342 9.27278 0.528354 8.61795 0.81248 8.01957C1.09245 7.43014 1.52027 6.94541 2.02049 6.65531C2.51666 6.36761 3.16289 6.23639 3.76953 6.53715C3.9583 6.63077 4.0903 6.79455 4.15234 6.98148C4.89188 6.80551 5.63743 6.86056 6.22462 7.03226L6.47657 7.11625L6.48731 7.12113L6.87599 7.28422C6.98277 7.18686 7.11194 7.07056 7.25587 6.94828C7.38624 6.83753 7.53576 6.71687 7.69337 6.59574C7.39699 6.57978 7.14544 6.35553 7.10743 6.05082C7.01872 5.33128 7.22912 4.62394 7.58009 4.0928C7.91814 3.58141 8.47646 3.10981 9.16018 3.09671C9.352 3.09317 9.5235 3.17857 9.64065 3.31253ZM13.1006 11.8116C11.3913 10.9381 8.98548 11.7132 7.68361 13.8848C6.33504 16.135 6.867 18.7239 8.58302 19.753C10.2995 20.7818 12.8357 20.0316 14.1846 17.7813C15.4848 15.6117 15.035 13.1269 13.4629 12.0293L13.1006 11.8116ZM8.62502 14.6944C9.53877 12.9 11.3932 12.0126 12.7666 12.712C14.1398 13.4115 14.3354 15.6904 13.5987 17.2266C12.8619 18.7627 10.8315 19.9088 9.45803 19.21C8.08459 18.5107 7.71138 16.4887 8.62502 14.6944ZM12.8252 15.6583C12.5605 15.5862 12.2843 15.7437 12.209 16.0108C11.9544 16.9164 11.348 17.5593 10.5908 17.7364C10.322 17.7993 10.1517 18.0692 10.211 18.3389C10.2705 18.6083 10.537 18.7758 10.8057 18.7129C11.998 18.434 12.8376 17.4515 13.169 16.2735C13.244 16.0063 13.0899 15.7308 12.8252 15.6583ZM5.85547 8.2266C4.97336 7.96914 3.68282 8.11567 2.97851 9.28324C2.35667 10.3146 2.57612 11.438 2.80858 11.8916L5.69629 16.0713C5.77272 15.1107 6.07668 14.1333 6.61134 13.2413C7.48409 11.7856 8.84564 10.7657 10.3028 10.377L9.4844 9.98149L9.47951 9.97954L8.30763 9.40043C8.2637 9.3787 8.22477 9.35022 8.18849 9.32035L6.74317 8.58402L6.02637 8.28324L5.85547 8.2266ZM12.9815 13.7989C12.8162 13.5797 12.5043 13.5386 12.2842 13.7071C12.0642 13.8761 12.0193 14.1909 12.1846 14.4102C12.2116 14.446 12.2398 14.4947 12.2637 14.5479C12.2881 14.6022 12.3012 14.6467 12.3057 14.669C12.3597 14.9397 12.6218 15.112 12.8916 15.0547C13.1615 14.9973 13.3369 14.7318 13.2832 14.461C13.2419 14.2534 13.1318 13.9983 12.9815 13.7989ZM19.9542 7.12699C18.5161 6.66773 16.6983 7.69499 16.043 9.74614C15.388 11.7975 16.2739 13.6882 17.712 14.1475C19.1501 14.6063 20.9671 13.5785 21.6222 11.5274C22.2768 9.47656 21.3918 7.58669 19.9542 7.12699ZM16.9376 10.0362C17.4516 8.5672 18.7799 7.69494 19.9044 8.08793C21.029 8.48141 21.4151 10.2077 21.0098 11.461C20.6045 12.7142 19.1677 13.8027 18.043 13.4092C16.9185 13.0156 16.4235 11.5054 16.9376 10.0362ZM20.253 10.3516C20.0491 10.3247 19.8601 10.4698 19.8311 10.6758C19.7294 11.3989 19.329 11.9579 18.7628 12.1739C18.5695 12.2476 18.4703 12.4649 18.5411 12.6592C18.6122 12.8536 18.8262 12.9515 19.0196 12.878C19.8995 12.5426 20.4401 11.7072 20.5714 10.7735C20.6 10.5675 20.457 10.3787 20.253 10.3516ZM11.0713 4.33011C10.6841 4.25272 10.0696 4.38611 9.6094 4.78128C9.32981 5.02143 9.10865 5.35827 9.05568 5.81449C9.06779 5.81012 9.07976 5.80506 9.09182 5.80082C9.4432 5.67716 9.78179 5.59962 10.0293 5.55277C10.1539 5.52919 10.2579 5.51245 10.3321 5.50199C10.4893 5.47983 10.638 5.47119 10.7901 5.543L13.6914 6.91312C13.8423 6.98455 13.9584 7.11404 14.0137 7.27152C14.0689 7.42905 14.0586 7.60294 13.9854 7.75297C13.5962 8.55099 13.3649 9.42277 13.3233 10.4864L13.7012 10.7139C13.7767 10.7535 13.8517 10.7955 13.9258 10.8399C14.1641 10.9827 14.3832 11.1444 14.584 11.3213C14.5588 10.678 14.6459 10.0123 14.8526 9.36528C15.2838 8.01572 16.186 6.9037 17.2891 6.28714L11.0713 4.33011ZM20.1739 8.90727C20.0289 8.76099 19.7921 8.76247 19.6456 8.9102C19.4996 9.05792 19.4981 9.29617 19.6426 9.44242C19.6677 9.46772 19.6956 9.50228 19.7198 9.54106C19.7443 9.58047 19.7586 9.61417 19.7647 9.63188C19.832 9.82743 20.0447 9.9297 20.2393 9.86039C20.4341 9.79056 20.5388 9.5748 20.4718 9.37895C20.4192 9.22583 20.3085 9.04328 20.1739 8.90727ZM12.3799 8.45512C12.0926 8.54994 11.8161 8.64566 11.6192 8.72856C11.4367 8.80543 11.1533 9.01215 10.8604 9.25785L12.1123 9.86332C12.1628 9.36625 12.2524 8.8992 12.3799 8.45512ZM10.2617 6.78129C10.0537 6.82066 9.78049 6.8842 9.50686 6.98051C9.39451 7.02005 9.28445 7.06426 9.18068 7.11332C8.87141 7.25956 8.46403 7.565 8.09865 7.87309L9.64748 8.66215C9.73004 8.58613 9.82307 8.4994 9.92483 8.41117C10.2477 8.13123 10.7217 7.75005 11.1348 7.57621C11.2995 7.50692 11.5001 7.43309 11.71 7.36039L10.4268 6.75297C10.3799 6.7604 10.324 6.7695 10.2617 6.78129Z" 15 + fill="currentColor" 16 + /> 17 + </svg> 18 + ); 19 + };
+18
components/Icons/MenuSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const MenuSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M19.9541 16.6455C20.6444 16.6455 21.2039 17.2053 21.2041 17.8955C21.2041 18.5859 20.6445 19.1455 19.9541 19.1455H4.04492C3.35476 19.1453 2.79492 18.5857 2.79492 17.8955C2.79509 17.2054 3.35486 16.6457 4.04492 16.6455H19.9541ZM19.9541 10.75C20.6444 10.75 21.2041 11.3097 21.2041 12C21.2041 12.6904 20.6445 13.25 19.9541 13.25H4.04492C3.35476 13.2498 2.79492 12.6902 2.79492 12C2.79495 11.3098 3.35478 10.7502 4.04492 10.75H19.9541ZM19.9541 4.85449C20.6445 4.85449 21.2041 5.41414 21.2041 6.10449C21.2039 6.79471 20.6444 7.35449 19.9541 7.35449H4.04492C3.35486 7.35426 2.79509 6.79457 2.79492 6.10449C2.79492 5.41428 3.35476 4.85472 4.04492 4.85449H19.9541Z" 14 + fill="currentColor" 15 + /> 16 + </svg> 17 + ); 18 + };
+55
components/Icons/NotificationSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const NotificationsUnreadSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + {...props} 12 + > 13 + <path 14 + d="M14.3024 1.43058C14.4867 1.24672 15.1979 0.543997 16.3942 0.836833C17.357 1.07263 17.7754 1.74014 17.9362 2.2255C18.1981 3.01633 18.0108 3.8152 17.82 4.33975C17.7969 4.40332 17.7727 4.46469 17.7487 4.52334C18.9071 5.32788 19.6985 6.48773 20.1706 7.88075C20.8486 9.8818 20.8697 12.3569 20.4616 14.9862C20.6157 15.1662 20.7567 15.3499 20.8825 15.537C21.5041 16.4613 21.7973 17.5195 21.4958 18.5887C21.0327 20.23 19.3745 21.253 17.4616 21.7391C17.2568 21.7911 17.0461 21.8384 16.8307 21.8797C16.1912 22.7044 15.1828 23.234 14.0554 23.2342C12.9086 23.2342 11.8844 22.6865 11.2468 21.8377C11.011 21.7957 10.774 21.7481 10.5368 21.6961C8.02478 21.1453 5.85985 20.1213 4.3962 18.8748C2.96429 17.6553 2.04288 16.066 2.50558 14.4247C2.89584 13.0411 4.13541 12.0966 5.66085 11.5428C6.53657 8.84034 7.70841 6.59211 9.27803 5.12978C10.4535 4.03476 11.8336 3.39971 13.4079 3.32608C13.4153 3.27496 13.4236 3.22252 13.4333 3.16983C13.5309 2.64013 13.7678 1.96392 14.3024 1.43058ZM13.3718 16.1415C13.1138 16.038 12.8422 16.1672 12.7653 16.4305C12.65 16.8259 12.6911 17.3483 12.8786 17.9227C12.4745 18.1673 12.1568 18.5345 11.9772 18.9686C11.872 19.2231 11.8142 19.501 11.8142 19.7909C11.8142 20.0147 11.8486 20.2312 11.9128 20.4354C12.1933 21.3271 13.0404 21.9842 14.0554 21.9842C15.0444 21.984 15.8734 21.3599 16.1745 20.5028C16.2531 20.279 16.2965 20.0395 16.2966 19.7909C16.2966 19.55 16.2562 19.3172 16.1823 19.0995C15.8876 18.2313 15.0526 17.5968 14.0554 17.5965C13.9626 17.5965 13.8706 17.6025 13.7809 17.6131C13.6611 17.222 13.6602 16.9421 13.6999 16.8055C13.7767 16.5423 13.6295 16.245 13.3718 16.1415ZM15.1657 5.00186C13.1827 4.51697 11.6095 5.00028 10.3005 6.21962C9.15557 7.28623 8.18904 8.94185 7.40108 11.0917C9.19338 10.785 11.3088 10.8436 13.4645 11.3163C13.869 11.4051 14.1062 11.8005 13.9938 12.1991C13.8813 12.5975 13.4617 12.8482 13.0573 12.7596C10.7306 12.2495 8.52421 12.2783 6.85225 12.703C5.13792 13.1386 4.20347 13.9198 3.97042 14.746C3.73772 15.5721 4.14841 16.6474 5.43136 17.7401C6.61793 18.7506 8.41814 19.6448 10.5847 20.1698C10.5709 20.0454 10.5642 19.919 10.5642 19.7909C10.5642 19.4052 10.6278 19.0346 10.7458 18.6893C7.80726 17.9008 5.76926 16.4462 6.09347 15.2967C6.44449 14.0544 9.42306 13.6382 12.7468 14.3671C16.0704 15.0959 18.4806 16.6938 18.1305 17.9364C18.0403 18.2562 17.7761 18.5215 17.3766 18.7264C17.4866 19.061 17.5466 19.4191 17.5466 19.7909C17.5465 19.9314 17.5377 20.0699 17.5212 20.2059C18.9994 19.7567 19.8152 19.0317 20.0309 18.2674C20.1833 17.727 20.0672 17.0908 19.5964 16.3905C19.1243 15.6887 18.3265 14.976 17.2477 14.3446C16.8964 14.139 16.798 13.6857 17.028 13.3329C17.2581 12.9804 17.7296 12.8608 18.0807 13.0663C18.4468 13.2805 18.7922 13.5085 19.113 13.7469C19.3429 11.6614 19.239 9.82077 18.7467 8.36805C18.1614 6.64093 17.0317 5.45828 15.1657 5.00186ZM16.6081 10.7997C16.8481 10.4428 17.332 10.348 17.6891 10.5878C18.0462 10.8277 18.1416 11.3116 17.902 11.6688C17.662 12.0261 17.1773 12.1217 16.82 11.8817C16.4628 11.6417 16.3681 11.1569 16.6081 10.7997ZM15.3757 6.62001C15.6081 6.27939 16.0775 6.19011 16.4235 6.42079C17.4996 7.13865 17.8434 8.29284 17.9235 9.08582C17.9646 9.4953 17.6614 9.85914 17.2468 9.89832C16.8322 9.93727 16.4621 9.6368 16.4206 9.22742C16.3626 8.65359 16.1303 8.01964 15.5827 7.65419C15.2369 7.42351 15.1438 6.96063 15.3757 6.62001ZM6.36202 2.73819C6.67743 2.46989 7.15121 2.50785 7.41963 2.82315C7.68787 3.13856 7.64995 3.61236 7.33467 3.88077C6.43652 4.64508 5.56938 5.65156 4.96652 7.31435C4.82536 7.70365 4.39491 7.90455 4.00558 7.76356C3.61644 7.62248 3.41476 7.19281 3.55539 6.80361C4.26248 4.85333 5.30341 3.6391 6.36202 2.73819ZM4.32003 1.66593C4.64351 1.40808 5.11532 1.46006 5.37374 1.78312C5.6321 2.1067 5.57904 2.58027 5.25558 2.83878C4.77905 3.21946 4.42558 3.58659 4.0466 4.23819C3.83836 4.59583 3.37909 4.71745 3.02121 4.50967C2.6633 4.30142 2.54164 3.8413 2.74972 3.48331C3.23405 2.65069 3.7184 2.14651 4.32003 1.66593ZM16.0261 2.29093C15.808 2.23756 15.6732 2.27495 15.5895 2.31339C15.491 2.35871 15.4235 2.42393 15.362 2.48526C15.1228 2.72383 14.9801 3.06822 14.9138 3.41983C15.1176 3.45367 15.3245 3.49655 15.5339 3.54776C15.8419 3.62311 16.1355 3.71434 16.4137 3.8212C16.5569 3.42086 16.6124 3.01823 16.5085 2.70401C16.4821 2.62444 16.44 2.54283 16.3747 2.47452C16.3146 2.4117 16.2132 2.33677 16.0261 2.29093Z" 15 + fill="currentColor" 16 + /> 17 + </svg> 18 + ); 19 + }; 20 + 21 + export const NotificationsReadSmall = (props: Props) => { 22 + return ( 23 + <svg 24 + width="24" 25 + height="24" 26 + viewBox="0 0 24 24" 27 + fill="none" 28 + xmlns="http://www.w3.org/2000/svg" 29 + {...props} 30 + > 31 + <path 32 + d="M2.40963 18.0472C2.40983 17.3641 2.99636 17.3642 2.99655 18.0472C2.99654 18.7307 3.28805 19.1528 3.38815 19.263C3.48823 19.3732 3.66105 19.7179 4.28463 19.7181C4.90847 19.7182 4.90847 20.3255 4.28463 20.3255C3.66107 20.3256 3.56221 20.5899 3.38815 20.7816C3.21412 20.9732 2.99855 21.2572 2.9985 21.9232C2.9985 22.5896 2.41159 22.6066 2.41159 21.9232C2.41151 21.2398 2.04325 20.8276 2.00143 20.7816C1.959 20.735 1.52689 20.3255 1.03854 20.3255C0.549966 20.3254 0.554718 19.7182 1.03854 19.7181C1.52186 19.7181 1.83806 19.3644 1.93014 19.263C2.02172 19.1622 2.40963 18.7307 2.40963 18.0472ZM18.3989 13.7962C18.3991 13.1543 18.8958 13.1543 18.896 13.7962C18.896 14.4386 19.1424 14.8352 19.227 14.9388C19.3117 15.0425 19.4577 15.3663 19.9848 15.3665C20.5125 15.3666 20.5125 15.9378 19.9848 15.9378C19.4575 15.9379 19.3742 16.1864 19.227 16.3665C19.0798 16.5466 18.897 16.8131 18.8969 17.4388C18.8969 18.0651 18.4008 18.0811 18.4008 17.4388C18.4007 16.7986 18.0911 16.4117 18.0542 16.3665C18.0188 16.3233 17.653 15.9381 17.2397 15.9378C16.8263 15.9378 16.8303 15.3665 17.2397 15.3665C17.6486 15.3663 17.916 15.0338 17.9936 14.9388C18.0711 14.844 18.3989 14.4386 18.3989 13.7962ZM5.68893 1.79134C6.32061 1.54967 6.8992 1.76772 7.31002 2.04427C7.71467 2.31686 8.06882 2.71494 8.35299 3.07649C8.63964 3.44125 8.90666 3.83827 9.10299 4.11653C9.3417 4.45493 9.26161 4.92361 8.9233 5.16243C8.58506 5.40111 8.1173 5.31974 7.87838 4.98177C7.65546 4.66583 7.42734 4.32673 7.17233 4.00227C6.91507 3.67506 6.67898 3.42822 6.47311 3.28938C6.3745 3.22296 6.30685 3.19837 6.27096 3.19075C6.24474 3.18528 6.23525 3.18881 6.22506 3.1927C6.18423 3.20859 5.9833 3.32114 5.7192 3.88899C5.59862 4.14843 5.29699 5.01402 4.93209 6.10677C4.85847 6.12987 4.80511 6.14661 4.79831 6.15462C4.79485 6.15884 4.79121 6.16214 4.78854 6.16536C4.4966 6.19308 4.21288 6.31734 3.99459 6.54427C3.75682 6.79162 3.64898 7.10768 3.64791 7.40462C3.64696 7.70171 3.75272 8.02065 3.99264 8.27083C4.04882 8.32937 4.1096 8.38044 4.1733 8.42513C3.95587 9.09776 3.75091 9.74532 3.5776 10.2884C3.85066 10.3144 4.12174 10.3779 4.38034 10.4691C5.16136 10.7446 5.92693 11.3005 6.54928 12.0501C6.71967 11.9646 6.90306 11.9158 7.08932 11.8929C7.52605 11.8392 7.97349 11.9251 8.39889 12.1146C8.52073 11.5922 8.77238 11.1032 9.17428 10.6868C10.661 9.14683 13.2111 9.62241 14.8227 11.1781C14.9553 11.306 15.0799 11.4413 15.1977 11.5814C15.3522 11.0931 15.5412 10.4983 15.7387 9.87044C16.2338 8.29641 16.7876 6.52428 17.0298 5.71028C17.333 4.69126 17.7647 3.91964 18.3823 3.49056C19.0569 3.02218 19.7993 3.06179 20.4389 3.34017C21.047 3.60494 21.6053 4.09649 22.0825 4.63802C22.5684 5.18942 23.0142 5.84393 23.3862 6.50032C23.59 6.86052 23.4639 7.31757 23.104 7.52181C22.7438 7.72592 22.2859 7.59954 22.0815 7.23958C21.7486 6.6521 21.3605 6.08653 20.9575 5.62923C20.546 5.16239 20.1597 4.85528 19.8403 4.71614C19.5524 4.5908 19.3833 4.62119 19.2368 4.72298C19.0332 4.86476 18.7324 5.24367 18.4663 6.13802C18.2201 6.9656 17.6633 8.74963 17.1694 10.3197C16.922 11.1061 16.6893 11.8418 16.519 12.3802C16.4339 12.6494 16.3652 12.87 16.3169 13.0228C16.2928 13.0988 16.274 13.1581 16.2612 13.1986C16.2548 13.2188 16.2489 13.235 16.2456 13.2454C16.2441 13.2501 16.2425 13.2536 16.2417 13.2562V13.2591L16.2407 13.2601C16.2268 13.3038 16.2077 13.3449 16.187 13.3841C16.5566 14.5982 16.4051 15.8801 15.5122 16.805C14.0254 18.345 11.4753 17.8696 9.86373 16.3138C9.18414 15.6576 8.69133 14.8276 8.45944 13.972C8.05454 13.5525 7.64913 13.397 7.38424 13.3831C7.59712 13.8598 7.7328 14.345 7.7778 14.8138C7.86052 15.6775 7.64387 16.5969 6.91061 17.1702C6.17739 17.7433 5.23363 17.7322 4.41549 17.4437C3.58744 17.1515 2.77404 16.5464 2.13327 15.7269C1.49254 14.9074 1.10184 13.972 1.01803 13.098C0.936885 12.25 1.1447 11.3498 1.84616 10.7747C2.09807 9.97534 2.5868 8.4405 3.06979 6.96224C3.59658 5.34994 4.14583 3.71844 4.3608 3.25618C4.68344 2.56265 5.10288 2.01575 5.68893 1.79134ZM13.7807 12.2572C12.4881 11.0094 10.9295 11.0284 10.2534 11.7288C9.57757 12.4294 9.61347 13.987 10.9057 15.2347C12.1981 16.4823 13.7567 16.464 14.4331 15.764C15.1092 15.0636 15.0731 13.505 13.7807 12.2572ZM3.88229 11.8841C3.3528 11.6973 2.99993 11.7759 2.80905 11.9251C2.61843 12.0746 2.45867 12.3971 2.51217 12.9554C2.5648 13.5041 2.82326 14.1742 3.31491 14.8031C3.8066 15.4319 4.39472 15.8452 4.91452 16.0286C5.44348 16.2152 5.79586 16.1376 5.98678 15.9886C6.17756 15.8394 6.33808 15.5159 6.28463 14.9574C6.23201 14.4087 5.97256 13.7385 5.48092 13.1097C4.98934 12.481 4.40201 12.0676 3.88229 11.8841ZM11.9194 14.7786C12.0925 14.5637 12.4074 14.5295 12.6225 14.7025C12.661 14.7334 12.7453 14.7848 12.8491 14.8284C12.8979 14.849 12.9439 14.8642 12.9819 14.8743C13.0173 14.8837 13.0359 14.8858 13.0395 14.8861C13.3156 14.8914 13.5361 15.1197 13.5307 15.3958C13.5253 15.6718 13.2969 15.8912 13.021 15.8861C12.823 15.8822 12.6163 15.8145 12.4614 15.7493C12.2997 15.6813 12.1271 15.5876 11.9956 15.4818C11.7806 15.3087 11.7465 14.9937 11.9194 14.7786ZM3.50924 12.1361L3.57955 12.1497L3.6733 12.1878C3.88055 12.2932 3.99317 12.533 3.92916 12.7659C3.90752 12.8445 3.8653 12.9118 3.81393 12.9681C3.81694 12.9884 3.81922 13.0124 3.82467 13.0384C3.87694 13.2878 4.02405 13.6447 4.28561 13.9671C4.45941 14.1814 4.42741 14.4972 4.21334 14.6712C3.99906 14.8451 3.68332 14.812 3.50924 14.598C3.14412 14.1481 2.92999 13.6431 2.84616 13.2425C2.80618 13.0513 2.78536 12.8362 2.82565 12.6468C2.84499 12.5561 2.89269 12.4054 3.02291 12.2845C3.16403 12.1538 3.34353 12.1108 3.50924 12.1361ZM11.0669 11.7454C11.2971 11.7138 11.5265 11.8478 11.6069 12.0755C11.6924 12.3193 11.576 12.5828 11.3471 12.6908C11.3428 12.696 11.3346 12.705 11.3256 12.721C11.2978 12.7706 11.2672 12.8577 11.2592 12.9779C11.244 13.2098 11.3168 13.5618 11.6518 13.9544C11.831 14.1644 11.806 14.4802 11.5962 14.6595C11.3862 14.8387 11.0704 14.8145 10.8911 14.6048C10.4014 14.0312 10.2273 13.4258 10.2612 12.9115C10.2779 12.6589 10.3462 12.4249 10.4546 12.2318C10.5444 12.0719 10.6842 11.9039 10.8803 11.807L10.9672 11.7699L11.0669 11.7454ZM5.92428 5.79427C5.92428 5.2357 6.35593 5.23572 6.35592 5.79427C6.35593 6.35264 6.57034 6.69727 6.64401 6.78743C6.71763 6.87749 6.84534 7.15945 7.30416 7.1595C7.76238 7.15988 7.76238 7.65527 7.30416 7.65559C6.84534 7.65559 6.77205 7.872 6.64401 8.02864C6.51604 8.18525 6.35794 8.41728 6.35788 8.96126C6.35788 9.50588 5.92623 9.51978 5.92623 8.96126C5.92613 8.40366 5.65582 8.067 5.62448 8.02864C5.59372 7.99102 5.27579 7.65582 4.91647 7.65559C4.557 7.65559 4.56049 7.1595 4.91647 7.1595C5.27178 7.15932 5.50406 6.87024 5.57174 6.78743C5.63909 6.70504 5.92426 6.3528 5.92428 5.79427Z" 33 + fill="currentColor" 34 + /> 35 + </svg> 36 + ); 37 + }; 38 + 39 + export const ReaderRead = (props: Props) => { 40 + return ( 41 + <svg 42 + width="24" 43 + height="24" 44 + viewBox="0 0 24 24" 45 + fill="none" 46 + xmlns="http://www.w3.org/2000/svg" 47 + {...props} 48 + > 49 + <path 50 + d="M5.3939 5.10098C6.94949 3.45228 9.5127 3.07248 11.1166 4.58535C11.8822 5.30769 12.2281 6.27357 12.2132 7.26504C12.4456 7.20657 12.6982 7.16362 12.9515 7.15469C13.2516 7.14413 13.6091 7.17916 13.9379 7.34707C14.6404 7.7064 15.0081 8.33088 15.142 8.9418C16.5998 8.30589 18.3571 8.43563 19.7631 9.42032C21.8907 10.9106 22.4521 13.8268 20.9711 15.9418C20.5099 16.6002 19.9071 17.0981 19.2328 17.4281C19.2725 17.8704 19.3144 18.3372 19.389 18.8285C19.4945 19.5226 19.6505 20.0808 19.8754 20.4223C20.103 20.7681 20.0071 21.2335 19.6615 21.4613C19.3156 21.6891 18.8503 21.5932 18.6224 21.2475C18.2076 20.6174 18.0171 19.7803 17.9066 19.0531C17.8466 18.658 17.8029 18.2404 17.7679 17.8647C16.6347 18.0087 15.441 17.7451 14.4291 17.0365C12.3744 15.5975 11.7805 12.8288 13.0756 10.7358L11.4466 9.68106C11.2965 9.90817 11.1261 10.1258 10.9349 10.3285C9.3795 11.9772 6.81619 12.3575 5.21226 10.8451C4.85829 10.5112 4.59328 10.1249 4.41246 9.70841C4.12505 9.84619 3.7775 10.0195 3.40464 10.2016C3.08808 10.3561 2.76192 10.5118 2.47203 10.6361C2.20246 10.7518 1.89418 10.872 1.63999 10.9145C1.23165 10.9825 0.845053 10.7066 0.776711 10.2982C0.7085 9.88981 0.984507 9.50334 1.39292 9.43497C1.44871 9.42563 1.60626 9.37614 1.8812 9.25821C2.1356 9.14908 2.43339 9.00579 2.74644 8.85294C3.20082 8.63107 3.71706 8.37021 4.11265 8.19278C4.12147 7.09833 4.57394 5.97021 5.3939 5.10098ZM18.9027 10.6488C17.7826 9.86431 16.3636 9.87558 15.3129 10.5473C16.188 11.0878 17.0483 11.6416 17.7211 12.0824C18.1169 12.3418 18.449 12.5624 18.682 12.7182C18.7984 12.796 18.8907 12.8585 18.9535 12.9008C18.9844 12.9216 19.0085 12.9377 19.0248 12.9486C19.0328 12.9541 19.0392 12.9585 19.0433 12.9613C19.0454 12.9627 19.0471 12.9645 19.0482 12.9652H19.0492V12.9662C19.2776 13.1212 19.3377 13.4321 19.183 13.6606C19.0279 13.8888 18.7161 13.9473 18.4877 13.7924L18.4867 13.7934C18.4857 13.7927 18.4847 13.7908 18.4828 13.7895C18.4789 13.7868 18.4729 13.7829 18.4652 13.7777C18.4493 13.767 18.4248 13.7507 18.3939 13.7299C18.3771 13.7186 18.3582 13.7059 18.3373 13.6918C18.3409 13.7007 18.3455 13.7094 18.349 13.7182C18.4391 13.9478 18.4846 14.151 18.514 14.3129C18.5488 14.5038 18.5528 14.5587 18.5697 14.6166C18.6475 14.8814 18.4955 15.1597 18.2308 15.2377C17.9663 15.3152 17.6888 15.1642 17.6107 14.8998C17.5741 14.7753 17.5483 14.5898 17.5306 14.4926C17.5076 14.366 17.4762 14.2309 17.4183 14.0834C17.3064 13.7986 17.0738 13.4138 16.516 12.9633L14.3353 11.5512C13.4914 12.949 13.8764 14.8184 15.2894 15.808C16.7626 16.8395 18.752 16.4941 19.7416 15.0815C20.731 13.6686 20.3757 11.6807 18.9027 10.6488ZM10.0873 5.67715C9.23327 4.87156 7.62151 4.92548 6.48472 6.13028C5.34805 7.33535 5.38756 8.94767 6.24156 9.75333C7.09567 10.5587 8.70742 10.5041 9.8441 9.29923C9.97166 9.16397 10.0826 9.02211 10.181 8.87833C8.43436 8.11224 8.03413 8.1053 7.73863 8.15372C7.49417 8.19384 7.43342 8.19931 7.34215 8.21719C7.27583 8.2302 7.20421 8.24884 7.00426 8.31387C6.74185 8.39904 6.45989 8.25486 6.37437 7.99258C6.28923 7.73019 6.43242 7.44723 6.69469 7.36172C6.90022 7.29488 7.02051 7.26011 7.14976 7.23477C7.25177 7.2148 7.38857 7.1961 7.5648 7.16739L7.50523 7.1293C7.27353 6.9792 7.20675 6.66966 7.3568 6.43789C7.50691 6.20642 7.81653 6.14058 8.0482 6.29043L10.6127 7.95059C10.8517 7.07384 10.6453 6.20394 10.0873 5.67715ZM13.0043 8.65372C12.9388 8.65604 12.868 8.66411 12.7933 8.67618L13.6478 9.14883C13.5794 8.94601 13.4513 8.7833 13.2552 8.68301C13.2369 8.67373 13.1626 8.64818 13.0043 8.65372Z" 51 + fill="currentColor" 52 + /> 53 + </svg> 54 + ); 55 + };
+1 -1
components/Icons/PopoverArrow.tsx
··· 11 11 height="8" 12 12 viewBox="0 0 16 8" 13 13 fill="none" 14 - className="-mt-[1px]" 14 + className="-mt-px" 15 15 xmlns="http://www.w3.org/2000/svg" 16 16 > 17 17 <path
+1
components/Icons/PublishSmall.tsx
··· 8 8 viewBox="0 0 24 24" 9 9 fill="none" 10 10 xmlns="http://www.w3.org/2000/svg" 11 + {...props} 11 12 > 12 13 <path 13 14 fillRule="evenodd"
+36
components/Icons/ReaderSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const ReaderUnreadSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="25" 7 + height="24" 8 + viewBox="0 0 25 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M2.82968 18.0472C2.82987 17.3641 3.41641 17.3642 3.41659 18.0472C3.41658 18.7307 3.7081 19.1528 3.80819 19.263C3.90827 19.3732 4.08109 19.7179 4.70468 19.7181C5.32851 19.7182 5.32851 20.3255 4.70468 20.3255C4.08111 20.3256 3.98225 20.5899 3.80819 20.7816C3.63417 20.9732 3.41859 21.2572 3.41854 21.9232C3.41854 22.5896 2.83163 22.6066 2.83163 21.9232C2.83155 21.2398 2.4633 20.8276 2.42147 20.7816C2.37905 20.735 1.94693 20.3255 1.45858 20.3255C0.97001 20.3254 0.974762 19.7182 1.45858 19.7181C1.9419 19.7181 2.25811 19.3644 2.35018 19.263C2.44176 19.1622 2.82968 18.7307 2.82968 18.0472ZM18.8189 13.7962C18.8191 13.1543 19.3158 13.1543 19.316 13.7962C19.316 14.4386 19.5624 14.8352 19.6471 14.9388C19.7317 15.0425 19.8778 15.3663 20.4049 15.3665C20.9325 15.3666 20.9325 15.9378 20.4049 15.9378C19.8775 15.9379 19.7943 16.1864 19.6471 16.3665C19.4999 16.5466 19.3171 16.8131 19.317 17.4388C19.317 18.0651 18.8209 18.0811 18.8209 17.4388C18.8207 16.7986 18.5111 16.4117 18.4742 16.3665C18.4388 16.3233 18.073 15.9381 17.6598 15.9378C17.2464 15.9378 17.2504 15.3665 17.6598 15.3665C18.0686 15.3663 18.336 15.0338 18.4137 14.9388C18.4911 14.844 18.8189 14.4386 18.8189 13.7962ZM6.10897 1.79134C6.74065 1.54967 7.31924 1.76772 7.73007 2.04427C8.13472 2.31686 8.48886 2.71494 8.77304 3.07649C9.05968 3.44125 9.32671 3.83827 9.52304 4.11653C9.76174 4.45493 9.68165 4.92361 9.34335 5.16243C9.0051 5.40111 8.53735 5.31974 8.29843 4.98177C8.07551 4.66583 7.84739 4.32673 7.59237 4.00227C7.33511 3.67506 7.09903 3.42822 6.89315 3.28938C6.79455 3.22296 6.72689 3.19837 6.69101 3.19075C6.66479 3.18528 6.65529 3.18881 6.64511 3.1927C6.60428 3.20859 6.40334 3.32114 6.13925 3.88899C6.01867 4.14843 5.71704 5.01402 5.35214 6.10677C5.27851 6.12987 5.22515 6.14661 5.21835 6.15462C5.21489 6.15884 5.21125 6.16214 5.20858 6.16536C4.91664 6.19308 4.63293 6.31734 4.41464 6.54427C4.17686 6.79162 4.06902 7.10768 4.06796 7.40462C4.067 7.70171 4.17276 8.02065 4.41268 8.27083C4.46886 8.32937 4.52964 8.38044 4.59335 8.42513C4.37591 9.09776 4.17095 9.74532 3.99765 10.2884C4.2707 10.3144 4.54179 10.3779 4.80038 10.4691C5.5814 10.7446 6.34697 11.3005 6.96933 12.0501C7.13971 11.9646 7.3231 11.9158 7.50936 11.8929C7.9461 11.8392 8.39353 11.9251 8.81893 12.1146C8.94078 11.5922 9.19242 11.1032 9.59433 10.6868C11.081 9.14683 13.6312 9.62241 15.2428 11.1781C15.3753 11.306 15.4999 11.4413 15.6178 11.5814C15.7723 11.0931 15.9613 10.4983 16.1588 9.87044C16.6539 8.29641 17.2076 6.52428 17.4498 5.71028C17.7531 4.69126 18.1848 3.91964 18.8023 3.49056C19.4769 3.02218 20.2194 3.06179 20.859 3.34017C21.4671 3.60494 22.0253 4.09649 22.5025 4.63802C22.9884 5.18942 23.4343 5.84393 23.8062 6.50032C24.0101 6.86052 23.884 7.31757 23.524 7.52181C23.1638 7.72592 22.7059 7.59954 22.5016 7.23958C22.1686 6.6521 21.7805 6.08653 21.3775 5.62923C20.9661 5.16239 20.5798 4.85528 20.2603 4.71614C19.9724 4.5908 19.8033 4.62119 19.6568 4.72298C19.4532 4.86476 19.1524 5.24367 18.8863 6.13802C18.6401 6.9656 18.0833 8.74963 17.5894 10.3197C17.3421 11.1061 17.1093 11.8418 16.9391 12.3802C16.854 12.6494 16.7853 12.87 16.7369 13.0228C16.7128 13.0988 16.6941 13.1581 16.6812 13.1986C16.6748 13.2188 16.6689 13.235 16.6656 13.2454C16.6641 13.2501 16.6625 13.2536 16.6617 13.2562V13.2591L16.6607 13.2601C16.6469 13.3038 16.6277 13.3449 16.607 13.3841C16.9766 14.5982 16.8251 15.8801 15.9322 16.805C14.4454 18.345 11.8953 17.8696 10.2838 16.3138C9.60418 15.6576 9.11137 14.8276 8.87948 13.972C8.47459 13.5525 8.06917 13.397 7.80429 13.3831C8.01717 13.8598 8.15284 14.345 8.19784 14.8138C8.28056 15.6775 8.06391 16.5969 7.33065 17.1702C6.59744 17.7433 5.65367 17.7322 4.83554 17.4437C4.00748 17.1515 3.19408 16.5464 2.55331 15.7269C1.91258 14.9074 1.52188 13.972 1.43808 13.098C1.35693 12.25 1.56474 11.3498 2.2662 10.7747C2.51811 9.97534 3.00684 8.4405 3.48983 6.96224C4.01662 5.34994 4.56587 3.71844 4.78085 3.25618C5.10348 2.56265 5.52293 2.01575 6.10897 1.79134ZM14.2008 12.2572C12.9082 11.0094 11.3496 11.0284 10.6734 11.7288C9.99762 12.4294 10.0335 13.987 11.3258 15.2347C12.6181 16.4823 14.1767 16.464 14.8531 15.764C15.5292 15.0636 15.4931 13.505 14.2008 12.2572ZM4.30233 11.8841C3.77284 11.6973 3.41998 11.7759 3.22909 11.9251C3.03847 12.0746 2.87871 12.3971 2.93222 12.9554C2.98485 13.5041 3.24331 14.1742 3.73495 14.8031C4.22664 15.4319 4.81476 15.8452 5.33456 16.0286C5.86353 16.2152 6.2159 16.1376 6.40683 15.9886C6.59761 15.8394 6.75812 15.5159 6.70468 14.9574C6.65205 14.4087 6.3926 13.7385 5.90097 13.1097C5.40938 12.481 4.82205 12.0676 4.30233 11.8841ZM12.3394 14.7786C12.5125 14.5637 12.8275 14.5295 13.0426 14.7025C13.081 14.7334 13.1653 14.7848 13.2691 14.8284C13.318 14.849 13.3639 14.8642 13.4019 14.8743C13.4373 14.8837 13.456 14.8858 13.4596 14.8861C13.7357 14.8914 13.9561 15.1197 13.9508 15.3958C13.9454 15.6718 13.7169 15.8912 13.441 15.8861C13.243 15.8822 13.0364 15.8145 12.8814 15.7493C12.7197 15.6813 12.5471 15.5876 12.4156 15.4818C12.2007 15.3087 12.1665 14.9937 12.3394 14.7786ZM3.92929 12.1361L3.9996 12.1497L4.09335 12.1878C4.3006 12.2932 4.41321 12.533 4.34921 12.7659C4.32756 12.8445 4.28534 12.9118 4.23397 12.9681C4.23698 12.9884 4.23927 13.0124 4.24472 13.0384C4.29698 13.2878 4.4441 13.6447 4.70565 13.9671C4.87945 14.1814 4.84746 14.4972 4.63339 14.6712C4.41911 14.8451 4.10336 14.812 3.92929 14.598C3.56417 14.1481 3.35003 13.6431 3.2662 13.2425C3.22622 13.0513 3.2054 12.8362 3.24569 12.6468C3.26503 12.5561 3.31274 12.4054 3.44296 12.2845C3.58407 12.1538 3.76357 12.1108 3.92929 12.1361ZM11.4869 11.7454C11.7171 11.7138 11.9466 11.8478 12.0269 12.0755C12.1125 12.3193 11.996 12.5828 11.7672 12.6908C11.7629 12.696 11.7547 12.705 11.7457 12.721C11.7178 12.7706 11.6872 12.8577 11.6793 12.9779C11.6641 13.2098 11.7368 13.5618 12.0719 13.9544C12.2511 14.1644 12.226 14.4802 12.0162 14.6595C11.8063 14.8387 11.4905 14.8145 11.3111 14.6048C10.8215 14.0312 10.6473 13.4258 10.6812 12.9115C10.698 12.6589 10.7662 12.4249 10.8746 12.2318C10.9645 12.0719 11.1042 11.9039 11.3004 11.807L11.3873 11.7699L11.4869 11.7454ZM6.34433 5.79427C6.34433 5.2357 6.77597 5.23572 6.77597 5.79427C6.77598 6.35264 6.99039 6.69727 7.06405 6.78743C7.13767 6.87749 7.26539 7.15945 7.72421 7.1595C8.18243 7.15988 8.18243 7.65527 7.72421 7.65559C7.26539 7.65559 7.19209 7.872 7.06405 8.02864C6.93609 8.18525 6.77798 8.41728 6.77792 8.96126C6.77792 9.50588 6.34628 9.51978 6.34628 8.96126C6.34618 8.40366 6.07586 8.067 6.04452 8.02864C6.01377 7.99102 5.69583 7.65582 5.33651 7.65559C4.97705 7.65559 4.98054 7.1595 5.33651 7.1595C5.69183 7.15932 5.92411 6.87024 5.99179 6.78743C6.05914 6.70504 6.3443 6.3528 6.34433 5.79427Z" 14 + fill="currentColor" 15 + /> 16 + </svg> 17 + ); 18 + }; 19 + 20 + export const ReaderReadSmall = (props: Props) => { 21 + return ( 22 + <svg 23 + width="24" 24 + height="24" 25 + viewBox="0 0 24 24" 26 + fill="none" 27 + xmlns="http://www.w3.org/2000/svg" 28 + {...props} 29 + > 30 + <path 31 + d="M5.3939 5.10098C6.94949 3.45228 9.5127 3.07248 11.1166 4.58535C11.8822 5.30769 12.2281 6.27357 12.2132 7.26504C12.4456 7.20657 12.6982 7.16362 12.9515 7.15469C13.2516 7.14413 13.6091 7.17916 13.9379 7.34707C14.6404 7.7064 15.0081 8.33088 15.142 8.9418C16.5998 8.30589 18.3571 8.43563 19.7631 9.42032C21.8907 10.9106 22.4521 13.8268 20.9711 15.9418C20.5099 16.6002 19.9071 17.0981 19.2328 17.4281C19.2725 17.8704 19.3144 18.3372 19.389 18.8285C19.4945 19.5226 19.6505 20.0808 19.8754 20.4223C20.103 20.7681 20.0071 21.2335 19.6615 21.4613C19.3156 21.6891 18.8503 21.5932 18.6224 21.2475C18.2076 20.6174 18.0171 19.7803 17.9066 19.0531C17.8466 18.658 17.8029 18.2404 17.7679 17.8647C16.6347 18.0087 15.441 17.7451 14.4291 17.0365C12.3744 15.5975 11.7805 12.8288 13.0756 10.7358L11.4466 9.68106C11.2965 9.90817 11.1261 10.1258 10.9349 10.3285C9.3795 11.9772 6.81619 12.3575 5.21226 10.8451C4.85829 10.5112 4.59328 10.1249 4.41246 9.70841C4.12505 9.84619 3.7775 10.0195 3.40464 10.2016C3.08808 10.3561 2.76192 10.5118 2.47203 10.6361C2.20246 10.7518 1.89418 10.872 1.63999 10.9145C1.23165 10.9825 0.845053 10.7066 0.776711 10.2982C0.7085 9.88981 0.984507 9.50334 1.39292 9.43497C1.44871 9.42563 1.60626 9.37614 1.8812 9.25821C2.1356 9.14908 2.43339 9.00579 2.74644 8.85294C3.20082 8.63107 3.71706 8.37021 4.11265 8.19278C4.12147 7.09833 4.57394 5.97021 5.3939 5.10098ZM18.9027 10.6488C17.7826 9.86431 16.3636 9.87558 15.3129 10.5473C16.188 11.0878 17.0483 11.6416 17.7211 12.0824C18.1169 12.3418 18.449 12.5624 18.682 12.7182C18.7984 12.796 18.8907 12.8585 18.9535 12.9008C18.9844 12.9216 19.0085 12.9377 19.0248 12.9486C19.0328 12.9541 19.0392 12.9585 19.0433 12.9613C19.0454 12.9627 19.0471 12.9645 19.0482 12.9652H19.0492V12.9662C19.2776 13.1212 19.3377 13.4321 19.183 13.6606C19.0279 13.8888 18.7161 13.9473 18.4877 13.7924L18.4867 13.7934C18.4857 13.7927 18.4847 13.7908 18.4828 13.7895C18.4789 13.7868 18.4729 13.7829 18.4652 13.7777C18.4493 13.767 18.4248 13.7507 18.3939 13.7299C18.3771 13.7186 18.3582 13.7059 18.3373 13.6918C18.3409 13.7007 18.3455 13.7094 18.349 13.7182C18.4391 13.9478 18.4846 14.151 18.514 14.3129C18.5488 14.5038 18.5528 14.5587 18.5697 14.6166C18.6475 14.8814 18.4955 15.1597 18.2308 15.2377C17.9663 15.3152 17.6888 15.1642 17.6107 14.8998C17.5741 14.7753 17.5483 14.5898 17.5306 14.4926C17.5076 14.366 17.4762 14.2309 17.4183 14.0834C17.3064 13.7986 17.0738 13.4138 16.516 12.9633L14.3353 11.5512C13.4914 12.949 13.8764 14.8184 15.2894 15.808C16.7626 16.8395 18.752 16.4941 19.7416 15.0815C20.731 13.6686 20.3757 11.6807 18.9027 10.6488ZM10.0873 5.67715C9.23327 4.87156 7.62151 4.92548 6.48472 6.13028C5.34805 7.33535 5.38756 8.94767 6.24156 9.75333C7.09567 10.5587 8.70742 10.5041 9.8441 9.29923C9.97166 9.16397 10.0826 9.02211 10.181 8.87833C8.43436 8.11224 8.03413 8.1053 7.73863 8.15372C7.49417 8.19384 7.43342 8.19931 7.34215 8.21719C7.27583 8.2302 7.20421 8.24884 7.00426 8.31387C6.74185 8.39904 6.45989 8.25487 6.37437 7.99258C6.28923 7.73019 6.43242 7.44723 6.69469 7.36172C6.90022 7.29488 7.02051 7.26011 7.14976 7.23477C7.25177 7.2148 7.38857 7.1961 7.5648 7.16739L7.50523 7.1293C7.27353 6.9792 7.20675 6.66966 7.3568 6.43789C7.50691 6.20642 7.81653 6.14058 8.0482 6.29043L10.6127 7.95059C10.8517 7.07384 10.6453 6.20394 10.0873 5.67715ZM13.0043 8.65372C12.9388 8.65604 12.868 8.66411 12.7933 8.67618L13.6478 9.14883C13.5794 8.94601 13.4513 8.7833 13.2552 8.68301C13.2369 8.67373 13.1626 8.64818 13.0043 8.65372Z" 32 + fill="currentColor" 33 + /> 34 + </svg> 35 + ); 36 + };
+21
components/Icons/SearchSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const SearchSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + {...props} 12 + > 13 + <path 14 + fillRule="evenodd" 15 + clipRule="evenodd" 16 + d="M15.3882 13.4918C16.0902 13.4116 16.8207 13.6407 17.3591 14.1791L17.368 14.188L17.3679 14.1881L20.8268 17.7645C21.7593 18.7021 21.7577 20.218 20.822 21.1537C19.8848 22.0909 18.3652 22.0909 17.428 21.1537L17.4191 21.1448L17.4192 21.1447L13.9603 17.5684C13.4304 17.0355 13.2022 16.3159 13.2755 15.6222L12.5856 14.9162C11.5587 15.5303 10.3576 15.8831 9.07417 15.8831C5.28991 15.8831 2.22216 12.8154 2.22216 9.03113C2.22216 5.24687 5.28991 2.17912 9.07417 2.17912C12.8584 2.17912 15.9262 5.24687 15.9262 9.03114C15.9262 10.4458 15.4975 11.7603 14.7629 12.8519L15.3882 13.4918ZM15.0363 16.5233C15.3829 16.5101 15.7131 16.3963 15.9528 16.1619C16.2057 15.9149 16.3227 15.6029 16.3364 15.2792L19.7526 18.8114L19.7525 18.8114L19.7614 18.8203C20.1128 19.1717 20.1128 19.7416 19.7614 20.093C19.4112 20.4432 18.8442 20.4445 18.4924 20.0968L15.0363 16.5233ZM9.07417 3.92912C6.25641 3.92912 3.97216 6.21337 3.97216 9.03113C3.97216 11.8489 6.25641 14.1331 9.07417 14.1331C11.8919 14.1331 14.1762 11.8489 14.1762 9.03114C14.1762 6.21337 11.8919 3.92912 9.07417 3.92912ZM10.2779 11.7018C10.4564 12.1657 10.2251 12.6865 9.76119 12.865C9.47813 12.974 9.18509 12.9893 8.95246 12.979C8.70872 12.9683 8.46274 12.926 8.23733 12.8706C7.80084 12.7632 7.33708 12.5802 7.01053 12.3667C6.55802 12.0708 5.66109 11.4318 5.39409 10.3237C5.27767 9.84042 5.57502 9.35431 6.05825 9.23788C6.54148 9.12146 7.02759 9.41881 7.14402 9.90204C7.23429 10.2767 7.57343 10.5841 7.99558 10.8601C8.10636 10.9325 8.36702 11.0488 8.66713 11.1226C8.81002 11.1578 8.93539 11.1765 9.03167 11.1808C9.09302 11.1835 9.12443 11.1795 9.13263 11.1784L9.13269 11.1784C9.59197 11.0126 10.1017 11.2439 10.2779 11.7018ZM5.5481 6.89456C5.19301 7.32017 5.25018 7.95304 5.67578 8.30813C6.10138 8.66322 6.73425 8.60606 7.08934 8.18046C7.44443 7.75485 7.38727 7.12198 6.96167 6.76689C6.53607 6.4118 5.90319 6.46896 5.5481 6.89456Z" 17 + fill="currentColor" 18 + /> 19 + </svg> 20 + ); 21 + };
+20
components/Icons/SearchTiny.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const SearchTiny = (props: Props) => { 4 + return ( 5 + <svg 6 + width="16" 7 + height="16" 8 + viewBox="0 0 16 16" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + fillRule="evenodd" 14 + clipRule="evenodd" 15 + d="M2.82929 7.1256C2.42204 5.14225 3.64873 3.25471 5.51296 2.87192C7.37719 2.48913 9.24843 3.74055 9.65568 5.72391C10.0629 7.70726 8.83624 9.5948 6.97201 9.97759C5.10778 10.3604 3.23654 9.10896 2.82929 7.1256ZM5.24142 1.54951C2.6052 2.09082 0.958359 4.7258 1.50688 7.39714C2.0554 10.0685 4.60732 11.8413 7.24355 11.3C7.9444 11.1561 8.57532 10.8642 9.11365 10.463L13.0573 13.6769C13.4854 14.0258 14.1153 13.9616 14.4642 13.5334C14.8131 13.1053 14.7489 12.4754 14.3207 12.1265L10.4344 8.95927C11.012 7.93759 11.2341 6.69894 10.9781 5.45237C10.4296 2.78102 7.87765 1.0082 5.24142 1.54951ZM4.70667 6.0399C5.09948 5.95924 5.35253 5.57542 5.27187 5.18261C5.19121 4.7898 4.80739 4.53675 4.41458 4.6174C4.02177 4.69806 3.76872 5.08189 3.84938 5.4747C3.93004 5.86751 4.31386 6.12056 4.70667 6.0399ZM5.09658 7.14249C5.03594 6.80268 4.71131 6.57638 4.3715 6.63703C4.03169 6.69767 3.80538 7.02231 3.86603 7.36211C4.05123 8.39982 5.20679 9.33276 6.72726 9.11913C7.06908 9.07111 7.30725 8.75508 7.25923 8.41326C7.2112 8.07143 6.89517 7.83327 6.55335 7.88129C5.61779 8.01274 5.14981 7.44073 5.09658 7.14249Z" 16 + fill="currentColor" 17 + /> 18 + </svg> 19 + ); 20 + };
+18
components/Icons/SortSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const SortSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M6.67186 18.5V8.74805L4.70408 10.7002C4.31199 11.089 3.67892 11.0863 3.29002 10.6943C2.90107 10.3023 2.90381 9.66923 3.29588 9.28027L6.96776 5.6377L7.04295 5.56934C7.43471 5.25182 8.01037 5.27501 8.37596 5.6377L12.0478 9.28027C12.4399 9.66924 12.4427 10.3023 12.0537 10.6943C11.6648 11.0863 11.0317 11.089 10.6396 10.7002L8.67186 8.74805V18.5C8.67186 19.0523 8.22414 19.5 7.67186 19.5C7.1196 19.5 6.67186 19.0523 6.67186 18.5ZM15.3281 5.5C15.3281 4.94772 15.7758 4.5 16.3281 4.5C16.8804 4.50003 17.3281 4.94774 17.3281 5.5V15.1016L19.2959 13.1504C19.688 12.7616 20.321 12.7642 20.7099 13.1562C21.0988 13.5483 21.0961 14.1814 20.7041 14.5703L17.0322 18.2129C16.6423 18.5997 16.0139 18.5997 15.624 18.2129L11.9521 14.5703C11.5601 14.1814 11.5573 13.5483 11.9463 13.1562C12.3352 12.7642 12.9683 12.7615 13.3603 13.1504L15.3281 15.1016V5.5Z" 14 + fill="currentColor" 15 + /> 16 + </svg> 17 + ); 18 + };
+18
components/Icons/TabsSmall.tsx
··· 1 + import { Props } from "./Props"; 2 + 3 + export const TabsSmall = (props: Props) => { 4 + return ( 5 + <svg 6 + width="24" 7 + height="24" 8 + viewBox="0 0 24 24" 9 + fill="none" 10 + xmlns="http://www.w3.org/2000/svg" 11 + > 12 + <path 13 + d="M4.5741 3.43762C4.71829 3.43388 4.83844 3.45707 4.91101 3.47278L7.50476 4.03333L7.50378 4.0343C7.93101 4.12295 8.3082 4.36081 8.56433 4.70715C8.91472 4.53042 9.32259 4.46201 9.72644 4.5509L11.4755 4.93567C11.8499 5.01812 12.1582 5.22518 12.3632 5.50598C12.7041 5.34426 13.096 5.28489 13.4843 5.37024L15.2343 5.755C15.961 5.91506 16.4399 6.54224 16.3944 7.27454L16.3358 8.20227C17.2858 8.41813 17.9768 9.25659 17.9823 10.2443L20.9491 10.8712L20.954 10.8732C22.0873 11.1209 22.7352 12.3188 22.3212 13.4025L19.8788 19.7941C19.4226 20.9881 18.1618 21.6693 16.913 21.3966L3.97937 18.5724C2.74764 18.3034 1.88959 17.1843 1.9491 15.9249C1.94954 15.9155 1.95025 15.906 1.95105 15.8966L2.85925 5.32043C2.89069 4.14023 3.78539 3.55411 4.42175 3.45129L4.5741 3.43762ZM4.62488 4.94543C4.58481 4.96035 4.52557 4.98845 4.47449 5.04114C4.41904 5.09848 4.35632 5.19676 4.35632 5.38489C4.35631 5.40603 4.35519 5.4273 4.35339 5.44836L3.44714 15.9962V16.0939C3.46785 16.5814 3.81504 17.0007 4.29968 17.1066L17.2333 19.9318C17.7572 20.0461 18.286 19.7598 18.4774 19.2589L20.9198 12.8673C21.0063 12.6408 20.8706 12.3898 20.6337 12.338V12.337L17.8378 11.7472C17.5141 13.7033 17.0716 16.26 16.8915 17.2648C16.8341 17.5855 16.9494 17.7292 17.0175 17.7823C17.1088 17.8537 17.1828 17.8354 17.2089 17.8195C17.562 17.6031 18.0237 17.7134 18.2401 18.0665C18.4564 18.4197 18.3451 18.8814 17.9921 19.0978C17.3655 19.4817 16.6134 19.3701 16.0936 18.964C15.5506 18.5395 15.2689 17.8204 15.4159 17.0001C15.6184 15.871 16.1561 12.765 16.4774 10.7823C16.4808 10.6491 16.4831 10.4619 16.4833 10.257C16.4836 9.97523 16.2841 9.72512 15.996 9.66321L15.9852 9.66028L7.88953 7.79407C7.52736 7.71059 7.28106 7.37451 7.31042 7.00403L7.40808 5.77161C7.40576 5.67206 7.37646 5.61707 7.35046 5.58606C7.3204 5.55031 7.27091 5.51692 7.19812 5.50208L7.18738 5.50012L4.62488 4.94543ZM13.1053 6.56555C12.8679 6.51346 12.6242 6.69552 12.6102 6.93469L12.5868 7.33606L15.0829 7.91223L15.1219 7.30188C15.1323 7.13296 15.022 6.9882 14.8544 6.95129L13.1053 6.56555ZM9.34656 5.74622C9.1658 5.70656 8.98124 5.80266 8.89734 5.95618L8.85632 6.47668L11.328 7.04602L11.3641 6.48254C11.3745 6.31363 11.2632 6.16886 11.0956 6.13196L9.34656 5.74622Z" 14 + fill="currentColor" 15 + /> 16 + </svg> 17 + ); 18 + };
+4
components/IdentityProvider.tsx
··· 2 2 import { getIdentityData } from "actions/getIdentityData"; 3 3 import { createContext, useContext } from "react"; 4 4 import useSWR, { KeyedMutator, mutate } from "swr"; 5 + import { DashboardState } from "./PageLayouts/DashboardLayout"; 5 6 7 + export type InterfaceState = { 8 + dashboards: { [id: string]: DashboardState | undefined }; 9 + }; 6 10 type Identity = Awaited<ReturnType<typeof getIdentityData>>; 7 11 let IdentityContext = createContext({ 8 12 identity: null as Identity,
+2 -2
components/Input.tsx
··· 100 100 JSX.IntrinsicElements["textarea"], 101 101 ) => { 102 102 let { label, textarea, ...inputProps } = props; 103 - let style = `appearance-none w-full font-normal bg-transparent text-base text-primary focus:outline-0 ${props.className} outline-none resize-none`; 103 + let style = `appearance-none w-full font-normal bg-transparent text-base text-primary focus:outline-0 ${props.className} outline-hidden resize-none`; 104 104 return ( 105 - <label className=" input-with-border flex flex-col gap-[1px] text-sm text-tertiary font-bold italic leading-tight !py-1 !px-[6px]"> 105 + <label className=" input-with-border flex flex-col gap-px text-sm text-tertiary font-bold italic leading-tight py-1! px-[6px]!"> 106 106 {props.label} 107 107 {textarea ? ( 108 108 <textarea {...inputProps} className={style} />
+2 -2
components/Layout.tsx
··· 90 90 font-bold z-10 py-1 px-3 91 91 text-left text-secondary 92 92 flex gap-2 93 - data-[highlighted]:bg-border-light data-[highlighted]:text-secondary 93 + data-highlighted:bg-border-light data-highlighted:text-secondary 94 94 hover:bg-border-light hover:text-secondary 95 - outline-none 95 + outline-hidden 96 96 cursor-pointer 97 97 ${props.className} 98 98 `}
+20
components/Media.tsx
··· 20 20 return <div className={props.className}>{props.children}</div>; 21 21 return null; 22 22 } 23 + 24 + export function MediaContents(props: { 25 + mobile: boolean; 26 + children: React.ReactNode; 27 + className?: string; 28 + }) { 29 + let initialRender = useIsInitialRender(); 30 + let isMobile = useIsMobile(); 31 + if (initialRender) 32 + return ( 33 + <div 34 + className={`${props.mobile ? "sm:hidden contents" : "hidden sm:contents"} ${props.className}`} 35 + > 36 + {props.children} 37 + </div> 38 + ); 39 + if ((isMobile && props.mobile) || (!isMobile && !props.mobile)) 40 + return <div className={props.className}>{props.children}</div>; 41 + return null; 42 + }
+77
components/PageHeader.tsx
··· 1 + "use client"; 2 + import { useState, useEffect } from "react"; 3 + 4 + export const Header = (props: { 5 + children: React.ReactNode; 6 + hasBackgroundImage: boolean; 7 + }) => { 8 + let [scrollPos, setScrollPos] = useState(0); 9 + 10 + useEffect(() => { 11 + const homeContent = document.getElementById("home-content"); 12 + 13 + const handleScroll = () => { 14 + if (homeContent) { 15 + setScrollPos(homeContent.scrollTop); 16 + } 17 + }; 18 + 19 + if (homeContent) { 20 + homeContent.addEventListener("scroll", handleScroll); 21 + return () => homeContent.removeEventListener("scroll", handleScroll); 22 + } 23 + }, []); 24 + 25 + let headerBGColor = props.hasBackgroundImage 26 + ? "var(--bg-page)" 27 + : "var(--bg-leaflet)"; 28 + 29 + return ( 30 + <div 31 + className={` 32 + headerWrapper 33 + sticky top-0 z-10 34 + w-full bg-transparent 35 + `} 36 + > 37 + <div 38 + style={ 39 + scrollPos < 20 40 + ? { 41 + paddingLeft: `calc(${scrollPos / 20}*8px)`, 42 + paddingRight: `calc(${scrollPos / 20}*8px)`, 43 + } 44 + : { paddingLeft: `8px`, paddingRight: `8px` } 45 + } 46 + > 47 + <div 48 + className={` 49 + headerContent 50 + border rounded-lg 51 + ${scrollPos > 20 ? "border-border-light" : "border-transparent"} 52 + py-1 53 + w-full flex justify-between items-center gap-4`} 54 + style={ 55 + scrollPos < 20 56 + ? { 57 + backgroundColor: `rgba(${headerBGColor}, ${scrollPos / 40 + 0.5})`, 58 + paddingLeft: props.hasBackgroundImage 59 + ? "8px" 60 + : `calc(${scrollPos / 20}*8px)`, 61 + paddingRight: props.hasBackgroundImage 62 + ? "8px" 63 + : `calc(${scrollPos / 20}*8px)`, 64 + } 65 + : { 66 + backgroundColor: `rgb(${headerBGColor})`, 67 + paddingLeft: "8px", 68 + paddingRight: "8px", 69 + } 70 + } 71 + > 72 + {props.children} 73 + </div> 74 + </div> 75 + </div> 76 + ); 77 + };
+450
components/PageLayouts/DashboardLayout.tsx
··· 1 + "use client"; 2 + import { useState, createContext, useContext } from "react"; 3 + import { Header } from "../PageHeader"; 4 + import { Footer } from "components/ActionBar/Footer"; 5 + import { Sidebar } from "components/ActionBar/Sidebar"; 6 + import { 7 + DesktopNavigation, 8 + MobileNavigation, 9 + navPages, 10 + } from "components/ActionBar/Navigation"; 11 + import { create } from "zustand"; 12 + import { Popover } from "components/Popover"; 13 + import { Checkbox } from "components/Checkbox"; 14 + import { Separator } from "components/Layout"; 15 + import { CloseTiny } from "components/Icons/CloseTiny"; 16 + import { MediaContents } from "components/Media"; 17 + import { SortSmall } from "components/Icons/SortSmall"; 18 + import { TabsSmall } from "components/Icons/TabsSmall"; 19 + import { Input } from "components/Input"; 20 + import { SearchTiny } from "components/Icons/SearchTiny"; 21 + import { InterfaceState, useIdentityData } from "components/IdentityProvider"; 22 + import { updateIdentityInterfaceState } from "actions/updateIdentityInterfaceState"; 23 + 24 + export type DashboardState = { 25 + display?: "grid" | "list"; 26 + sort?: "created" | "alphabetical"; 27 + filter: { 28 + drafts: boolean; 29 + published: boolean; 30 + docs: boolean; 31 + templates: boolean; 32 + }; 33 + }; 34 + 35 + type DashboardStore = { 36 + dashboards: { [id: string]: DashboardState }; 37 + setDashboard: (id: string, partial: Partial<DashboardState>) => void; 38 + }; 39 + 40 + const defaultDashboardState: DashboardState = { 41 + display: undefined, 42 + sort: undefined, 43 + filter: { drafts: false, published: false, docs: false, templates: false }, 44 + }; 45 + 46 + export const useDashboardStore = create<DashboardStore>((set, get) => ({ 47 + dashboards: {}, 48 + setDashboard: (id: string, partial: Partial<DashboardState>) => { 49 + console.log(partial); 50 + set((state) => ({ 51 + dashboards: { 52 + ...state.dashboards, 53 + [id]: { 54 + ...(state.dashboards[id] || defaultDashboardState), 55 + ...partial, 56 + }, 57 + }, 58 + })); 59 + }, 60 + })); 61 + 62 + const DashboardIdContext = createContext<string | null>(null); 63 + 64 + export const useDashboardId = () => { 65 + const id = useContext(DashboardIdContext); 66 + if (!id) { 67 + throw new Error("useDashboardId must be used within a DashboardLayout"); 68 + } 69 + return id; 70 + }; 71 + 72 + export const useDashboardState = () => { 73 + const id = useDashboardId(); 74 + let { identity } = useIdentityData(); 75 + let localState = useDashboardStore( 76 + (state) => state.dashboards[id] || defaultDashboardState, 77 + ); 78 + if (!identity) return localState; 79 + let metadata = identity.interface_state as InterfaceState; 80 + return metadata?.dashboards?.[id] || defaultDashboardState; 81 + }; 82 + 83 + export const useSetDashboardState = () => { 84 + const id = useDashboardId(); 85 + let { identity, mutate } = useIdentityData(); 86 + const setDashboard = useDashboardStore((state) => state.setDashboard); 87 + return async (partial: Partial<DashboardState>) => { 88 + if (!identity) return setDashboard(id, partial); 89 + 90 + let interface_state = (identity.interface_state as InterfaceState) || {}; 91 + let newDashboardState = { 92 + ...defaultDashboardState, 93 + ...interface_state.dashboards?.[id], 94 + ...partial, 95 + }; 96 + mutate( 97 + { 98 + ...identity, 99 + interface_state: { 100 + ...interface_state, 101 + dashboards: { 102 + ...interface_state.dashboards, 103 + [id]: newDashboardState, 104 + }, 105 + }, 106 + }, 107 + { revalidate: false }, 108 + ); 109 + await updateIdentityInterfaceState({ 110 + ...interface_state, 111 + dashboards: { 112 + [id]: newDashboardState, 113 + }, 114 + }); 115 + }; 116 + }; 117 + 118 + export function DashboardLayout< 119 + T extends { 120 + [name: string]: { content: React.ReactNode; controls: React.ReactNode }; 121 + }, 122 + >(props: { 123 + id: string; 124 + hasBackgroundImage: boolean; 125 + tabs: T; 126 + defaultTab: keyof T; 127 + currentPage: navPages; 128 + publication?: string; 129 + actions: React.ReactNode; 130 + }) { 131 + let [tab, setTab] = useState(props.defaultTab); 132 + let { content, controls } = props.tabs[tab]; 133 + 134 + let [headerState, setHeaderState] = useState<"default" | "controls">( 135 + "default", 136 + ); 137 + return ( 138 + <DashboardIdContext.Provider value={props.id}> 139 + <div 140 + className={`dashboard pwa-padding relative max-w-(--breakpoint-lg) w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6`} 141 + > 142 + <MediaContents mobile={false}> 143 + <div className="flex flex-col gap-4 my-6"> 144 + <DesktopNavigation 145 + currentPage={props.currentPage} 146 + publication={props.publication} 147 + /> 148 + {props.actions && <Sidebar alwaysOpen>{props.actions}</Sidebar>} 149 + </div> 150 + </MediaContents> 151 + <div 152 + className={`w-full h-full flex flex-col gap-2 relative overflow-y-scroll pt-3 pb-12 px-3 sm:pt-8 sm:pb-12 sm:pl-6 sm:pr-4 `} 153 + id="home-content" 154 + > 155 + {Object.keys(props.tabs).length <= 1 && !controls ? null : ( 156 + <> 157 + <Header hasBackgroundImage={props.hasBackgroundImage}> 158 + {headerState === "default" ? ( 159 + <> 160 + {Object.keys(props.tabs).length > 1 && ( 161 + <div className="pubDashTabs flex flex-row gap-1"> 162 + {Object.keys(props.tabs).map((t) => ( 163 + <Tab 164 + key={t} 165 + name={t} 166 + selected={t === tab} 167 + onSelect={() => setTab(t)} 168 + /> 169 + ))} 170 + </div> 171 + )} 172 + {props.publication && ( 173 + <button 174 + className={`sm:hidden block text-tertiary`} 175 + onClick={() => { 176 + setHeaderState("controls"); 177 + }} 178 + > 179 + <SortSmall /> 180 + </button> 181 + )} 182 + <div 183 + className={`sm:block ${props.publication && "hidden"} grow`} 184 + > 185 + {controls} 186 + </div> 187 + </> 188 + ) : ( 189 + <> 190 + {controls} 191 + <button 192 + className="text-tertiary" 193 + onClick={() => { 194 + setHeaderState("default"); 195 + }} 196 + > 197 + <TabsSmall /> 198 + </button> 199 + </> 200 + )} 201 + </Header> 202 + </> 203 + )} 204 + {content} 205 + </div> 206 + <Footer> 207 + <MobileNavigation 208 + currentPage={props.currentPage} 209 + publication={props.publication} 210 + /> 211 + {props.actions && ( 212 + <> 213 + <Separator /> 214 + {props.actions} 215 + </> 216 + )} 217 + </Footer> 218 + </div> 219 + </DashboardIdContext.Provider> 220 + ); 221 + } 222 + 223 + export const HomeDashboardControls = (props: { 224 + searchValue: string; 225 + setSearchValueAction: (searchValue: string) => void; 226 + hasBackgroundImage: boolean; 227 + defaultDisplay: Exclude<DashboardState["display"], undefined>; 228 + hasPubs: boolean; 229 + hasTemplates: boolean; 230 + }) => { 231 + let { display, sort } = useDashboardState(); 232 + console.log({ display, props }); 233 + display = display || props.defaultDisplay; 234 + let setState = useSetDashboardState(); 235 + 236 + let { identity } = useIdentityData(); 237 + console.log(props); 238 + 239 + return ( 240 + <div className="dashboardControls w-full flex gap-4"> 241 + {identity && ( 242 + <SearchInput 243 + searchValue={props.searchValue} 244 + setSearchValue={props.setSearchValueAction} 245 + hasBackgroundImage={props.hasBackgroundImage} 246 + /> 247 + )} 248 + <div className="flex gap-2 w-max shrink-0 items-center text-sm text-tertiary"> 249 + <DisplayToggle setState={setState} display={display} /> 250 + <Separator classname="h-4 min-h-4!" /> 251 + 252 + {props.hasPubs || props.hasTemplates ? ( 253 + <> 254 + {props.hasPubs} 255 + {props.hasTemplates} 256 + <FilterOptions 257 + hasPubs={props.hasPubs} 258 + hasTemplates={props.hasTemplates} 259 + /> 260 + <Separator classname="h-4 min-h-4!" />{" "} 261 + </> 262 + ) : null} 263 + <SortToggle setState={setState} sort={sort} /> 264 + </div> 265 + </div> 266 + ); 267 + }; 268 + 269 + export const PublicationDashboardControls = (props: { 270 + searchValue: string; 271 + setSearchValueAction: (searchValue: string) => void; 272 + hasBackgroundImage: boolean; 273 + defaultDisplay: Exclude<DashboardState["display"], undefined>; 274 + }) => { 275 + let { display, sort } = useDashboardState(); 276 + console.log({ display, props }); 277 + display = display || props.defaultDisplay; 278 + let setState = useSetDashboardState(); 279 + return ( 280 + <div className="dashboardControls w-full flex gap-4"> 281 + <SearchInput 282 + searchValue={props.searchValue} 283 + setSearchValue={props.setSearchValueAction} 284 + hasBackgroundImage={props.hasBackgroundImage} 285 + /> 286 + <div className="flex gap-2 w-max shrink-0 items-center text-sm text-tertiary"> 287 + <DisplayToggle setState={setState} display={display} /> 288 + <Separator classname="h-4 min-h-4!" /> 289 + <SortToggle setState={setState} sort={sort} /> 290 + </div> 291 + </div> 292 + ); 293 + }; 294 + 295 + const SortToggle = (props: { 296 + setState: (partial: Partial<DashboardState>) => Promise<void>; 297 + sort: string | undefined; 298 + }) => { 299 + return ( 300 + <button 301 + onClick={() => 302 + props.setState({ 303 + sort: props.sort === "created" ? "alphabetical" : "created", 304 + }) 305 + } 306 + > 307 + Sort: {props.sort === "created" ? "Created On" : "A to Z"} 308 + </button> 309 + ); 310 + }; 311 + 312 + const DisplayToggle = (props: { 313 + setState: (partial: Partial<DashboardState>) => Promise<void>; 314 + display: string | undefined; 315 + }) => { 316 + return ( 317 + <button 318 + onClick={() => { 319 + props.setState({ 320 + display: props.display === "list" ? "grid" : "list", 321 + }); 322 + }} 323 + > 324 + {props.display === "list" ? "List" : "Grid"} 325 + </button> 326 + ); 327 + }; 328 + 329 + function Tab(props: { name: string; selected: boolean; onSelect: () => void }) { 330 + return ( 331 + <div 332 + className={`pubTabs px-1 py-0 rounded-md hover:cursor-pointer ${props.selected ? "text-accent-2 bg-accent-1 font-bold -mb-px" : "text-tertiary"}`} 333 + onClick={() => props.onSelect()} 334 + > 335 + {props.name} 336 + </div> 337 + ); 338 + } 339 + 340 + const FilterOptions = (props: { hasPubs: boolean; hasTemplates: boolean }) => { 341 + let { filter } = useDashboardState(); 342 + let setState = useSetDashboardState(); 343 + let filterCount = Object.values(filter).filter(Boolean).length; 344 + 345 + return ( 346 + <Popover 347 + className="text-sm px-2! py-1!" 348 + trigger={<div>Filter {filterCount > 0 && `(${filterCount})`}</div>} 349 + > 350 + {props.hasPubs && ( 351 + <> 352 + <Checkbox 353 + small 354 + checked={filter.drafts} 355 + onChange={(e) => 356 + setState({ 357 + filter: { ...filter, drafts: !!e.target.checked }, 358 + }) 359 + } 360 + > 361 + Drafts 362 + </Checkbox> 363 + <Checkbox 364 + small 365 + checked={filter.published} 366 + onChange={(e) => 367 + setState({ 368 + filter: { ...filter, published: !!e.target.checked }, 369 + }) 370 + } 371 + > 372 + Published 373 + </Checkbox> 374 + </> 375 + )} 376 + 377 + {props.hasTemplates && ( 378 + <> 379 + <Checkbox 380 + small 381 + checked={filter.templates} 382 + onChange={(e) => 383 + setState({ 384 + filter: { ...filter, templates: !!e.target.checked }, 385 + }) 386 + } 387 + > 388 + Templates 389 + </Checkbox> 390 + </> 391 + )} 392 + <Checkbox 393 + small 394 + checked={filter.docs} 395 + onChange={(e) => 396 + setState({ 397 + filter: { ...filter, docs: !!e.target.checked }, 398 + }) 399 + } 400 + > 401 + Docs 402 + </Checkbox> 403 + <hr className="border-border-light mt-1 mb-0.5" /> 404 + <button 405 + className="flex gap-1 items-center -mx-[2px] text-tertiary" 406 + onClick={() => { 407 + setState({ 408 + filter: { 409 + docs: false, 410 + published: false, 411 + drafts: false, 412 + templates: false, 413 + }, 414 + }); 415 + }} 416 + > 417 + <CloseTiny className="scale-75" /> Clear 418 + </button> 419 + </Popover> 420 + ); 421 + }; 422 + 423 + const SearchInput = (props: { 424 + searchValue: string; 425 + setSearchValue: (searchValue: string) => void; 426 + hasBackgroundImage: boolean; 427 + }) => { 428 + return ( 429 + <div className="relative grow shrink-0"> 430 + <Input 431 + className={`dashboardSearchInput 432 + appearance-none! outline-hidden! 433 + w-full min-w-0 text-primary relative pl-7 pr-1 -my-px 434 + border rounded-md border-transparent focus-within:border-border 435 + bg-transparent ${props.hasBackgroundImage ? "focus-within:bg-bg-page" : "focus-within:bg-bg-leaflet"} `} 436 + type="text" 437 + id="pubName" 438 + size={1} 439 + placeholder="search..." 440 + value={props.searchValue} 441 + onChange={(e) => { 442 + props.setSearchValue(e.currentTarget.value); 443 + }} 444 + /> 445 + <div className="absolute left-[6px] top-[4px] text-tertiary"> 446 + <SearchTiny /> 447 + </div> 448 + </div> 449 + ); 450 + };
+9
components/PageLayouts/NotFoundLayout.tsx
··· 1 + export const NotFoundLayout = (props: { children: React.ReactNode }) => { 2 + return ( 3 + <div className="w-screen h-full flex place-items-center bg-bg-leaflet p-4"> 4 + <div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-center gap-1 w-fit"> 5 + {props.children} 6 + </div> 7 + </div> 8 + ); 9 + };
+7 -24
components/Pages/PublicationMetadata.tsx
··· 18 18 } from "app/lish/createPub/getPublicationURL"; 19 19 import { useSubscribe } from "src/replicache/useSubscribe"; 20 20 import { useEntitySetContext } from "components/EntitySetProvider"; 21 + import { timeAgo } from "src/utils/timeAgo"; 21 22 export const PublicationMetadata = ({ 22 23 cardBorderHidden, 23 24 }: { ··· 59 60 </div> 60 61 <AsyncValueAutosizeTextarea 61 62 disabled={!permissions.write} 62 - className="text-xl font-bold outline-none bg-transparent" 63 + className="text-xl font-bold outline-hidden bg-transparent" 63 64 value={title} 64 65 onChange={async (e) => { 65 66 await rep?.mutate.updatePublicationDraft({ ··· 72 73 <AsyncValueAutosizeTextarea 73 74 disabled={!permissions.write} 74 75 placeholder="add an optional description..." 75 - className="italic text-secondary outline-none bg-transparent" 76 + className="italic text-secondary outline-hidden bg-transparent" 76 77 value={description} 77 78 onChange={async (e) => { 78 79 await rep?.mutate.updatePublicationDraft({ ··· 84 85 {pub.doc ? ( 85 86 <div className="flex flex-row items-center gap-2 pt-3"> 86 87 <p className="text-sm text-tertiary"> 87 - Published{" "} 88 - {publishedAt && 89 - new Date(publishedAt).toLocaleString(undefined, { 90 - year: "numeric", 91 - month: "2-digit", 92 - day: "2-digit", 93 - hour: "2-digit", 94 - minute: "2-digit", 95 - hour12: true, 96 - })} 88 + Published {publishedAt && timeAgo(publishedAt)} 97 89 </p> 98 90 <Separator classname="h-4" /> 99 91 <Link ··· 125 117 </div> 126 118 127 119 <div 128 - className={`text-xl font-bold outline-none bg-transparent ${!pub.title && "text-tertiary italic"}`} 120 + className={`text-xl font-bold outline-hidden bg-transparent ${!pub.title && "text-tertiary italic"}`} 129 121 > 130 122 {pub.title ? pub.title : "Untitled"} 131 123 </div> 132 - <div className="italic text-secondary outline-none bg-transparent"> 124 + <div className="italic text-secondary outline-hidden bg-transparent"> 133 125 {pub.description} 134 126 </div> 135 127 136 128 {pub.doc ? ( 137 129 <div className="flex flex-row items-center gap-2 pt-3"> 138 130 <p className="text-sm text-tertiary"> 139 - Published{" "} 140 - {publishedAt && 141 - new Date(publishedAt).toLocaleString(undefined, { 142 - year: "numeric", 143 - month: "2-digit", 144 - day: "2-digit", 145 - hour: "2-digit", 146 - minute: "2-digit", 147 - hour12: true, 148 - })} 131 + Published {publishedAt && timeAgo(publishedAt)} 149 132 </p> 150 133 </div> 151 134 ) : (
+3 -3
components/Pages/index.tsx
··· 119 119 : "rgba(var(--bg-page), var(--bg-page-alpha))", 120 120 }} 121 121 className={` 122 - ${pageType === "canvas" ? "!lg:max-w-[1152px]" : "max-w-[var(--page-width-units)]"} 122 + ${pageType === "canvas" ? "!lg:max-w-[1152px]" : "max-w-(--page-width-units)"} 123 123 page 124 124 grow flex flex-col 125 125 overscroll-y-none 126 126 overflow-y-auto 127 - ${cardBorderHidden ? "border-0 !shadow-none sm:-mt-6 sm:-mb-12 -mt-2 -mb-1 pt-3 " : "border rounded-lg"} 127 + ${cardBorderHidden ? "border-0 shadow-none! sm:-mt-6 sm:-mb-12 -mt-2 -mb-1 pt-3 " : "border rounded-lg"} 128 128 ${isFocused ? "shadow-md border-border" : "border-border-light"} 129 129 `} 130 130 > ··· 359 359 trigger={ 360 360 <PageOptionButton 361 361 cardBorderHidden={props.cardBorderHidden} 362 - className="!w-8 !h-5 sm:!w-5 sm:!h-8" 362 + className="w-8! h-5! sm:w-5! sm:h-8!" 363 363 > 364 364 <MoreOptionsTiny className="sm:rotate-90" /> 365 365 </PageOptionButton>
+4 -2
components/Popover.tsx
··· 17 17 className?: string; 18 18 open?: boolean; 19 19 onOpenChange?: (open: boolean) => void; 20 + onOpenAutoFocus?: (e: Event) => void; 20 21 asChild?: boolean; 21 22 arrowFill?: string; 22 23 }) => { ··· 39 40 className={` 40 41 z-20 bg-bg-page 41 42 px-3 py-2 42 - max-w-[var(--radix-popover-content-available-width)] 43 - max-h-[var(--radix-popover-content-available-height)] 43 + max-w-(--radix-popover-content-available-width) 44 + max-h-(--radix-popover-content-available-height) 44 45 border border-border rounded-md shadow-md 45 46 overflow-y-scroll no-scrollbar 46 47 ${props.className} ··· 49 50 align={props.align ? props.align : "center"} 50 51 sideOffset={4} 51 52 collisionPadding={16} 53 + onOpenAutoFocus={props.onOpenAutoFocus} 52 54 > 53 55 {props.children} 54 56 <RadixPopover.Arrow
+1 -1
components/ShareOptions/DomainOptions.tsx
··· 234 234 <Input 235 235 type="text" 236 236 autoFocus 237 - className="appearance-none focus:outline-none font-normal text-accent-2 w-full bg-transparent placeholder:text-accent-2 placeholder:opacity-50" 237 + className="appearance-none focus:outline-hidden font-normal text-accent-2 w-full bg-transparent placeholder:text-accent-2 placeholder:opacity-50" 238 238 placeholder="add-optional-path" 239 239 onChange={(e) => props.setSelectedRoute(e.target.value)} 240 240 value={props.selectedRoute}
+1 -1
components/ShareOptions/index.tsx
··· 6 6 import { Menu, MenuItem } from "components/Layout"; 7 7 import { ActionButton } from "components/ActionBar/ActionButton"; 8 8 import useSWR from "swr"; 9 - import { useTemplateState } from "app/home/CreateNewButton"; 9 + import { useTemplateState } from "app/home/Actions/CreateNewButton"; 10 10 import LoginForm from "app/login/LoginForm"; 11 11 import { CustomDomainMenu } from "./DomainOptions"; 12 12 import { useIdentityData } from "components/IdentityProvider";
+3 -3
components/ThemeManager/Pickers/ColorPicker.tsx
··· 17 17 import { onMouseDown } from "src/utils/iosInputMouseDown"; 18 18 19 19 export let thumbStyle = 20 - "w-4 h-4 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C,_inset_0_0_0_1px_#8C8C8C]"; 20 + "w-4 h-4 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"; 21 21 22 22 export const ColorPicker = (props: { 23 23 label?: string; ··· 80 80 onBlur={(e) => { 81 81 props.setValue(parseColor(e.currentTarget.value)); 82 82 }} 83 - className="w-[72px] bg-transparent outline-none disabled:text-tertiary" 83 + className="w-[72px] bg-transparent outline-hidden disabled:text-tertiary" 84 84 /> 85 85 </ColorField> 86 86 )} ··· 105 105 e.currentTarget.blur(); 106 106 } else return; 107 107 }} 108 - className="w-[72px] bg-transparent outline-none " 108 + className="w-[72px] bg-transparent outline-hidden " 109 109 /> 110 110 </ColorField> 111 111 </>
+5 -5
components/ThemeManager/Pickers/ImagePicker.tsx
··· 32 32 <div className="themeBGImageControls font-bold flex flex-col gap-1 items-center px-3"> 33 33 <label htmlFor="cover" className="w-full"> 34 34 <Radio 35 - radioCheckedClassName="!text-[#595959]" 36 - radioEmptyClassName="!text-[#969696]" 35 + radioCheckedClassName="text-[#595959]!" 36 + radioEmptyClassName="text-[#969696]!" 37 37 type="radio" 38 38 id="cover" 39 39 name="bg-image-options" ··· 58 58 id="repeat" 59 59 name="bg-image-options" 60 60 value="repeat" 61 - radioCheckedClassName="!text-[#595959]" 62 - radioEmptyClassName="!text-[#969696]" 61 + radioCheckedClassName="text-[#595959]!" 62 + radioEmptyClassName="text-[#969696]!" 63 63 checked={!!repeat} 64 64 onChange={async (e) => { 65 65 if (!e.currentTarget.checked) return; ··· 128 128 className={` 129 129 flex w-4 h-4 rounded-full border-2 border-white cursor-pointer 130 130 ${repeat ? "bg-[#595959]" : " bg-[#C3C3C3] "} 131 - ${repeat && "shadow-[0_0_0_1px_#8C8C8C,_inset_0_0_0_1px_#8C8C8C]"} `} 131 + ${repeat && "shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"} `} 132 132 aria-label="Volume" 133 133 /> 134 134 </Slider.Root>
+5 -5
components/ThemeManager/Pickers/PageThemePickers.tsx
··· 213 213 }); 214 214 }} 215 215 > 216 - <Separator classname="!h-4 my-1 !border-[#C3C3C3]" /> 216 + <Separator classname="h-4! my-1 border-[#C3C3C3]!" /> 217 217 <ColorField className="w-fit pl-[6px]" channel="alpha"> 218 218 <Input 219 219 disabled={props.disabled} ··· 229 229 e.currentTarget.blur(); 230 230 } else return; 231 231 }} 232 - className={`w-[48px] bg-transparent outline-none disabled:text-[#969696]`} 232 + className={`w-[48px] bg-transparent outline-hidden disabled:text-[#969696]`} 233 233 /> 234 234 </ColorField> 235 235 </SpectrumColorPicker> ··· 305 305 return ( 306 306 <div className="flex gap-2 h-8 "> 307 307 <button 308 - className={`w-full rounded-md bg-bg-page border ${selectedPattern === "grid" ? "outline outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 308 + className={`w-full rounded-md bg-bg-page border ${selectedPattern === "grid" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 309 309 onMouseDown={() => { 310 310 props.rep && 311 311 props.rep.mutate.assertFact({ ··· 318 318 <CanvasBackgroundPattern pattern="grid" scale={0.5} /> 319 319 </button> 320 320 <button 321 - className={`w-full rounded-md bg-bg-page border ${selectedPattern === "dot" ? "outline outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 321 + className={`w-full rounded-md bg-bg-page border ${selectedPattern === "dot" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 322 322 onMouseDown={() => { 323 323 props.rep && 324 324 props.rep.mutate.assertFact({ ··· 331 331 <CanvasBackgroundPattern pattern="dot" scale={0.5} /> 332 332 </button> 333 333 <button 334 - className={`w-full rounded-md bg-bg-page border ${selectedPattern === "plain" ? "outline outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 334 + className={`w-full rounded-md bg-bg-page border ${selectedPattern === "plain" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 335 335 onMouseDown={() => { 336 336 props.rep && 337 337 props.rep.mutate.assertFact({
+5 -5
components/ThemeManager/PubPickers/PubBackgroundPickers.tsx
··· 216 216 <div className="themeBGImageControls font-bold flex flex-col gap-1 items-center px-3"> 217 217 <label htmlFor="cover" className="w-full"> 218 218 <Radio 219 - radioCheckedClassName="!text-[#595959]" 220 - radioEmptyClassName="!text-[#969696]" 219 + radioCheckedClassName="text-[#595959]!" 220 + radioEmptyClassName="text-[#969696]!" 221 221 type="radio" 222 222 id="cover" 223 223 name="bg-image-options" ··· 242 242 id="repeat" 243 243 name="bg-image-options" 244 244 value="repeat" 245 - radioCheckedClassName="!text-[#595959]" 246 - radioEmptyClassName="!text-[#969696]" 245 + radioCheckedClassName="text-[#595959]!" 246 + radioEmptyClassName="text-[#969696]!" 247 247 checked={!!props.bgImage?.repeat} 248 248 onChange={async (e) => { 249 249 if (!e.currentTarget.checked) return; ··· 296 296 className={` 297 297 flex w-4 h-4 rounded-full border-2 border-white cursor-pointer 298 298 ${props.bgImage?.repeat ? "bg-[#595959]" : " bg-[#C3C3C3] "} 299 - ${props.bgImage?.repeat && "shadow-[0_0_0_1px_#8C8C8C,_inset_0_0_0_1px_#8C8C8C]"} `} 299 + ${props.bgImage?.repeat && "shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"} `} 300 300 aria-label="Volume" 301 301 /> 302 302 </Slider.Root>
+10 -17
components/ThemeManager/PubThemeSetter.tsx
··· 27 27 let [loading, setLoading] = useState(false); 28 28 let [sample, setSample] = useState<"pub" | "post">("pub"); 29 29 let [openPicker, setOpenPicker] = useState<pickers>("null"); 30 - let { data: pub, mutate } = usePublicationData(); 30 + let { data, mutate } = usePublicationData(); 31 + let { publication: pub } = data || {}; 31 32 let record = pub?.record as PubLeafletPublication.Record | undefined; 32 33 let [showPageBackground, setShowPageBackground] = useState( 33 34 !!record?.theme?.showPageBackground, ··· 87 88 }} 88 89 > 89 90 <h4 className="text-accent-2">Publication Theme</h4> 90 - <ButtonSecondary 91 - compact 92 - disabled={ 93 - !( 94 - showPageBackground === !!record?.theme?.showPageBackground || 95 - changes || 96 - !!image?.file || 97 - record?.theme?.backgroundImage?.width !== image?.repeat 98 - ) 99 - } 100 - > 91 + <ButtonSecondary compact> 101 92 {loading ? <DotLoader /> : "Update"} 102 93 </ButtonSecondary> 103 94 </form> ··· 130 121 <SectionArrow 131 122 fill="white" 132 123 stroke="#CCCCCC" 133 - className="ml-2 -mt-[1px]" 124 + className="ml-2 -mt-px" 134 125 /> 135 126 </div> 136 127 </div> ··· 177 168 <div className="flex flex-col mt-4 "> 178 169 <div className="flex gap-2 items-center text-sm text-[#8C8C8C]"> 179 170 <div className="text-sm">Preview</div> 180 - <Separator classname="!h-4" />{" "} 171 + <Separator classname="h-4!" />{" "} 181 172 <button 182 173 className={`${sample === "pub" ? "font-bold text-[#595959]" : ""}`} 183 174 onClick={() => setSample("pub")} ··· 216 207 pubBGRepeat: number | null; 217 208 showPageBackground: boolean; 218 209 }) => { 219 - let { data: publication } = usePublicationData(); 210 + let { data } = usePublicationData(); 211 + let { publication } = data || {}; 220 212 let record = publication?.record as PubLeafletPublication.Record | null; 221 213 222 214 return ( ··· 260 252 <div className="text-[7px] font-normal text-tertiary"> 261 253 {record?.description} 262 254 </div> 263 - <div className=" flex gap-1 items-center mt-[6px] bg-accent-1 text-accent-2 py-[1px] px-[4px] text-[7px] w-fit font-bold rounded-[2px] mx-auto"> 255 + <div className=" flex gap-1 items-center mt-[6px] bg-accent-1 text-accent-2 py-px px-[4px] text-[7px] w-fit font-bold rounded-[2px] mx-auto"> 264 256 <div className="h-[7px] w-[7px] rounded-full bg-accent-2" /> 265 257 Subscribe with Bluesky 266 258 </div> ··· 283 275 pubBGRepeat: number | null; 284 276 showPageBackground: boolean; 285 277 }) => { 286 - let { data: publication } = usePublicationData(); 278 + let { data } = usePublicationData(); 279 + let { publication } = data || {}; 287 280 let record = publication?.record as PubLeafletPublication.Record | null; 288 281 return ( 289 282 <div
+2 -1
components/ThemeManager/PublicationThemeProvider.tsx
··· 49 49 children: React.ReactNode; 50 50 record?: PubLeafletPublication.Record | null; 51 51 }) { 52 - let { data: pub } = usePublicationData(); 52 + let { data } = usePublicationData(); 53 + let { publication: pub } = data || {}; 53 54 return ( 54 55 <PublicationThemeProvider 55 56 pub_creator={pub?.identity_did || ""}
+1 -1
components/ThemeManager/ThemeSetter.tsx
··· 114 114 <SectionArrow 115 115 fill="white" 116 116 stroke="#CCCCCC" 117 - className="ml-2 -mt-[1px]" 117 + className="ml-2 -mt-px" 118 118 /> 119 119 </div> 120 120 </div>
+1 -1
components/Toolbar/InlineLinkToolbar.tsx
··· 135 135 <Separator classname="h-6" /> 136 136 <Input 137 137 autoFocus 138 - className="w-full grow bg-transparent border-none outline-none " 138 + className="w-full grow bg-transparent border-none outline-hidden " 139 139 placeholder="www.example.com" 140 140 value={linkValue} 141 141 onChange={(e) => setLinkValue(e.target.value)}
+68
components/Tooltip.tsx
··· 1 + import * as RadixTooltip from "@radix-ui/react-tooltip"; 2 + import { PopoverArrow } from "./Icons/PopoverArrow"; 3 + import { theme } from "tailwind.config"; 4 + import { NestedCardThemeProvider } from "./ThemeManager/ThemeProvider"; 5 + 6 + export const Tooltip = (props: { 7 + trigger: React.ReactNode; 8 + disabled?: boolean; 9 + children: React.ReactNode; 10 + delayDuration?: number; 11 + skipDelayDuration?: number; 12 + align?: "start" | "end" | "center"; 13 + side?: "top" | "bottom" | "left" | "right"; 14 + background?: string; 15 + border?: string; 16 + className?: string; 17 + open?: boolean; 18 + onOpenChange?: (open: boolean) => void; 19 + asChild?: boolean; 20 + arrowFill?: string; 21 + }) => { 22 + return ( 23 + <RadixTooltip.Provider 24 + delayDuration={props.delayDuration ? props.delayDuration : 600} 25 + skipDelayDuration={ 26 + props.skipDelayDuration ? props.skipDelayDuration : 300 27 + } 28 + > 29 + <RadixTooltip.Root> 30 + <RadixTooltip.Trigger disabled={props.disabled} asChild={props.asChild}> 31 + {props.trigger} 32 + </RadixTooltip.Trigger> 33 + <RadixTooltip.Portal> 34 + <NestedCardThemeProvider> 35 + <RadixTooltip.Content 36 + className={` 37 + z-20 bg-bg-page 38 + px-3 py-2 39 + max-w-(--radix-popover-content-available-width) 40 + max-h-(--radix-popover-content-available-height) 41 + border border-border rounded-md shadow-md 42 + overflow-y-scroll no-scrollbar 43 + ${props.className} 44 + `} 45 + side={props.side} 46 + align={props.align ? props.align : "center"} 47 + sideOffset={4} 48 + collisionPadding={16} 49 + > 50 + {props.children} 51 + <RadixTooltip.Arrow 52 + asChild 53 + width={16} 54 + height={8} 55 + viewBox="0 0 16 8" 56 + > 57 + <PopoverArrow 58 + arrowFill={theme.colors["border"]} 59 + arrowStroke="transparent" 60 + /> 61 + </RadixTooltip.Arrow> 62 + </RadixTooltip.Content> 63 + </NestedCardThemeProvider> 64 + </RadixTooltip.Portal> 65 + </RadixTooltip.Root> 66 + </RadixTooltip.Provider> 67 + ); 68 + };
+602 -856
package-lock.json
··· 86 86 "devDependencies": { 87 87 "@atproto/lex-cli": "^0.9.5", 88 88 "@cloudflare/workers-types": "^4.20240512.0", 89 + "@tailwindcss/postcss": "^4.1.13", 89 90 "@types/katex": "^0.16.7", 90 91 "@types/node": "^22.15.17", 91 92 "@types/react": "19.1.3", 92 93 "@types/react-dom": "19.1.3", 93 94 "@types/uuid": "^10.0.0", 94 - "autoprefixer": "^10.4.19", 95 95 "drizzle-kit": "^0.21.2", 96 96 "esbuild": "^0.25.4", 97 97 "eslint": "8.57.0", ··· 99 99 "postcss": "^8.4.38", 100 100 "prettier": "3.2.5", 101 101 "supabase": "^1.187.3", 102 - "tailwindcss": "^3.4.3", 102 + "tailwindcss": "^4.1.13", 103 103 "tsx": "^4.19.3", 104 104 "typescript": "^5.8.3", 105 105 "wrangler": "^3.56.0" ··· 110 110 "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", 111 111 "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", 112 112 "dev": true, 113 + "license": "MIT", 113 114 "engines": { 114 115 "node": ">=10" 115 116 }, ··· 1144 1145 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 1145 1146 "license": "(Apache-2.0 AND MIT)" 1146 1147 }, 1147 - "node_modules/@isaacs/cliui": { 1148 - "version": "8.0.2", 1149 - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 1150 - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 1151 - "dev": true, 1152 - "dependencies": { 1153 - "string-width": "^5.1.2", 1154 - "string-width-cjs": "npm:string-width@^4.2.0", 1155 - "strip-ansi": "^7.0.1", 1156 - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 1157 - "wrap-ansi": "^8.1.0", 1158 - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 1159 - }, 1160 - "engines": { 1161 - "node": ">=12" 1162 - } 1163 - }, 1164 - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { 1165 - "version": "6.0.1", 1166 - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 1167 - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 1168 - "dev": true, 1169 - "engines": { 1170 - "node": ">=12" 1171 - }, 1172 - "funding": { 1173 - "url": "https://github.com/chalk/ansi-regex?sponsor=1" 1174 - } 1175 - }, 1176 - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { 1177 - "version": "6.2.1", 1178 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 1179 - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 1180 - "dev": true, 1181 - "engines": { 1182 - "node": ">=12" 1183 - }, 1184 - "funding": { 1185 - "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1186 - } 1187 - }, 1188 - "node_modules/@isaacs/cliui/node_modules/string-width": { 1189 - "version": "5.1.2", 1190 - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1191 - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1192 - "dev": true, 1193 - "dependencies": { 1194 - "eastasianwidth": "^0.2.0", 1195 - "emoji-regex": "^9.2.2", 1196 - "strip-ansi": "^7.0.1" 1197 - }, 1198 - "engines": { 1199 - "node": ">=12" 1200 - }, 1201 - "funding": { 1202 - "url": "https://github.com/sponsors/sindresorhus" 1203 - } 1204 - }, 1205 - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { 1206 - "version": "7.1.0", 1207 - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1208 - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1209 - "dev": true, 1210 - "dependencies": { 1211 - "ansi-regex": "^6.0.1" 1212 - }, 1213 - "engines": { 1214 - "node": ">=12" 1215 - }, 1216 - "funding": { 1217 - "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1218 - } 1219 - }, 1220 - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { 1221 - "version": "8.1.0", 1222 - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1223 - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1224 - "dev": true, 1225 - "dependencies": { 1226 - "ansi-styles": "^6.1.0", 1227 - "string-width": "^5.0.1", 1228 - "strip-ansi": "^7.0.1" 1229 - }, 1230 - "engines": { 1231 - "node": ">=12" 1232 - }, 1233 - "funding": { 1234 - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1235 - } 1236 - }, 1237 1148 "node_modules/@isaacs/fs-minipass": { 1238 1149 "version": "4.0.1", 1239 1150 "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", ··· 1253 1164 "license": "MIT" 1254 1165 }, 1255 1166 "node_modules/@jridgewell/gen-mapping": { 1256 - "version": "0.3.5", 1257 - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 1258 - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 1167 + "version": "0.3.13", 1168 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 1169 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 1259 1170 "dev": true, 1171 + "license": "MIT", 1260 1172 "dependencies": { 1261 - "@jridgewell/set-array": "^1.2.1", 1262 - "@jridgewell/sourcemap-codec": "^1.4.10", 1173 + "@jridgewell/sourcemap-codec": "^1.5.0", 1263 1174 "@jridgewell/trace-mapping": "^0.3.24" 1264 - }, 1265 - "engines": { 1266 - "node": ">=6.0.0" 1175 + } 1176 + }, 1177 + "node_modules/@jridgewell/remapping": { 1178 + "version": "2.3.5", 1179 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 1180 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 1181 + "dev": true, 1182 + "license": "MIT", 1183 + "dependencies": { 1184 + "@jridgewell/gen-mapping": "^0.3.5", 1185 + "@jridgewell/trace-mapping": "^0.3.24" 1267 1186 } 1268 1187 }, 1269 1188 "node_modules/@jridgewell/resolve-uri": { ··· 1275 1194 "node": ">=6.0.0" 1276 1195 } 1277 1196 }, 1278 - "node_modules/@jridgewell/set-array": { 1279 - "version": "1.2.1", 1280 - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 1281 - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 1282 - "dev": true, 1283 - "engines": { 1284 - "node": ">=6.0.0" 1285 - } 1286 - }, 1287 1197 "node_modules/@jridgewell/sourcemap-codec": { 1288 - "version": "1.4.15", 1289 - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 1290 - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 1291 - "dev": true 1198 + "version": "1.5.5", 1199 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 1200 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 1201 + "dev": true, 1202 + "license": "MIT" 1292 1203 }, 1293 1204 "node_modules/@jridgewell/trace-mapping": { 1294 - "version": "0.3.25", 1295 - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 1296 - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 1205 + "version": "0.3.31", 1206 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 1207 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 1297 1208 "dev": true, 1209 + "license": "MIT", 1298 1210 "dependencies": { 1299 1211 "@jridgewell/resolve-uri": "^3.1.0", 1300 1212 "@jridgewell/sourcemap-codec": "^1.4.14" ··· 3005 2917 }, 3006 2918 "peerDependencies": { 3007 2919 "@opentelemetry/api": "^1.1.0" 3008 - } 3009 - }, 3010 - "node_modules/@pkgjs/parseargs": { 3011 - "version": "0.11.0", 3012 - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 3013 - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 3014 - "dev": true, 3015 - "optional": true, 3016 - "engines": { 3017 - "node": ">=14" 3018 2920 } 3019 2921 }, 3020 2922 "node_modules/@polka/url": { ··· 5871 5773 "tslib": "^2.8.0" 5872 5774 } 5873 5775 }, 5776 + "node_modules/@tailwindcss/node": { 5777 + "version": "4.1.13", 5778 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", 5779 + "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", 5780 + "dev": true, 5781 + "license": "MIT", 5782 + "dependencies": { 5783 + "@jridgewell/remapping": "^2.3.4", 5784 + "enhanced-resolve": "^5.18.3", 5785 + "jiti": "^2.5.1", 5786 + "lightningcss": "1.30.1", 5787 + "magic-string": "^0.30.18", 5788 + "source-map-js": "^1.2.1", 5789 + "tailwindcss": "4.1.13" 5790 + } 5791 + }, 5792 + "node_modules/@tailwindcss/node/node_modules/magic-string": { 5793 + "version": "0.30.19", 5794 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", 5795 + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", 5796 + "dev": true, 5797 + "license": "MIT", 5798 + "dependencies": { 5799 + "@jridgewell/sourcemap-codec": "^1.5.5" 5800 + } 5801 + }, 5802 + "node_modules/@tailwindcss/oxide": { 5803 + "version": "4.1.13", 5804 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", 5805 + "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", 5806 + "dev": true, 5807 + "hasInstallScript": true, 5808 + "license": "MIT", 5809 + "dependencies": { 5810 + "detect-libc": "^2.0.4", 5811 + "tar": "^7.4.3" 5812 + }, 5813 + "engines": { 5814 + "node": ">= 10" 5815 + }, 5816 + "optionalDependencies": { 5817 + "@tailwindcss/oxide-android-arm64": "4.1.13", 5818 + "@tailwindcss/oxide-darwin-arm64": "4.1.13", 5819 + "@tailwindcss/oxide-darwin-x64": "4.1.13", 5820 + "@tailwindcss/oxide-freebsd-x64": "4.1.13", 5821 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", 5822 + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", 5823 + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", 5824 + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", 5825 + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", 5826 + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", 5827 + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", 5828 + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" 5829 + } 5830 + }, 5831 + "node_modules/@tailwindcss/oxide-android-arm64": { 5832 + "version": "4.1.13", 5833 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", 5834 + "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", 5835 + "cpu": [ 5836 + "arm64" 5837 + ], 5838 + "dev": true, 5839 + "license": "MIT", 5840 + "optional": true, 5841 + "os": [ 5842 + "android" 5843 + ], 5844 + "engines": { 5845 + "node": ">= 10" 5846 + } 5847 + }, 5848 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 5849 + "version": "4.1.13", 5850 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", 5851 + "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", 5852 + "cpu": [ 5853 + "arm64" 5854 + ], 5855 + "dev": true, 5856 + "license": "MIT", 5857 + "optional": true, 5858 + "os": [ 5859 + "darwin" 5860 + ], 5861 + "engines": { 5862 + "node": ">= 10" 5863 + } 5864 + }, 5865 + "node_modules/@tailwindcss/oxide-darwin-x64": { 5866 + "version": "4.1.13", 5867 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", 5868 + "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", 5869 + "cpu": [ 5870 + "x64" 5871 + ], 5872 + "dev": true, 5873 + "license": "MIT", 5874 + "optional": true, 5875 + "os": [ 5876 + "darwin" 5877 + ], 5878 + "engines": { 5879 + "node": ">= 10" 5880 + } 5881 + }, 5882 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 5883 + "version": "4.1.13", 5884 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", 5885 + "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", 5886 + "cpu": [ 5887 + "x64" 5888 + ], 5889 + "dev": true, 5890 + "license": "MIT", 5891 + "optional": true, 5892 + "os": [ 5893 + "freebsd" 5894 + ], 5895 + "engines": { 5896 + "node": ">= 10" 5897 + } 5898 + }, 5899 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 5900 + "version": "4.1.13", 5901 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", 5902 + "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", 5903 + "cpu": [ 5904 + "arm" 5905 + ], 5906 + "dev": true, 5907 + "license": "MIT", 5908 + "optional": true, 5909 + "os": [ 5910 + "linux" 5911 + ], 5912 + "engines": { 5913 + "node": ">= 10" 5914 + } 5915 + }, 5916 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 5917 + "version": "4.1.13", 5918 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", 5919 + "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", 5920 + "cpu": [ 5921 + "arm64" 5922 + ], 5923 + "dev": true, 5924 + "license": "MIT", 5925 + "optional": true, 5926 + "os": [ 5927 + "linux" 5928 + ], 5929 + "engines": { 5930 + "node": ">= 10" 5931 + } 5932 + }, 5933 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 5934 + "version": "4.1.13", 5935 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", 5936 + "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", 5937 + "cpu": [ 5938 + "arm64" 5939 + ], 5940 + "dev": true, 5941 + "license": "MIT", 5942 + "optional": true, 5943 + "os": [ 5944 + "linux" 5945 + ], 5946 + "engines": { 5947 + "node": ">= 10" 5948 + } 5949 + }, 5950 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 5951 + "version": "4.1.13", 5952 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", 5953 + "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", 5954 + "cpu": [ 5955 + "x64" 5956 + ], 5957 + "dev": true, 5958 + "license": "MIT", 5959 + "optional": true, 5960 + "os": [ 5961 + "linux" 5962 + ], 5963 + "engines": { 5964 + "node": ">= 10" 5965 + } 5966 + }, 5967 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 5968 + "version": "4.1.13", 5969 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", 5970 + "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", 5971 + "cpu": [ 5972 + "x64" 5973 + ], 5974 + "dev": true, 5975 + "license": "MIT", 5976 + "optional": true, 5977 + "os": [ 5978 + "linux" 5979 + ], 5980 + "engines": { 5981 + "node": ">= 10" 5982 + } 5983 + }, 5984 + "node_modules/@tailwindcss/oxide-wasm32-wasi": { 5985 + "version": "4.1.13", 5986 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", 5987 + "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", 5988 + "bundleDependencies": [ 5989 + "@napi-rs/wasm-runtime", 5990 + "@emnapi/core", 5991 + "@emnapi/runtime", 5992 + "@tybys/wasm-util", 5993 + "@emnapi/wasi-threads", 5994 + "tslib" 5995 + ], 5996 + "cpu": [ 5997 + "wasm32" 5998 + ], 5999 + "dev": true, 6000 + "license": "MIT", 6001 + "optional": true, 6002 + "dependencies": { 6003 + "@emnapi/core": "^1.4.5", 6004 + "@emnapi/runtime": "^1.4.5", 6005 + "@emnapi/wasi-threads": "^1.0.4", 6006 + "@napi-rs/wasm-runtime": "^0.2.12", 6007 + "@tybys/wasm-util": "^0.10.0", 6008 + "tslib": "^2.8.0" 6009 + }, 6010 + "engines": { 6011 + "node": ">=14.0.0" 6012 + } 6013 + }, 6014 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 6015 + "version": "4.1.13", 6016 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", 6017 + "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", 6018 + "cpu": [ 6019 + "arm64" 6020 + ], 6021 + "dev": true, 6022 + "license": "MIT", 6023 + "optional": true, 6024 + "os": [ 6025 + "win32" 6026 + ], 6027 + "engines": { 6028 + "node": ">= 10" 6029 + } 6030 + }, 6031 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 6032 + "version": "4.1.13", 6033 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", 6034 + "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", 6035 + "cpu": [ 6036 + "x64" 6037 + ], 6038 + "dev": true, 6039 + "license": "MIT", 6040 + "optional": true, 6041 + "os": [ 6042 + "win32" 6043 + ], 6044 + "engines": { 6045 + "node": ">= 10" 6046 + } 6047 + }, 6048 + "node_modules/@tailwindcss/oxide/node_modules/tar": { 6049 + "version": "7.5.1", 6050 + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", 6051 + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", 6052 + "dev": true, 6053 + "license": "ISC", 6054 + "dependencies": { 6055 + "@isaacs/fs-minipass": "^4.0.0", 6056 + "chownr": "^3.0.0", 6057 + "minipass": "^7.1.2", 6058 + "minizlib": "^3.1.0", 6059 + "yallist": "^5.0.0" 6060 + }, 6061 + "engines": { 6062 + "node": ">=18" 6063 + } 6064 + }, 6065 + "node_modules/@tailwindcss/postcss": { 6066 + "version": "4.1.13", 6067 + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz", 6068 + "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", 6069 + "dev": true, 6070 + "license": "MIT", 6071 + "dependencies": { 6072 + "@alloc/quick-lru": "^5.2.0", 6073 + "@tailwindcss/node": "4.1.13", 6074 + "@tailwindcss/oxide": "4.1.13", 6075 + "postcss": "^8.4.41", 6076 + "tailwindcss": "4.1.13" 6077 + } 6078 + }, 5874 6079 "node_modules/@tiptap/core": { 5875 6080 "version": "2.11.5", 5876 6081 "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz", ··· 6600 6805 "url": "https://github.com/chalk/ansi-styles?sponsor=1" 6601 6806 } 6602 6807 }, 6603 - "node_modules/any-promise": { 6604 - "version": "1.3.0", 6605 - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 6606 - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", 6607 - "dev": true 6608 - }, 6609 6808 "node_modules/anymatch": { 6610 6809 "version": "3.1.3", 6611 6810 "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", ··· 6618 6817 "engines": { 6619 6818 "node": ">= 8" 6620 6819 } 6621 - }, 6622 - "node_modules/arg": { 6623 - "version": "5.0.2", 6624 - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 6625 - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", 6626 - "dev": true 6627 6820 }, 6628 6821 "node_modules/argparse": { 6629 6822 "version": "2.0.1", ··· 6859 7052 "node": ">=8.0.0" 6860 7053 } 6861 7054 }, 6862 - "node_modules/autoprefixer": { 6863 - "version": "10.4.19", 6864 - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", 6865 - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", 6866 - "dev": true, 6867 - "funding": [ 6868 - { 6869 - "type": "opencollective", 6870 - "url": "https://opencollective.com/postcss/" 6871 - }, 6872 - { 6873 - "type": "tidelift", 6874 - "url": "https://tidelift.com/funding/github/npm/autoprefixer" 6875 - }, 6876 - { 6877 - "type": "github", 6878 - "url": "https://github.com/sponsors/ai" 6879 - } 6880 - ], 6881 - "dependencies": { 6882 - "browserslist": "^4.23.0", 6883 - "caniuse-lite": "^1.0.30001599", 6884 - "fraction.js": "^4.3.7", 6885 - "normalize-range": "^0.1.2", 6886 - "picocolors": "^1.0.0", 6887 - "postcss-value-parser": "^4.2.0" 6888 - }, 6889 - "bin": { 6890 - "autoprefixer": "bin/autoprefixer" 6891 - }, 6892 - "engines": { 6893 - "node": "^10 || ^12 || >=14" 6894 - }, 6895 - "peerDependencies": { 6896 - "postcss": "^8.1.0" 6897 - } 6898 - }, 6899 7055 "node_modules/available-typed-arrays": { 6900 7056 "version": "1.0.7", 6901 7057 "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", ··· 7110 7266 "node": ">=8" 7111 7267 } 7112 7268 }, 7113 - "node_modules/browserslist": { 7114 - "version": "4.23.0", 7115 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", 7116 - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", 7117 - "dev": true, 7118 - "funding": [ 7119 - { 7120 - "type": "opencollective", 7121 - "url": "https://opencollective.com/browserslist" 7122 - }, 7123 - { 7124 - "type": "tidelift", 7125 - "url": "https://tidelift.com/funding/github/npm/browserslist" 7126 - }, 7127 - { 7128 - "type": "github", 7129 - "url": "https://github.com/sponsors/ai" 7130 - } 7131 - ], 7132 - "dependencies": { 7133 - "caniuse-lite": "^1.0.30001587", 7134 - "electron-to-chromium": "^1.4.668", 7135 - "node-releases": "^2.0.14", 7136 - "update-browserslist-db": "^1.0.13" 7137 - }, 7138 - "bin": { 7139 - "browserslist": "cli.js" 7140 - }, 7141 - "engines": { 7142 - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 7143 - } 7144 - }, 7145 7269 "node_modules/buffer": { 7146 7270 "version": "6.0.3", 7147 7271 "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", ··· 7242 7366 "dev": true, 7243 7367 "engines": { 7244 7368 "node": ">=6" 7245 - } 7246 - }, 7247 - "node_modules/camelcase-css": { 7248 - "version": "2.0.1", 7249 - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 7250 - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 7251 - "dev": true, 7252 - "engines": { 7253 - "node": ">= 6" 7254 7369 } 7255 7370 }, 7256 7371 "node_modules/caniuse-lite": { ··· 7681 7796 "node": ">= 8" 7682 7797 } 7683 7798 }, 7684 - "node_modules/cssesc": { 7685 - "version": "3.0.0", 7686 - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 7687 - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 7688 - "dev": true, 7689 - "bin": { 7690 - "cssesc": "bin/cssesc" 7691 - }, 7692 - "engines": { 7693 - "node": ">=4" 7694 - } 7695 - }, 7696 7799 "node_modules/csstype": { 7697 7800 "version": "3.1.3", 7698 7801 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", ··· 7947 8050 "url": "https://github.com/sponsors/wooorm" 7948 8051 } 7949 8052 }, 7950 - "node_modules/didyoumean": { 7951 - "version": "1.2.2", 7952 - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 7953 - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", 7954 - "dev": true 7955 - }, 7956 8053 "node_modules/difflib": { 7957 8054 "version": "0.2.4", 7958 8055 "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", ··· 7965 8062 "node": "*" 7966 8063 } 7967 8064 }, 7968 - "node_modules/dlv": { 7969 - "version": "1.1.3", 7970 - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 7971 - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 7972 - "dev": true 7973 - }, 7974 8065 "node_modules/doctrine": { 7975 8066 "version": "3.0.0", 7976 8067 "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", ··· 8200 8291 "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 8201 8292 "license": "MIT" 8202 8293 }, 8203 - "node_modules/eastasianwidth": { 8204 - "version": "0.2.0", 8205 - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 8206 - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 8207 - "dev": true 8208 - }, 8209 8294 "node_modules/ecdsa-sig-formatter": { 8210 8295 "version": "1.0.11", 8211 8296 "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", ··· 8221 8306 "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 8222 8307 "license": "MIT" 8223 8308 }, 8224 - "node_modules/electron-to-chromium": { 8225 - "version": "1.4.783", 8226 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", 8227 - "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", 8228 - "dev": true 8229 - }, 8230 8309 "node_modules/emoji-regex": { 8231 8310 "version": "9.2.2", 8232 8311 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", ··· 8243 8322 } 8244 8323 }, 8245 8324 "node_modules/enhanced-resolve": { 8246 - "version": "5.16.1", 8247 - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", 8248 - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", 8325 + "version": "5.18.3", 8326 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", 8327 + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", 8249 8328 "dev": true, 8329 + "license": "MIT", 8250 8330 "dependencies": { 8251 8331 "graceful-fs": "^4.2.4", 8252 8332 "tapable": "^2.2.0" ··· 9539 9619 "url": "https://github.com/sponsors/ljharb" 9540 9620 } 9541 9621 }, 9542 - "node_modules/foreground-child": { 9543 - "version": "3.1.1", 9544 - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 9545 - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 9546 - "dev": true, 9547 - "dependencies": { 9548 - "cross-spawn": "^7.0.0", 9549 - "signal-exit": "^4.0.1" 9550 - }, 9551 - "engines": { 9552 - "node": ">=14" 9553 - }, 9554 - "funding": { 9555 - "url": "https://github.com/sponsors/isaacs" 9556 - } 9557 - }, 9558 9622 "node_modules/form-data": { 9559 9623 "version": "4.0.0", 9560 9624 "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", ··· 9594 9658 "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", 9595 9659 "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", 9596 9660 "license": "MIT" 9597 - }, 9598 - "node_modules/fraction.js": { 9599 - "version": "4.3.7", 9600 - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", 9601 - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", 9602 - "dev": true, 9603 - "engines": { 9604 - "node": "*" 9605 - }, 9606 - "funding": { 9607 - "type": "patreon", 9608 - "url": "https://github.com/sponsors/rawify" 9609 - } 9610 9661 }, 9611 9662 "node_modules/fractional-indexing": { 9612 9663 "version": "3.2.0", ··· 11224 11275 } 11225 11276 }, 11226 11277 "node_modules/jiti": { 11227 - "version": "1.21.0", 11228 - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", 11229 - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", 11278 + "version": "2.6.0", 11279 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", 11280 + "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", 11230 11281 "dev": true, 11282 + "license": "MIT", 11231 11283 "bin": { 11232 - "jiti": "bin/jiti.js" 11284 + "jiti": "lib/jiti-cli.mjs" 11233 11285 } 11234 11286 }, 11235 11287 "node_modules/jose": { ··· 11469 11521 "url": "https://github.com/sponsors/dmonad" 11470 11522 } 11471 11523 }, 11472 - "node_modules/lilconfig": { 11473 - "version": "2.1.0", 11474 - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 11475 - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 11524 + "node_modules/lightningcss": { 11525 + "version": "1.30.1", 11526 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", 11527 + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", 11476 11528 "dev": true, 11529 + "license": "MPL-2.0", 11530 + "dependencies": { 11531 + "detect-libc": "^2.0.3" 11532 + }, 11477 11533 "engines": { 11478 - "node": ">=10" 11534 + "node": ">= 12.0.0" 11535 + }, 11536 + "funding": { 11537 + "type": "opencollective", 11538 + "url": "https://opencollective.com/parcel" 11539 + }, 11540 + "optionalDependencies": { 11541 + "lightningcss-darwin-arm64": "1.30.1", 11542 + "lightningcss-darwin-x64": "1.30.1", 11543 + "lightningcss-freebsd-x64": "1.30.1", 11544 + "lightningcss-linux-arm-gnueabihf": "1.30.1", 11545 + "lightningcss-linux-arm64-gnu": "1.30.1", 11546 + "lightningcss-linux-arm64-musl": "1.30.1", 11547 + "lightningcss-linux-x64-gnu": "1.30.1", 11548 + "lightningcss-linux-x64-musl": "1.30.1", 11549 + "lightningcss-win32-arm64-msvc": "1.30.1", 11550 + "lightningcss-win32-x64-msvc": "1.30.1" 11551 + } 11552 + }, 11553 + "node_modules/lightningcss-darwin-arm64": { 11554 + "version": "1.30.1", 11555 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", 11556 + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", 11557 + "cpu": [ 11558 + "arm64" 11559 + ], 11560 + "dev": true, 11561 + "license": "MPL-2.0", 11562 + "optional": true, 11563 + "os": [ 11564 + "darwin" 11565 + ], 11566 + "engines": { 11567 + "node": ">= 12.0.0" 11568 + }, 11569 + "funding": { 11570 + "type": "opencollective", 11571 + "url": "https://opencollective.com/parcel" 11572 + } 11573 + }, 11574 + "node_modules/lightningcss-darwin-x64": { 11575 + "version": "1.30.1", 11576 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", 11577 + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", 11578 + "cpu": [ 11579 + "x64" 11580 + ], 11581 + "dev": true, 11582 + "license": "MPL-2.0", 11583 + "optional": true, 11584 + "os": [ 11585 + "darwin" 11586 + ], 11587 + "engines": { 11588 + "node": ">= 12.0.0" 11589 + }, 11590 + "funding": { 11591 + "type": "opencollective", 11592 + "url": "https://opencollective.com/parcel" 11479 11593 } 11480 11594 }, 11481 - "node_modules/lines-and-columns": { 11482 - "version": "1.2.4", 11483 - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 11484 - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 11485 - "dev": true 11595 + "node_modules/lightningcss-freebsd-x64": { 11596 + "version": "1.30.1", 11597 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", 11598 + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", 11599 + "cpu": [ 11600 + "x64" 11601 + ], 11602 + "dev": true, 11603 + "license": "MPL-2.0", 11604 + "optional": true, 11605 + "os": [ 11606 + "freebsd" 11607 + ], 11608 + "engines": { 11609 + "node": ">= 12.0.0" 11610 + }, 11611 + "funding": { 11612 + "type": "opencollective", 11613 + "url": "https://opencollective.com/parcel" 11614 + } 11615 + }, 11616 + "node_modules/lightningcss-linux-arm-gnueabihf": { 11617 + "version": "1.30.1", 11618 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", 11619 + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", 11620 + "cpu": [ 11621 + "arm" 11622 + ], 11623 + "dev": true, 11624 + "license": "MPL-2.0", 11625 + "optional": true, 11626 + "os": [ 11627 + "linux" 11628 + ], 11629 + "engines": { 11630 + "node": ">= 12.0.0" 11631 + }, 11632 + "funding": { 11633 + "type": "opencollective", 11634 + "url": "https://opencollective.com/parcel" 11635 + } 11636 + }, 11637 + "node_modules/lightningcss-linux-arm64-gnu": { 11638 + "version": "1.30.1", 11639 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", 11640 + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", 11641 + "cpu": [ 11642 + "arm64" 11643 + ], 11644 + "dev": true, 11645 + "license": "MPL-2.0", 11646 + "optional": true, 11647 + "os": [ 11648 + "linux" 11649 + ], 11650 + "engines": { 11651 + "node": ">= 12.0.0" 11652 + }, 11653 + "funding": { 11654 + "type": "opencollective", 11655 + "url": "https://opencollective.com/parcel" 11656 + } 11657 + }, 11658 + "node_modules/lightningcss-linux-arm64-musl": { 11659 + "version": "1.30.1", 11660 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", 11661 + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", 11662 + "cpu": [ 11663 + "arm64" 11664 + ], 11665 + "dev": true, 11666 + "license": "MPL-2.0", 11667 + "optional": true, 11668 + "os": [ 11669 + "linux" 11670 + ], 11671 + "engines": { 11672 + "node": ">= 12.0.0" 11673 + }, 11674 + "funding": { 11675 + "type": "opencollective", 11676 + "url": "https://opencollective.com/parcel" 11677 + } 11678 + }, 11679 + "node_modules/lightningcss-linux-x64-gnu": { 11680 + "version": "1.30.1", 11681 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", 11682 + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", 11683 + "cpu": [ 11684 + "x64" 11685 + ], 11686 + "dev": true, 11687 + "license": "MPL-2.0", 11688 + "optional": true, 11689 + "os": [ 11690 + "linux" 11691 + ], 11692 + "engines": { 11693 + "node": ">= 12.0.0" 11694 + }, 11695 + "funding": { 11696 + "type": "opencollective", 11697 + "url": "https://opencollective.com/parcel" 11698 + } 11699 + }, 11700 + "node_modules/lightningcss-linux-x64-musl": { 11701 + "version": "1.30.1", 11702 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", 11703 + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", 11704 + "cpu": [ 11705 + "x64" 11706 + ], 11707 + "dev": true, 11708 + "license": "MPL-2.0", 11709 + "optional": true, 11710 + "os": [ 11711 + "linux" 11712 + ], 11713 + "engines": { 11714 + "node": ">= 12.0.0" 11715 + }, 11716 + "funding": { 11717 + "type": "opencollective", 11718 + "url": "https://opencollective.com/parcel" 11719 + } 11720 + }, 11721 + "node_modules/lightningcss-win32-arm64-msvc": { 11722 + "version": "1.30.1", 11723 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", 11724 + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", 11725 + "cpu": [ 11726 + "arm64" 11727 + ], 11728 + "dev": true, 11729 + "license": "MPL-2.0", 11730 + "optional": true, 11731 + "os": [ 11732 + "win32" 11733 + ], 11734 + "engines": { 11735 + "node": ">= 12.0.0" 11736 + }, 11737 + "funding": { 11738 + "type": "opencollective", 11739 + "url": "https://opencollective.com/parcel" 11740 + } 11741 + }, 11742 + "node_modules/lightningcss-win32-x64-msvc": { 11743 + "version": "1.30.1", 11744 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", 11745 + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", 11746 + "cpu": [ 11747 + "x64" 11748 + ], 11749 + "dev": true, 11750 + "license": "MPL-2.0", 11751 + "optional": true, 11752 + "os": [ 11753 + "win32" 11754 + ], 11755 + "engines": { 11756 + "node": ">= 12.0.0" 11757 + }, 11758 + "funding": { 11759 + "type": "opencollective", 11760 + "url": "https://opencollective.com/parcel" 11761 + } 11486 11762 }, 11487 11763 "node_modules/linkify-it": { 11488 11764 "version": "5.0.0", ··· 12834 13110 } 12835 13111 }, 12836 13112 "node_modules/minizlib": { 12837 - "version": "3.0.1", 12838 - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", 12839 - "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", 13113 + "version": "3.1.0", 13114 + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", 13115 + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", 12840 13116 "dev": true, 13117 + "license": "MIT", 12841 13118 "dependencies": { 12842 - "minipass": "^7.0.4", 12843 - "rimraf": "^5.0.5" 13119 + "minipass": "^7.1.2" 12844 13120 }, 12845 13121 "engines": { 12846 13122 "node": ">= 18" 12847 13123 } 12848 13124 }, 12849 - "node_modules/minizlib/node_modules/brace-expansion": { 12850 - "version": "2.0.1", 12851 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 12852 - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 12853 - "dev": true, 12854 - "dependencies": { 12855 - "balanced-match": "^1.0.0" 12856 - } 12857 - }, 12858 - "node_modules/minizlib/node_modules/glob": { 12859 - "version": "10.4.5", 12860 - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", 12861 - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", 12862 - "dev": true, 12863 - "dependencies": { 12864 - "foreground-child": "^3.1.0", 12865 - "jackspeak": "^3.1.2", 12866 - "minimatch": "^9.0.4", 12867 - "minipass": "^7.1.2", 12868 - "package-json-from-dist": "^1.0.0", 12869 - "path-scurry": "^1.11.1" 12870 - }, 12871 - "bin": { 12872 - "glob": "dist/esm/bin.mjs" 12873 - }, 12874 - "funding": { 12875 - "url": "https://github.com/sponsors/isaacs" 12876 - } 12877 - }, 12878 - "node_modules/minizlib/node_modules/jackspeak": { 12879 - "version": "3.4.3", 12880 - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", 12881 - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", 12882 - "dev": true, 12883 - "dependencies": { 12884 - "@isaacs/cliui": "^8.0.2" 12885 - }, 12886 - "funding": { 12887 - "url": "https://github.com/sponsors/isaacs" 12888 - }, 12889 - "optionalDependencies": { 12890 - "@pkgjs/parseargs": "^0.11.0" 12891 - } 12892 - }, 12893 - "node_modules/minizlib/node_modules/minimatch": { 12894 - "version": "9.0.5", 12895 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 12896 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 12897 - "dev": true, 12898 - "dependencies": { 12899 - "brace-expansion": "^2.0.1" 12900 - }, 12901 - "engines": { 12902 - "node": ">=16 || 14 >=14.17" 12903 - }, 12904 - "funding": { 12905 - "url": "https://github.com/sponsors/isaacs" 12906 - } 12907 - }, 12908 - "node_modules/minizlib/node_modules/rimraf": { 12909 - "version": "5.0.9", 12910 - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", 12911 - "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", 12912 - "dev": true, 12913 - "dependencies": { 12914 - "glob": "^10.3.7" 12915 - }, 12916 - "bin": { 12917 - "rimraf": "dist/esm/bin.mjs" 12918 - }, 12919 - "engines": { 12920 - "node": "14 >=14.20 || 16 >=16.20 || >=18" 12921 - }, 12922 - "funding": { 12923 - "url": "https://github.com/sponsors/isaacs" 12924 - } 12925 - }, 12926 13125 "node_modules/mkdirp": { 12927 13126 "version": "3.0.1", 12928 13127 "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", ··· 12974 13173 "mustache": "bin/mustache" 12975 13174 } 12976 13175 }, 12977 - "node_modules/mz": { 12978 - "version": "2.7.0", 12979 - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", 12980 - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", 12981 - "dev": true, 12982 - "dependencies": { 12983 - "any-promise": "^1.0.0", 12984 - "object-assign": "^4.0.1", 12985 - "thenify-all": "^1.0.0" 12986 - } 12987 - }, 12988 13176 "node_modules/nanoid": { 12989 - "version": "3.3.7", 12990 - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 12991 - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 13177 + "version": "3.3.11", 13178 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 13179 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 12992 13180 "funding": [ 12993 13181 { 12994 13182 "type": "github", 12995 13183 "url": "https://github.com/sponsors/ai" 12996 13184 } 12997 13185 ], 13186 + "license": "MIT", 12998 13187 "bin": { 12999 13188 "nanoid": "bin/nanoid.cjs" 13000 13189 }, ··· 13168 13357 "node-gyp-build-optional-packages-optional": "optional.js", 13169 13358 "node-gyp-build-optional-packages-test": "build-test.js" 13170 13359 } 13171 - }, 13172 - "node_modules/node-releases": { 13173 - "version": "2.0.14", 13174 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", 13175 - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", 13176 - "dev": true 13177 13360 }, 13178 13361 "node_modules/normalize-path": { 13179 13362 "version": "3.0.0", ··· 13184 13367 "node": ">=0.10.0" 13185 13368 } 13186 13369 }, 13187 - "node_modules/normalize-range": { 13188 - "version": "0.1.2", 13189 - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 13190 - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 13191 - "dev": true, 13192 - "engines": { 13193 - "node": ">=0.10.0" 13194 - } 13195 - }, 13196 13370 "node_modules/npm-normalize-package-bin": { 13197 13371 "version": "3.0.1", 13198 13372 "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", ··· 13209 13383 "dev": true, 13210 13384 "engines": { 13211 13385 "node": ">=0.10.0" 13212 - } 13213 - }, 13214 - "node_modules/object-hash": { 13215 - "version": "3.0.0", 13216 - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 13217 - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 13218 - "dev": true, 13219 - "engines": { 13220 - "node": ">= 6" 13221 13386 } 13222 13387 }, 13223 13388 "node_modules/object-inspect": { ··· 13490 13655 "node": ">=8" 13491 13656 } 13492 13657 }, 13493 - "node_modules/package-json-from-dist": { 13494 - "version": "1.0.0", 13495 - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", 13496 - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", 13497 - "dev": true 13498 - }, 13499 13658 "node_modules/parent-module": { 13500 13659 "version": "1.0.1", 13501 13660 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", ··· 13591 13750 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 13592 13751 "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 13593 13752 }, 13594 - "node_modules/path-scurry": { 13595 - "version": "1.11.1", 13596 - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 13597 - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 13598 - "dev": true, 13599 - "dependencies": { 13600 - "lru-cache": "^10.2.0", 13601 - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 13602 - }, 13603 - "engines": { 13604 - "node": ">=16 || 14 >=14.18" 13605 - }, 13606 - "funding": { 13607 - "url": "https://github.com/sponsors/isaacs" 13608 - } 13609 - }, 13610 13753 "node_modules/path-to-regexp": { 13611 13754 "version": "6.2.2", 13612 13755 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", ··· 13703 13846 } 13704 13847 }, 13705 13848 "node_modules/picocolors": { 13706 - "version": "1.0.1", 13707 - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 13708 - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" 13849 + "version": "1.1.1", 13850 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 13851 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 13852 + "license": "ISC" 13709 13853 }, 13710 13854 "node_modules/picomatch": { 13711 13855 "version": "2.3.1", ··· 13717 13861 }, 13718 13862 "funding": { 13719 13863 "url": "https://github.com/sponsors/jonschlinkert" 13720 - } 13721 - }, 13722 - "node_modules/pify": { 13723 - "version": "2.3.0", 13724 - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 13725 - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 13726 - "dev": true, 13727 - "engines": { 13728 - "node": ">=0.10.0" 13729 13864 } 13730 13865 }, 13731 13866 "node_modules/pino": { ··· 13766 13901 "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", 13767 13902 "license": "MIT" 13768 13903 }, 13769 - "node_modules/pirates": { 13770 - "version": "4.0.6", 13771 - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", 13772 - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", 13773 - "dev": true, 13774 - "engines": { 13775 - "node": ">= 6" 13776 - } 13777 - }, 13778 13904 "node_modules/possible-typed-array-names": { 13779 13905 "version": "1.1.0", 13780 13906 "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", ··· 13786 13912 } 13787 13913 }, 13788 13914 "node_modules/postcss": { 13789 - "version": "8.4.38", 13790 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", 13791 - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", 13915 + "version": "8.5.6", 13916 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 13917 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 13792 13918 "dev": true, 13793 13919 "funding": [ 13794 13920 { ··· 13804 13930 "url": "https://github.com/sponsors/ai" 13805 13931 } 13806 13932 ], 13933 + "license": "MIT", 13807 13934 "dependencies": { 13808 - "nanoid": "^3.3.7", 13809 - "picocolors": "^1.0.0", 13810 - "source-map-js": "^1.2.0" 13935 + "nanoid": "^3.3.11", 13936 + "picocolors": "^1.1.1", 13937 + "source-map-js": "^1.2.1" 13811 13938 }, 13812 13939 "engines": { 13813 13940 "node": "^10 || ^12 || >=14" 13814 13941 } 13815 13942 }, 13816 - "node_modules/postcss-import": { 13817 - "version": "15.1.0", 13818 - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", 13819 - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", 13820 - "dev": true, 13821 - "dependencies": { 13822 - "postcss-value-parser": "^4.0.0", 13823 - "read-cache": "^1.0.0", 13824 - "resolve": "^1.1.7" 13825 - }, 13826 - "engines": { 13827 - "node": ">=14.0.0" 13828 - }, 13829 - "peerDependencies": { 13830 - "postcss": "^8.0.0" 13831 - } 13832 - }, 13833 - "node_modules/postcss-js": { 13834 - "version": "4.0.1", 13835 - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", 13836 - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", 13837 - "dev": true, 13838 - "dependencies": { 13839 - "camelcase-css": "^2.0.1" 13840 - }, 13841 - "engines": { 13842 - "node": "^12 || ^14 || >= 16" 13843 - }, 13844 - "funding": { 13845 - "type": "opencollective", 13846 - "url": "https://opencollective.com/postcss/" 13847 - }, 13848 - "peerDependencies": { 13849 - "postcss": "^8.4.21" 13850 - } 13851 - }, 13852 - "node_modules/postcss-load-config": { 13853 - "version": "4.0.2", 13854 - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", 13855 - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", 13856 - "dev": true, 13857 - "funding": [ 13858 - { 13859 - "type": "opencollective", 13860 - "url": "https://opencollective.com/postcss/" 13861 - }, 13862 - { 13863 - "type": "github", 13864 - "url": "https://github.com/sponsors/ai" 13865 - } 13866 - ], 13867 - "dependencies": { 13868 - "lilconfig": "^3.0.0", 13869 - "yaml": "^2.3.4" 13870 - }, 13871 - "engines": { 13872 - "node": ">= 14" 13873 - }, 13874 - "peerDependencies": { 13875 - "postcss": ">=8.0.9", 13876 - "ts-node": ">=9.0.0" 13877 - }, 13878 - "peerDependenciesMeta": { 13879 - "postcss": { 13880 - "optional": true 13881 - }, 13882 - "ts-node": { 13883 - "optional": true 13884 - } 13885 - } 13886 - }, 13887 - "node_modules/postcss-load-config/node_modules/lilconfig": { 13888 - "version": "3.1.1", 13889 - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", 13890 - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", 13891 - "dev": true, 13892 - "engines": { 13893 - "node": ">=14" 13894 - }, 13895 - "funding": { 13896 - "url": "https://github.com/sponsors/antonk52" 13897 - } 13898 - }, 13899 - "node_modules/postcss-nested": { 13900 - "version": "6.0.1", 13901 - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", 13902 - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", 13903 - "dev": true, 13904 - "dependencies": { 13905 - "postcss-selector-parser": "^6.0.11" 13906 - }, 13907 - "engines": { 13908 - "node": ">=12.0" 13909 - }, 13910 - "funding": { 13911 - "type": "opencollective", 13912 - "url": "https://opencollective.com/postcss/" 13913 - }, 13914 - "peerDependencies": { 13915 - "postcss": "^8.2.14" 13916 - } 13917 - }, 13918 - "node_modules/postcss-selector-parser": { 13919 - "version": "6.1.0", 13920 - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", 13921 - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", 13922 - "dev": true, 13923 - "dependencies": { 13924 - "cssesc": "^3.0.0", 13925 - "util-deprecate": "^1.0.2" 13926 - }, 13927 - "engines": { 13928 - "node": ">=4" 13929 - } 13930 - }, 13931 - "node_modules/postcss-value-parser": { 13932 - "version": "4.2.0", 13933 - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 13934 - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 13935 - "dev": true 13936 - }, 13937 13943 "node_modules/postgres": { 13938 13944 "version": "3.4.4", 13939 13945 "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.4.tgz", ··· 14676 14682 "react-dom": ">=16.13" 14677 14683 } 14678 14684 }, 14679 - "node_modules/read-cache": { 14680 - "version": "1.0.0", 14681 - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", 14682 - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 14683 - "dev": true, 14684 - "dependencies": { 14685 - "pify": "^2.3.0" 14686 - } 14687 - }, 14688 14685 "node_modules/read-cmd-shim": { 14689 14686 "version": "4.0.0", 14690 14687 "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", ··· 15726 15723 } 15727 15724 }, 15728 15725 "node_modules/source-map-js": { 15729 - "version": "1.2.0", 15730 - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 15731 - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 15726 + "version": "1.2.1", 15727 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 15728 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 15729 + "license": "BSD-3-Clause", 15732 15730 "engines": { 15733 15731 "node": ">=0.10.0" 15734 15732 } ··· 15825 15823 "node": ">=8" 15826 15824 } 15827 15825 }, 15828 - "node_modules/string-width-cjs": { 15829 - "name": "string-width", 15830 - "version": "4.2.3", 15831 - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 15832 - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 15833 - "dev": true, 15834 - "dependencies": { 15835 - "emoji-regex": "^8.0.0", 15836 - "is-fullwidth-code-point": "^3.0.0", 15837 - "strip-ansi": "^6.0.1" 15838 - }, 15839 - "engines": { 15840 - "node": ">=8" 15841 - } 15842 - }, 15843 - "node_modules/string-width-cjs/node_modules/emoji-regex": { 15844 - "version": "8.0.0", 15845 - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 15846 - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 15847 - "dev": true 15848 - }, 15849 15826 "node_modules/string-width/node_modules/emoji-regex": { 15850 15827 "version": "8.0.0", 15851 15828 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", ··· 15987 15964 "node": ">=8" 15988 15965 } 15989 15966 }, 15990 - "node_modules/strip-ansi-cjs": { 15991 - "name": "strip-ansi", 15992 - "version": "6.0.1", 15993 - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 15994 - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 15995 - "dev": true, 15996 - "dependencies": { 15997 - "ansi-regex": "^5.0.1" 15998 - }, 15999 - "engines": { 16000 - "node": ">=8" 16001 - } 16002 - }, 16003 15967 "node_modules/strip-bom": { 16004 15968 "version": "3.0.0", 16005 15969 "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", ··· 16052 16016 } 16053 16017 } 16054 16018 }, 16055 - "node_modules/sucrase": { 16056 - "version": "3.35.0", 16057 - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", 16058 - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", 16059 - "dev": true, 16060 - "dependencies": { 16061 - "@jridgewell/gen-mapping": "^0.3.2", 16062 - "commander": "^4.0.0", 16063 - "glob": "^10.3.10", 16064 - "lines-and-columns": "^1.1.6", 16065 - "mz": "^2.7.0", 16066 - "pirates": "^4.0.1", 16067 - "ts-interface-checker": "^0.1.9" 16068 - }, 16069 - "bin": { 16070 - "sucrase": "bin/sucrase", 16071 - "sucrase-node": "bin/sucrase-node" 16072 - }, 16073 - "engines": { 16074 - "node": ">=16 || 14 >=14.17" 16075 - } 16076 - }, 16077 - "node_modules/sucrase/node_modules/brace-expansion": { 16078 - "version": "2.0.1", 16079 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 16080 - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 16081 - "dev": true, 16082 - "dependencies": { 16083 - "balanced-match": "^1.0.0" 16084 - } 16085 - }, 16086 - "node_modules/sucrase/node_modules/commander": { 16087 - "version": "4.1.1", 16088 - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 16089 - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 16090 - "dev": true, 16091 - "engines": { 16092 - "node": ">= 6" 16093 - } 16094 - }, 16095 - "node_modules/sucrase/node_modules/glob": { 16096 - "version": "10.4.1", 16097 - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", 16098 - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", 16099 - "dev": true, 16100 - "dependencies": { 16101 - "foreground-child": "^3.1.0", 16102 - "jackspeak": "^3.1.2", 16103 - "minimatch": "^9.0.4", 16104 - "minipass": "^7.1.2", 16105 - "path-scurry": "^1.11.1" 16106 - }, 16107 - "bin": { 16108 - "glob": "dist/esm/bin.mjs" 16109 - }, 16110 - "engines": { 16111 - "node": ">=16 || 14 >=14.18" 16112 - }, 16113 - "funding": { 16114 - "url": "https://github.com/sponsors/isaacs" 16115 - } 16116 - }, 16117 - "node_modules/sucrase/node_modules/jackspeak": { 16118 - "version": "3.1.2", 16119 - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", 16120 - "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", 16121 - "dev": true, 16122 - "dependencies": { 16123 - "@isaacs/cliui": "^8.0.2" 16124 - }, 16125 - "engines": { 16126 - "node": ">=14" 16127 - }, 16128 - "funding": { 16129 - "url": "https://github.com/sponsors/isaacs" 16130 - }, 16131 - "optionalDependencies": { 16132 - "@pkgjs/parseargs": "^0.11.0" 16133 - } 16134 - }, 16135 - "node_modules/sucrase/node_modules/minimatch": { 16136 - "version": "9.0.4", 16137 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", 16138 - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", 16139 - "dev": true, 16140 - "dependencies": { 16141 - "brace-expansion": "^2.0.1" 16142 - }, 16143 - "engines": { 16144 - "node": ">=16 || 14 >=14.17" 16145 - }, 16146 - "funding": { 16147 - "url": "https://github.com/sponsors/isaacs" 16148 - } 16149 - }, 16150 16019 "node_modules/supabase": { 16151 16020 "version": "1.187.3", 16152 16021 "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.187.3.tgz", ··· 16202 16071 } 16203 16072 }, 16204 16073 "node_modules/tailwindcss": { 16205 - "version": "3.4.3", 16206 - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", 16207 - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", 16074 + "version": "4.1.13", 16075 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", 16076 + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", 16208 16077 "dev": true, 16209 - "dependencies": { 16210 - "@alloc/quick-lru": "^5.2.0", 16211 - "arg": "^5.0.2", 16212 - "chokidar": "^3.5.3", 16213 - "didyoumean": "^1.2.2", 16214 - "dlv": "^1.1.3", 16215 - "fast-glob": "^3.3.0", 16216 - "glob-parent": "^6.0.2", 16217 - "is-glob": "^4.0.3", 16218 - "jiti": "^1.21.0", 16219 - "lilconfig": "^2.1.0", 16220 - "micromatch": "^4.0.5", 16221 - "normalize-path": "^3.0.0", 16222 - "object-hash": "^3.0.0", 16223 - "picocolors": "^1.0.0", 16224 - "postcss": "^8.4.23", 16225 - "postcss-import": "^15.1.0", 16226 - "postcss-js": "^4.0.1", 16227 - "postcss-load-config": "^4.0.1", 16228 - "postcss-nested": "^6.0.1", 16229 - "postcss-selector-parser": "^6.0.11", 16230 - "resolve": "^1.22.2", 16231 - "sucrase": "^3.32.0" 16232 - }, 16233 - "bin": { 16234 - "tailwind": "lib/cli.js", 16235 - "tailwindcss": "lib/cli.js" 16236 - }, 16237 - "engines": { 16238 - "node": ">=14.0.0" 16239 - } 16078 + "license": "MIT" 16240 16079 }, 16241 16080 "node_modules/tapable": { 16242 16081 "version": "2.2.1", ··· 16285 16124 "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 16286 16125 "dev": true 16287 16126 }, 16288 - "node_modules/thenify": { 16289 - "version": "3.3.1", 16290 - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", 16291 - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", 16292 - "dev": true, 16293 - "dependencies": { 16294 - "any-promise": "^1.0.0" 16295 - } 16296 - }, 16297 - "node_modules/thenify-all": { 16298 - "version": "1.6.0", 16299 - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", 16300 - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", 16301 - "dev": true, 16302 - "dependencies": { 16303 - "thenify": ">= 3.1.0 < 4" 16304 - }, 16305 - "engines": { 16306 - "node": ">=0.8" 16307 - } 16308 - }, 16309 16127 "node_modules/thread-stream": { 16310 16128 "version": "2.7.0", 16311 16129 "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", ··· 16468 16286 "peerDependencies": { 16469 16287 "typescript": ">=4.8.4" 16470 16288 } 16471 - }, 16472 - "node_modules/ts-interface-checker": { 16473 - "version": "0.1.13", 16474 - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", 16475 - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", 16476 - "dev": true 16477 16289 }, 16478 16290 "node_modules/ts-morph": { 16479 16291 "version": "24.0.0", ··· 16872 16684 "node": ">= 0.8" 16873 16685 } 16874 16686 }, 16875 - "node_modules/update-browserslist-db": { 16876 - "version": "1.0.16", 16877 - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", 16878 - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", 16879 - "dev": true, 16880 - "funding": [ 16881 - { 16882 - "type": "opencollective", 16883 - "url": "https://opencollective.com/browserslist" 16884 - }, 16885 - { 16886 - "type": "tidelift", 16887 - "url": "https://tidelift.com/funding/github/npm/browserslist" 16888 - }, 16889 - { 16890 - "type": "github", 16891 - "url": "https://github.com/sponsors/ai" 16892 - } 16893 - ], 16894 - "dependencies": { 16895 - "escalade": "^3.1.2", 16896 - "picocolors": "^1.0.1" 16897 - }, 16898 - "bin": { 16899 - "update-browserslist-db": "cli.js" 16900 - }, 16901 - "peerDependencies": { 16902 - "browserslist": ">= 4.21.0" 16903 - } 16904 - }, 16905 16687 "node_modules/use-callback-ref": { 16906 16688 "version": "1.3.3", 16907 16689 "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", ··· 16953 16735 "peerDependencies": { 16954 16736 "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 16955 16737 } 16956 - }, 16957 - "node_modules/util-deprecate": { 16958 - "version": "1.0.2", 16959 - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 16960 - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 16961 - "dev": true 16962 16738 }, 16963 16739 "node_modules/utils-merge": { 16964 16740 "version": "1.0.1", ··· 17387 17163 "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 17388 17164 } 17389 17165 }, 17390 - "node_modules/wrap-ansi-cjs": { 17391 - "name": "wrap-ansi", 17392 - "version": "7.0.0", 17393 - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 17394 - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 17395 - "dev": true, 17396 - "dependencies": { 17397 - "ansi-styles": "^4.0.0", 17398 - "string-width": "^4.1.0", 17399 - "strip-ansi": "^6.0.0" 17400 - }, 17401 - "engines": { 17402 - "node": ">=10" 17403 - }, 17404 - "funding": { 17405 - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 17406 - } 17407 - }, 17408 17166 "node_modules/wrappy": { 17409 17167 "version": "1.0.2", 17410 17168 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", ··· 17539 17297 "dev": true, 17540 17298 "engines": { 17541 17299 "node": ">=18" 17542 - } 17543 - }, 17544 - "node_modules/yaml": { 17545 - "version": "2.4.2", 17546 - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", 17547 - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", 17548 - "dev": true, 17549 - "bin": { 17550 - "yaml": "bin.mjs" 17551 - }, 17552 - "engines": { 17553 - "node": ">= 14" 17554 17300 } 17555 17301 }, 17556 17302 "node_modules/yargs": {
+2 -2
package.json
··· 96 96 "devDependencies": { 97 97 "@atproto/lex-cli": "^0.9.5", 98 98 "@cloudflare/workers-types": "^4.20240512.0", 99 + "@tailwindcss/postcss": "^4.1.13", 99 100 "@types/katex": "^0.16.7", 100 101 "@types/node": "^22.15.17", 101 102 "@types/react": "19.1.3", 102 103 "@types/react-dom": "19.1.3", 103 104 "@types/uuid": "^10.0.0", 104 - "autoprefixer": "^10.4.19", 105 105 "drizzle-kit": "^0.21.2", 106 106 "esbuild": "^0.25.4", 107 107 "eslint": "8.57.0", ··· 109 109 "postcss": "^8.4.38", 110 110 "prettier": "3.2.5", 111 111 "supabase": "^1.187.3", 112 - "tailwindcss": "^3.4.3", 112 + "tailwindcss": "^4.1.13", 113 113 "tsx": "^4.19.3", 114 114 "typescript": "^5.8.3", 115 115 "wrangler": "^3.56.0"
+1 -2
postcss.config.js
··· 1 1 module.exports = { 2 2 plugins: { 3 - tailwindcss: {}, 4 - autoprefixer: {}, 3 + '@tailwindcss/postcss': {}, 5 4 }, 6 5 }
+22
src/utils/timeAgo.ts
··· 1 + export function timeAgo(timestamp: string): string { 2 + const now = new Date(); 3 + const date = new Date(timestamp); 4 + const diffMs = now.getTime() - date.getTime(); 5 + const diffSeconds = Math.floor(diffMs / 1000); 6 + const diffMinutes = Math.floor(diffSeconds / 60); 7 + const diffHours = Math.floor(diffMinutes / 60); 8 + const diffDays = Math.floor(diffHours / 24); 9 + const diffYears = Math.floor(diffDays / 365); 10 + 11 + if (diffYears > 0) { 12 + return `${diffYears} year${diffYears === 1 ? "" : "s"} ago`; 13 + } else if (diffDays > 0) { 14 + return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`; 15 + } else if (diffHours > 0) { 16 + return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; 17 + } else if (diffMinutes > 0) { 18 + return `${diffMinutes} minute${diffMinutes === 1 ? "" : "s"} ago`; 19 + } else { 20 + return "just now"; 21 + } 22 + }