a tool for shared writing and social publishing

Renaming things (#59)

* renamed Doc => Leaflet

mostly touched things in home, but couldnt rename things in /storage without needing a migration so lef that alone

* renamed Page => Leaflet

mostly touched things in theming

* renamed card => page

but not the attributes, cause those need a speical migration

* rename theme variable

* remove unnessecary path revalidation

---------

Co-authored-by: Jared Pereira <jared@awarm.space>

authored by cozylittle.house

Jared Pereira and committed by
GitHub
5cee5e22 3979cc32

+595 -577
+1 -1
actions/addLinkCard.ts actions/addPageLink.ts
··· 7 7 process.env.SUPABASE_SERVICE_ROLE_KEY as string, 8 8 ); 9 9 10 - export async function addLinkCard(args: { link: string }) { 10 + export async function addPageLink(args: { link: string }) { 11 11 let result = await get_url_preview_data(args.link); 12 12 return result; 13 13 }
+1 -1
actions/createNewDoc.ts actions/createNewLeaflet.ts
··· 13 13 import { v7 } from "uuid"; 14 14 import { sql } from "drizzle-orm"; 15 15 16 - export async function createNewDoc() { 16 + export async function createNewLeaflet() { 17 17 const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); 18 18 const db = drizzle(client); 19 19 let { permissionToken } = await db.transaction(async (tx) => {
+2 -2
actions/deleteDoc.ts actions/deleteLeaflet.ts
··· 14 14 import { PermissionToken } from "src/replicache"; 15 15 import { revalidatePath } from "next/cache"; 16 16 17 - export async function deleteDoc(permission_token: PermissionToken) { 17 + export async function deleteLeaflet(permission_token: PermissionToken) { 18 18 const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); 19 19 const db = drizzle(client); 20 20 await db.transaction(async (tx) => { ··· 36 36 .where(eq(permission_tokens.id, permission_token.id)); 37 37 }); 38 38 client.end(); 39 - return revalidatePath("/docs"); 39 + return ; 40 40 }
+6 -6
actions/subscriptions/sendPostToSubscribers.ts
··· 12 12 let supabase = createServerClient<Database>( 13 13 process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, 14 14 process.env.SUPABASE_SERVICE_ROLE_KEY as string, 15 - { cookies: {} } 15 + { cookies: {} }, 16 16 ); 17 17 export async function sendPostToSubscribers({ 18 18 title, ··· 36 36 .eq("id", permission_token.id) 37 37 .single(); 38 38 let rootEntity = token_rights.data?.root_entity; 39 - if (!rootEntity || !token_rights.data) return { title: "Doc not found" }; 39 + if (!rootEntity || !token_rights.data) return { title: "Leaflet not found" }; 40 40 let { data } = await supabase.rpc("get_facts", { 41 41 root: rootEntity, 42 42 }); ··· 51 51 let entity_set = subscribers[0]?.entities.set; 52 52 if ( 53 53 !token_rights.data.permission_token_rights.find( 54 - (r) => r.entity_set === entity_set 54 + (r) => r.entity_set === entity_set, 55 55 ) 56 56 ) { 57 57 return; ··· 80 80 Subject: `New Mail in: ${title}`, 81 81 To: sub.email_subscriptions_to_entity.email, 82 82 HtmlBody: ` 83 - You've got new mail from <a href="${domain}/${sub.email_subscriptions_to_entity.token}?sub_id=${sub.email_subscriptions_to_entity.id}&email=${sub.email_subscriptions_to_entity.email}&entity=${sub.email_subscriptions_to_entity.entity}&openCard=${messageEntity}"> 83 + You've got new mail from <a href="${domain}/${sub.email_subscriptions_to_entity.token}?sub_id=${sub.email_subscriptions_to_entity.id}&email=${sub.email_subscriptions_to_entity.email}&entity=${sub.email_subscriptions_to_entity.entity}&openPage=${messageEntity}"> 84 84 ${title}! 85 85 </a> 86 86 <hr style="margin-top: 1em; margin-bottom: 1em;"> 87 87 ${contents.html} 88 88 <hr style="margin-top: 1em; margin-bottom: 1em;"> 89 89 <em>Manage your subscription at 90 - <a href="${domain}/${sub.email_subscriptions_to_entity.token}?sub_id=${sub.email_subscriptions_to_entity.id}&email=${sub.email_subscriptions_to_entity.email}&entity=${sub.email_subscriptions_to_entity.entity}&openCard=${messageEntity}"> 90 + <a href="${domain}/${sub.email_subscriptions_to_entity.token}?sub_id=${sub.email_subscriptions_to_entity.id}&email=${sub.email_subscriptions_to_entity.email}&entity=${sub.email_subscriptions_to_entity.entity}&openPage=${messageEntity}"> 91 91 ${title} 92 92 </a></em> 93 93 `, 94 94 TextBody: contents.markdown, 95 - })) 95 + })), 96 96 ), 97 97 }); 98 98 client.end();
+1 -1
actions/subscriptions/subscribeToMailboxWithEmail.ts
··· 98 98 let text = initialFacts.find( 99 99 (f) => f.entity === title.value && f.attribute === "block/text", 100 100 ) as Fact<"block/text"> | undefined; 101 - if (!text) return "Untitled Doc"; 101 + if (!text) return "Untitled Leaflet"; 102 102 let doc = new Y.Doc(); 103 103 const update = base64.toByteArray(text.data.value); 104 104 Y.applyUpdate(doc, update);
-54
app/[doc_id]/Doc.tsx
··· 1 - import { Fact, PermissionToken, ReplicacheProvider } from "src/replicache"; 2 - import { Database } from "../../supabase/database.types"; 3 - import { Attributes } from "src/replicache/attributes"; 4 - import { createServerClient } from "@supabase/ssr"; 5 - import { SelectionManager } from "components/SelectionManager"; 6 - import { Cards } from "components/Cards"; 7 - import { 8 - ThemeBackgroundProvider, 9 - ThemeProvider, 10 - } from "components/ThemeManager/ThemeProvider"; 11 - import { MobileFooter } from "components/MobileFooter"; 12 - import { PopUpProvider } from "components/Toast"; 13 - import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment"; 14 - import { 15 - EntitySetContext, 16 - EntitySetProvider, 17 - } from "components/EntitySetProvider"; 18 - import { UpdatePageTitle } from "components/utils/UpdatePageTitle"; 19 - import { AddDocToHomepage } from "components/utils/AddDocToHomepage"; 20 - export function Doc(props: { 21 - token: PermissionToken; 22 - initialFacts: Fact<keyof typeof Attributes>[]; 23 - doc_id: string; 24 - }) { 25 - return ( 26 - <ReplicacheProvider 27 - rootEntity={props.doc_id} 28 - token={props.token} 29 - name={props.doc_id} 30 - initialFacts={props.initialFacts} 31 - > 32 - <EntitySetProvider 33 - set={props.token.permission_token_rights[0].entity_set} 34 - > 35 - <PopUpProvider> 36 - <ThemeProvider entityID={props.doc_id}> 37 - <ThemeBackgroundProvider entityID={props.doc_id}> 38 - <UpdatePageTitle entityID={props.doc_id} /> 39 - <AddDocToHomepage /> 40 - <SelectionManager /> 41 - <div 42 - className="pageContentWrapper w-full relative overflow-x-scroll snap-x snap-mandatory no-scrollbar grow items-stretch flex h-full" 43 - id="card-carousel" 44 - > 45 - <Cards rootCard={props.doc_id} /> 46 - </div> 47 - <MobileFooter entityID={props.doc_id} /> 48 - </ThemeBackgroundProvider> 49 - </ThemeProvider> 50 - </PopUpProvider> 51 - </EntitySetProvider> 52 - </ReplicacheProvider> 53 - ); 54 - }
+4 -4
app/[doc_id]/icon.tsx app/[leaflet_id]/icon.tsx
··· 24 24 process.env.SUPABASE_SERVICE_ROLE_KEY as string, 25 25 { cookies: {} }, 26 26 ); 27 - export default async function Icon(props: { params: { doc_id: string } }) { 27 + export default async function Icon(props: { params: { leaflet_id: string } }) { 28 28 let res = await supabase 29 29 .from("permission_tokens") 30 30 .select("*, permission_token_rights(*)") 31 - .eq("id", props.params.doc_id) 31 + .eq("id", props.params.leaflet_id) 32 32 .single(); 33 33 let rootEntity = res.data?.root_entity; 34 34 let outlineColor, fillColor; ··· 38 38 }); 39 39 let initialFacts = 40 40 (data as unknown as Fact<keyof typeof Attributes>[]) || []; 41 - let themeCardBG = initialFacts.find( 41 + let themePageBG = initialFacts.find( 42 42 (f) => f.attribute === "theme/card-background", 43 43 ) as Fact<"theme/card-background"> | undefined; 44 44 ··· 46 46 (f) => f.attribute === "theme/primary", 47 47 ) as Fact<"theme/primary"> | undefined; 48 48 49 - outlineColor = parseHSBToRGB(`hsba(${themeCardBG?.data.value})`); 49 + outlineColor = parseHSBToRGB(`hsba(${themePageBG?.data.value})`); 50 50 51 51 fillColor = parseHSBToRGB(`hsba(${themePrimary?.data.value})`); 52 52 }
+2 -2
app/[doc_id]/opengraph-image.tsx app/[leaflet_id]/opengraph-image.tsx
··· 2 2 import { ImageResponse } from "next/og"; 3 3 export const runtime = "edge"; 4 4 export default async function OpenGraphImage(props: { 5 - params: { doc_id: string }; 5 + params: { leaflet_id: string }; 6 6 }) { 7 7 if (process.env.NODE_ENV === "development") return; 8 8 const headersList = headers(); 9 9 const hostname = headersList.get("x-forwarded-host"); 10 10 let protocol = headersList.get("x-forwarded-proto"); 11 - let path = `${protocol}://${hostname}/${props.params.doc_id}`; 11 + let path = `${protocol}://${hostname}/${props.params.leaflet_id}`; 12 12 let response = await fetch( 13 13 `https://pro.microlink.io/?url=${path}&screenshot=true&&viewport.width=1200&viewport.height=630&meta=false&embed=screenshot.url`, 14 14 {
+14 -10
app/[doc_id]/page.tsx app/[leaflet_id]/page.tsx
··· 7 7 import { Attributes } from "src/replicache/attributes"; 8 8 import { createServerClient } from "@supabase/ssr"; 9 9 import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment"; 10 - import { Doc } from "./Doc"; 10 + import { Leaflet } from "./Leaflet"; 11 11 12 12 export const preferredRegion = ["sfo1"]; 13 13 export const dynamic = "force-dynamic"; ··· 19 19 { cookies: {} }, 20 20 ); 21 21 type Props = { 22 - // this is now a token id not doc! Should probs rename 23 - params: { doc_id: string }; 22 + // this is now a token id not leaflet! Should probs rename 23 + params: { leaflet_id: string }; 24 24 }; 25 - export default async function DocumentPage(props: Props) { 25 + export default async function LeafletPage(props: Props) { 26 26 let res = await supabase 27 27 .from("permission_tokens") 28 28 .select("*, permission_token_rights(*) ") 29 - .eq("id", props.params.doc_id) 29 + .eq("id", props.params.leaflet_id) 30 30 .single(); 31 31 let rootEntity = res.data?.root_entity; 32 32 if (!rootEntity || !res.data) 33 33 return ( 34 - <div className="w-screen h-screen flex place-items-center bg-bg-page"> 35 - <div className="bg-bg-card mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-centergap-1 w-fit"> 34 + <div className="w-screen h-screen flex place-items-center bg-bg-leaflet"> 35 + <div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-centergap-1 w-fit"> 36 36 <div className="font-bold"> 37 37 Hmmm... Couldn&apos;t find that leaflet. 38 38 </div> ··· 52 52 }); 53 53 let initialFacts = (data as unknown as Fact<keyof typeof Attributes>[]) || []; 54 54 return ( 55 - <Doc initialFacts={initialFacts} doc_id={rootEntity} token={res.data} /> 55 + <Leaflet 56 + initialFacts={initialFacts} 57 + leaflet_id={rootEntity} 58 + token={res.data} 59 + /> 56 60 ); 57 61 } 58 62 ··· 60 64 let res = await supabase 61 65 .from("permission_tokens") 62 66 .select("*, permission_token_rights(*)") 63 - .eq("id", props.params.doc_id) 67 + .eq("id", props.params.leaflet_id) 64 68 .single(); 65 69 let rootEntity = res.data?.root_entity; 66 - if (!rootEntity || !res.data) return { title: "Doc not found" }; 70 + if (!rootEntity || !res.data) return { title: "Leaflet not found" }; 67 71 let { data } = await supabase.rpc("get_facts", { 68 72 root: rootEntity, 69 73 });
+54
app/[leaflet_id]/Leaflet.tsx
··· 1 + import { Fact, PermissionToken, ReplicacheProvider } from "src/replicache"; 2 + import { Database } from "../../supabase/database.types"; 3 + import { Attributes } from "src/replicache/attributes"; 4 + import { createServerClient } from "@supabase/ssr"; 5 + import { SelectionManager } from "components/SelectionManager"; 6 + import { Pages } from "components/Pages"; 7 + import { 8 + ThemeBackgroundProvider, 9 + ThemeProvider, 10 + } from "components/ThemeManager/ThemeProvider"; 11 + import { MobileFooter } from "components/MobileFooter"; 12 + import { PopUpProvider } from "components/Toast"; 13 + import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment"; 14 + import { 15 + EntitySetContext, 16 + EntitySetProvider, 17 + } from "components/EntitySetProvider"; 18 + import { AddLeafletToHomepage } from "components/utils/AddLeafletToHomepage"; 19 + import { UpdateLeafletTitle } from "components/utils/UpdateLeafletTitle"; 20 + export function Leaflet(props: { 21 + token: PermissionToken; 22 + initialFacts: Fact<keyof typeof Attributes>[]; 23 + leaflet_id: string; 24 + }) { 25 + return ( 26 + <ReplicacheProvider 27 + rootEntity={props.leaflet_id} 28 + token={props.token} 29 + name={props.leaflet_id} 30 + initialFacts={props.initialFacts} 31 + > 32 + <EntitySetProvider 33 + set={props.token.permission_token_rights[0].entity_set} 34 + > 35 + <PopUpProvider> 36 + <ThemeProvider entityID={props.leaflet_id}> 37 + <ThemeBackgroundProvider entityID={props.leaflet_id}> 38 + <UpdateLeafletTitle entityID={props.leaflet_id} /> 39 + <AddLeafletToHomepage /> 40 + <SelectionManager /> 41 + <div 42 + className="leafletContentWrapper w-full relative overflow-x-scroll snap-x snap-mandatory no-scrollbar grow items-stretch flex h-full" 43 + id="page-carousel" 44 + > 45 + <Pages rootPage={props.leaflet_id} /> 46 + </div> 47 + <MobileFooter entityID={props.leaflet_id} /> 48 + </ThemeBackgroundProvider> 49 + </ThemeProvider> 50 + </PopUpProvider> 51 + </EntitySetProvider> 52 + </ReplicacheProvider> 53 + ); 54 + }
+13 -10
app/globals.css
··· 4 4 5 5 @layer base { 6 6 :root { 7 - --bg-page: 240, 247, 250; 8 - --bg-card: 255, 255, 255; 9 - --bg-card-alpha: 1; 7 + --bg-leaflet: 240, 247, 250; 8 + --bg-page: 255, 255, 255; 9 + --bg-page-alpha: 1; 10 10 11 11 --primary: 39, 39, 39; 12 12 ··· 19 19 --highlight-3: 224, 244, 255; 20 20 21 21 --list-marker-width: 36px; 22 - --card-width-unitless: min(624, calc(var(--page-width-unitless) - 12)); 23 - --card-width-units: min(624px, calc(100vw - 12px)); 22 + --page-width-unitless: min(624, calc(var(--leaflet-width-unitless) - 12)); 23 + --page-width-units: min(624px, calc(100vw - 12px)); 24 24 } 25 25 @media (max-width: 640px) { 26 26 :root { ··· 30 30 31 31 @media (min-width: 640px) { 32 32 :root { 33 - --card-width-unitless: min(624, calc(var(--page-width-unitless) - 128)); 34 - --card-width-units: min(624px, calc(100vw - 128px)); 33 + --page-width-unitless: min( 34 + 624, 35 + calc(var(--leaflet-width-unitless) - 128) 36 + ); 37 + --page-width-units: min(624px, calc(100vw - 128px)); 35 38 } 36 39 } 37 40 38 41 @media (min-width: 1280px) { 39 42 :root { 40 - --card-width-unitless: min( 43 + --page-width-unitless: min( 41 44 624, 42 - calc((var(--page-width-unitless) / 2) - 32) 45 + calc((var(--leaflet-width-unitless) / 2) - 32) 43 46 ); 44 - --card-width-units: min(624px, calc((100vw / 2) - 32px)); 47 + --page-width-units: min(624px, calc((100vw / 2) - 32px)); 45 48 } 46 49 } 47 50
+6 -6
app/home/DocOptions.tsx app/home/LeafletOptions.tsx
··· 3 3 import { Menu, MenuItem } from "components/Layout"; 4 4 import { PermissionToken } from "src/replicache"; 5 5 import { mutate } from "swr"; 6 - import { hideDoc, removeDocFromHome } from "./storage"; 6 + import { hideDoc } from "./storage"; 7 7 8 - export const DocOptions = (props: { 9 - doc: PermissionToken; 8 + export const LeafletOptions = (props: { 9 + leaflet: PermissionToken; 10 10 setState: (s: "normal" | "deleting") => void; 11 11 }) => { 12 12 return ( ··· 20 20 > 21 21 <MenuItem 22 22 onSelect={() => { 23 - hideDoc(props.doc); 24 - mutate("docs"); 23 + hideDoc(props.leaflet); 24 + mutate("leaflets"); 25 25 }} 26 26 > 27 27 Hide from home{" "} ··· 32 32 }} 33 33 > 34 34 <DeleteSmall /> 35 - Delete Doc 35 + Delete Leaflet 36 36 </MenuItem> 37 37 </Menu> 38 38 </>
+26 -24
app/home/DocPreview.tsx app/home/LeafletPreview.tsx
··· 1 1 "use client"; 2 - import { BlockPreview, CardPreview } from "components/Blocks/CardBlock"; 2 + import { BlockPreview, PagePreview } from "components/Blocks/PageLinkBlock"; 3 3 import { 4 4 ThemeBackgroundProvider, 5 5 ThemeProvider, ··· 8 8 import { Link } from "react-aria-components"; 9 9 import { useBlocks } from "src/hooks/queries/useBlocks"; 10 10 import { PermissionToken } from "src/replicache"; 11 - import { DocOptions } from "./DocOptions"; 12 - import { deleteDoc } from "actions/deleteDoc"; 11 + import { deleteLeaflet } from "actions/deleteLeaflet"; 13 12 import { removeDocFromHome } from "./storage"; 14 13 import { mutate } from "swr"; 15 14 import useMeasure from "react-use-measure"; 16 15 import { ButtonPrimary } from "components/Buttons"; 16 + import { LeafletOptions } from "./LeafletOptions"; 17 17 18 - export const DocPreview = (props: { 18 + export const LeafletPreview = (props: { 19 19 token: PermissionToken; 20 - doc_id: string; 20 + leaflet_id: string; 21 21 }) => { 22 22 let [state, setState] = useState<"normal" | "deleting">("normal"); 23 23 return ( 24 24 <div className="relative h-40"> 25 - <ThemeProvider local entityID={props.doc_id}> 26 - <div className="rounded-lg hover:shadow-sm overflow-clip border border-border outline outline-transparent hover:outline-border bg-bg-page grow w-full h-full"> 25 + <ThemeProvider local entityID={props.leaflet_id}> 26 + <div className="rounded-lg hover:shadow-sm overflow-clip border border-border outline outline-transparent hover:outline-border bg-bg-leaflet grow w-full h-full"> 27 27 {state === "normal" ? ( 28 28 <Link 29 29 href={"/" + props.token.id} 30 30 className={`no-underline hover:no-underline text-primary h-full`} 31 31 > 32 - <ThemeBackgroundProvider entityID={props.doc_id}> 33 - <div className="docPreview grow shrink-0 h-full w-full px-2 pt-2 sm:px-3 sm:pt-3 flex items-end pointer-events-none"> 32 + <ThemeBackgroundProvider entityID={props.leaflet_id}> 33 + <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"> 34 34 <div 35 - className="docContentWrapper w-full h-full max-w-48 mx-auto border border-border-light border-b-0 rounded-t-md overflow-clip" 35 + className="leafletContentWrapper w-full h-full max-w-48 mx-auto border border-border-light border-b-0 rounded-t-md overflow-clip" 36 36 style={{ 37 37 backgroundColor: 38 - "rgba(var(--bg-card), var(--bg-card-alpha))", 38 + "rgba(var(--bg-page), var(--bg-page-alpha))", 39 39 }} 40 40 > 41 - <DocContent entityID={props.doc_id} /> 41 + <LeafletContent entityID={props.leaflet_id} /> 42 42 </div> 43 43 </div> 44 44 </ThemeBackgroundProvider> 45 45 </Link> 46 46 ) : ( 47 - <DocAreYouSure token={props.token} setState={setState} /> 47 + <LeafletAreYouSure token={props.token} setState={setState} /> 48 48 )} 49 49 </div> 50 50 <div className="flex justify-end pt-1"> 51 - <DocOptions doc={props.token} setState={setState} /> 51 + <LeafletOptions leaflet={props.token} setState={setState} /> 52 52 </div> 53 53 </ThemeProvider> 54 54 </div> 55 55 ); 56 56 }; 57 57 58 - const DocContent = (props: { entityID: string }) => { 58 + const LeafletContent = (props: { entityID: string }) => { 59 59 let blocks = useBlocks(props.entityID); 60 60 let previewRef = useRef<HTMLDivElement | null>(null); 61 61 let [ref, dimensions] = useMeasure(); ··· 63 63 return ( 64 64 <div 65 65 ref={previewRef} 66 - className={`cardBlockPreview w-full h-full overflow-clip flex flex-col gap-0.5 no-underline relative`} 66 + className={`pageLinkBlockPreview w-full h-full overflow-clip flex flex-col gap-0.5 no-underline relative`} 67 67 > 68 68 <div className="w-full" ref={ref} /> 69 69 <div 70 70 className="absolute top-0 left-0 w-full h-full origin-top-left pointer-events-none" 71 71 style={{ 72 - width: `calc(var(--card-width) * 1px)`, 73 - transform: `scale(calc(${dimensions.width} / var(--card-width)))`, 72 + width: `calc(var(--page-width) * 1px)`, 73 + transform: `scale(calc(${dimensions.width} / var(--page-width)))`, 74 74 }} 75 75 > 76 76 {blocks.slice(0, 10).map((b, index, arr) => { ··· 91 91 ); 92 92 }; 93 93 94 - const DocAreYouSure = (props: { 94 + const LeafletAreYouSure = (props: { 95 95 token: PermissionToken; 96 96 setState: (s: "normal" | "deleting") => void; 97 97 }) => { 98 98 return ( 99 99 <div 100 - className="docContentWrapper w-full h-full px-1 pt-1 sm:px-[6px] sm:pt-2 flex flex-col gap-2 justify-center items-center " 100 + 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 " 101 101 style={{ 102 - backgroundColor: "rgba(var(--bg-card), var(--bg-card-alpha))", 102 + backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 103 103 }} 104 104 > 105 - <div className="font-bold text-center">Permanently delete this doc?</div> 105 + <div className="font-bold text-center"> 106 + Permanently delete this Leaflet? 107 + </div> 106 108 <div className="flex gap-2 font-bold "> 107 109 <ButtonPrimary 108 110 compact 109 111 onMouseDown={(e) => { 110 112 e.stopPropagation(); 111 113 e.preventDefault(); 112 - deleteDoc(props.token); 114 + deleteLeaflet(props.token); 113 115 removeDocFromHome(props.token); 114 - mutate("docs"); 116 + mutate("leaflets"); 115 117 }} 116 118 > 117 119 Delete
-34
app/home/DocsList.tsx
··· 1 - "use client"; 2 - 3 - import { useEffect, useState } from "react"; 4 - import { getHomeDocs, HomeDoc } from "./storage"; 5 - import useSWR from "swr"; 6 - import { ReplicacheProvider } from "src/replicache"; 7 - import { DocPreview } from "./DocPreview"; 8 - 9 - export function DocsList() { 10 - let { data: docs } = useSWR("docs", () => getHomeDocs(), { 11 - fallbackData: [], 12 - }); 13 - 14 - return ( 15 - <div className="homeDocGrid grow w-full h-full overflow-y-scroll no-scrollbar pt-3 pb-28 sm:pt-6 sm:pb-12 "> 16 - <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-6 grow"> 17 - {docs 18 - .sort((a, b) => (a.added_at > b.added_at ? -1 : 1)) 19 - .filter((d) => !d.hidden) 20 - .map(({ token: doc }) => ( 21 - <ReplicacheProvider 22 - key={doc.id} 23 - rootEntity={doc.root_entity} 24 - token={doc} 25 - name={doc.root_entity} 26 - initialFacts={[]} 27 - > 28 - <DocPreview token={doc} doc_id={doc.root_entity} /> 29 - </ReplicacheProvider> 30 - ))} 31 - </div> 32 - </div> 33 - ); 34 - }
+3 -3
app/home/HomeHelp.tsx
··· 23 23 > 24 24 <div className="flex flex-col gap-2"> 25 25 <p> 26 - Docs are saved to home <strong>per-device / browser</strong> using 27 - cookies. 26 + Leaflets are saved to home <strong>per-device / browser</strong>{" "} 27 + using cookies. 28 28 </p> 29 29 <p> 30 30 <strong> ··· 33 33 </p> 34 34 <p> 35 35 Please <a href="mailto:contact@hyperlink.academy">contact us</a>{" "} 36 - for help recovering docs! 36 + for help recovering Leaflets! 37 37 </p> 38 38 </div> 39 39 <Popover.Arrow asChild width={16} height={8} viewBox="0 0 16 8">
+37
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 { ReplicacheProvider } from "src/replicache"; 7 + import { LeafletPreview } from "./LeafletPreview"; 8 + 9 + export function LeafletList() { 10 + let { data: leaflets } = useSWR("leaflets", () => getHomeDocs(), { 11 + fallbackData: [], 12 + }); 13 + 14 + return ( 15 + <div className="homeLeafletGrid grow w-full h-full overflow-y-scroll no-scrollbar pt-3 pb-28 sm:pt-6 sm:pb-12 "> 16 + <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-6 grow"> 17 + {leaflets 18 + .sort((a, b) => (a.added_at > b.added_at ? -1 : 1)) 19 + .filter((d) => !d.hidden) 20 + .map(({ token: leaflet }) => ( 21 + <ReplicacheProvider 22 + key={leaflet.id} 23 + rootEntity={leaflet.root_entity} 24 + token={leaflet} 25 + name={leaflet.root_entity} 26 + initialFacts={[]} 27 + > 28 + <LeafletPreview 29 + token={leaflet} 30 + leaflet_id={leaflet.root_entity} 31 + /> 32 + </ReplicacheProvider> 33 + ))} 34 + </div> 35 + </div> 36 + ); 37 + }
+2 -2
app/home/icon.tsx
··· 51 51 }); 52 52 let initialFacts = 53 53 (data as unknown as Fact<keyof typeof Attributes>[]) || []; 54 - let themeCardBG = initialFacts.find( 54 + let themePageBG = initialFacts.find( 55 55 (f) => f.attribute === "theme/card-background", 56 56 ) as Fact<"theme/card-background"> | undefined; 57 57 ··· 59 59 (f) => f.attribute === "theme/primary", 60 60 ) as Fact<"theme/primary"> | undefined; 61 61 62 - outlineColor = parseHSBToRGB(`hsba(${themeCardBG?.data.value})`); 62 + outlineColor = parseHSBToRGB(`hsba(${themePageBG?.data.value})`); 63 63 64 64 fillColor = parseHSBToRGB(`hsba(${themePrimary?.data.value})`); 65 65 }
+5 -6
app/home/page.tsx
··· 3 3 import { Fact, ReplicacheProvider } from "src/replicache"; 4 4 import { createServerClient } from "@supabase/ssr"; 5 5 import { Database } from "supabase/database.types"; 6 - import { DocPreview } from "./DocPreview"; 7 6 import { Attributes } from "src/replicache/attributes"; 8 7 import { 9 8 ThemeBackgroundProvider, ··· 11 10 } from "components/ThemeManager/ThemeProvider"; 12 11 import { EntitySetProvider } from "components/EntitySetProvider"; 13 12 import { ThemePopover } from "components/ThemeManager/ThemeSetter"; 14 - import { createNewDoc } from "actions/createNewDoc"; 13 + import { createNewLeaflet } from "actions/createNewLeaflet"; 15 14 import { createIdentity } from "actions/createIdentity"; 16 15 import postgres from "postgres"; 17 16 import { drizzle } from "drizzle-orm/postgres-js"; 18 17 import { IdentitySetter } from "./IdentitySetter"; 19 18 import { HoverButton } from "components/Buttons"; 20 19 import { HomeHelp } from "./HomeHelp"; 21 - import { DocsList } from "./DocsList"; 20 + import { LeafletList } from "./LeafletList"; 22 21 23 22 let supabase = createServerClient<Database>( 24 23 process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, ··· 72 71 set={res.data.permission_tokens.permission_token_rights[0].entity_set} 73 72 > 74 73 <ThemeProvider entityID={root_entity}> 75 - <div className="flex h-full bg-bg-page"> 74 + <div className="flex h-full bg-bg-leaflet"> 76 75 <ThemeBackgroundProvider entityID={root_entity}> 77 76 <div className="home relative max-w-screen-lg w-full h-full mx-auto flex sm:flex-row flex-col-reverse sm:gap-4 px-2 sm:px-6 "> 78 77 <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"> 79 - <form action={createNewDoc}> 78 + <form action={createNewLeaflet}> 80 79 <button className="contents"> 81 80 <HoverButton 82 81 icon=<AddTiny className="m-1 shrink-0" /> ··· 90 89 91 90 <ThemePopover entityID={root_entity} home /> 92 91 </div> 93 - <DocsList /> 92 + <LeafletList /> 94 93 </div> 95 94 </ThemeBackgroundProvider> 96 95 </div>
+2 -2
app/layout.tsx
··· 41 41 __html: ` 42 42 let listener = () => { 43 43 let el = document.querySelector(":root"); 44 - el.style.setProperty("--page-height-unitless", window.innerHeight) 45 - el.style.setProperty("--page-width-unitless", window.innerWidth) 44 + el.style.setProperty("--leaflet-height-unitless", window.innerHeight) 45 + el.style.setProperty("--leaflet-width-unitless", window.innerWidth) 46 46 } 47 47 listener() 48 48 window.addEventListener("resize", listener)
+2 -2
app/route.ts
··· 1 - import { createNewDoc } from "actions/createNewDoc"; 1 + import { createNewLeaflet } from "actions/createNewLeaflet"; 2 2 3 3 export const preferredRegion = ["sfo1"]; 4 4 export const dynamic = "force-dynamic"; 5 5 export const fetchCache = "force-no-store"; 6 6 7 7 export async function GET() { 8 - await createNewDoc(); 8 + await createNewLeaflet(); 9 9 }
+2 -2
components/Blocks/Block.tsx
··· 10 10 11 11 import { TextBlock } from "components/Blocks/TextBlock"; 12 12 import { ImageBlock } from "./ImageBlock"; 13 - import { CardBlock } from "./CardBlock"; 13 + import { PageLinkBlock } from "./PageLinkBlock"; 14 14 import { ExternalLinkBlock } from "./ExternalLinkBlock"; 15 15 import { MailboxBlock } from "./MailboxBlock"; 16 16 import { HeadingBlock } from "./HeadingBlock"; ··· 123 123 ) : ( 124 124 <> 125 125 {props.type === "card" ? ( 126 - <CardBlock {...props} preview={!props.preview} /> 126 + <PageLinkBlock {...props} preview={!props.preview} /> 127 127 ) : props.type === "text" ? ( 128 128 <TextBlock {...props} className="" preview={props.preview} /> 129 129 ) : props.type === "heading" ? (
+11 -11
components/Blocks/BlockOptions.tsx
··· 1 1 import { useEntity, useReplicache } from "src/replicache"; 2 2 import { useUIState } from "src/useUIState"; 3 3 import { 4 - BlockCardSmall, 4 + BlockPageLinkSmall, 5 5 BlockImageSmall, 6 6 BlockLinkSmall, 7 7 CheckTiny, ··· 16 16 } from "components/Icons"; 17 17 import { generateKeyBetween } from "fractional-indexing"; 18 18 import { addImage } from "src/utils/addImage"; 19 - import { focusCard } from "components/Cards"; 19 + import { focusPage } from "components/Pages"; 20 20 import { useState } from "react"; 21 21 import { Separator } from "components/Layout"; 22 22 import { addLinkBlock } from "src/utils/addLinkBlock"; ··· 49 49 >("default"); 50 50 51 51 let focusedElement = useUIState((s) => s.focusedEntity); 52 - let focusedCardID = 53 - focusedElement?.entityType === "card" 52 + let focusedPageID = 53 + focusedElement?.entityType === "page" 54 54 ? focusedElement.entityID 55 55 : focusedElement?.parent; 56 56 ··· 111 111 </label> 112 112 </ToolbarButton> 113 113 <ToolbarButton 114 - tooltipContent="Add a card" 114 + tooltipContent="Add a page" 115 115 className="text-tertiary h-6" 116 116 onClick={async () => { 117 117 let entity; ··· 137 137 data: { type: "block-type-union", value: "card" }, 138 138 }); 139 139 } 140 - let newCard = v7(); 141 - await rep?.mutate.addCardBlock({ 140 + let newPage = v7(); 141 + await rep?.mutate.addPageLinkBlock({ 142 142 blockEntity: entity, 143 143 firstBlockFactID: v7(), 144 144 firstBlockEntity: v7(), 145 - cardEntity: newCard, 145 + pageEntity: newPage, 146 146 permission_set: entity_set.set, 147 147 }); 148 - useUIState.getState().openCard(props.parent, newCard); 149 - if (rep) focusCard(newCard, rep, "focusFirstBlock"); 148 + useUIState.getState().openPage(props.parent, newPage); 149 + if (rep) focusPage(newPage, rep, "focusFirstBlock"); 150 150 }} 151 151 > 152 - <BlockCardSmall /> 152 + <BlockPageLinkSmall /> 153 153 </ToolbarButton> 154 154 <ToolbarButton 155 155 tooltipContent="Add a Link"
-162
components/Blocks/CardBlock.tsx
··· 1 - "use client"; 2 - import { BlockProps, BaseBlock, ListMarker, Block } from "./Block"; 3 - import { focusBlock } from "src/utils/focusBlock"; 4 - 5 - import { focusCard } from "components/Cards"; 6 - import { useEntity, useReplicache } from "src/replicache"; 7 - import { useUIState } from "src/useUIState"; 8 - import { RenderedTextBlock } from "components/Blocks/TextBlock"; 9 - import { useDocMetadata } from "src/hooks/queries/useDocMetadata"; 10 - import { CSSProperties, useEffect, useRef, useState } from "react"; 11 - import { useBlocks } from "src/hooks/queries/useBlocks"; 12 - 13 - export function CardBlock(props: BlockProps & { preview?: boolean }) { 14 - let { rep } = useReplicache(); 15 - let card = useEntity(props.entityID, "block/card"); 16 - let cardEntity = card ? card.data.value : props.entityID; 17 - let docMetadata = useDocMetadata(cardEntity); 18 - 19 - let isSelected = useUIState((s) => 20 - s.selectedBlocks.find((b) => b.value === props.entityID), 21 - ); 22 - 23 - let isOpen = useUIState((s) => s.openCards).includes(cardEntity); 24 - 25 - return ( 26 - <div 27 - style={{ "--list-marker-width": "20px" } as CSSProperties} 28 - className={` 29 - cardBlockWrapper relative group/cardBlock 30 - w-full h-[104px] 31 - bg-bg-card border shadow-sm outline outline-1 rounded-lg 32 - flex overflow-clip 33 - ${ 34 - isSelected 35 - ? "border-tertiary outline-tertiary" 36 - : isOpen 37 - ? "border-border outline-transparent hover:outline-border-light" 38 - : "border-border-light outline-transparent hover:outline-border-light" 39 - } 40 - `} 41 - > 42 - <> 43 - <div 44 - className="cardBlockContent w-full flex overflow-clip cursor-pointer" 45 - onClick={(e) => { 46 - if (e.isDefaultPrevented()) return; 47 - if (e.shiftKey) return; 48 - e.preventDefault(); 49 - e.stopPropagation(); 50 - useUIState.getState().openCard(props.parent, cardEntity); 51 - if (rep) focusCard(cardEntity, rep); 52 - }} 53 - > 54 - <div className="my-2 ml-3 grow min-w-0 text-sm bg-transparent overflow-clip "> 55 - {docMetadata[0] && ( 56 - <div 57 - className={`cardBlockOne outline-none resize-none align-top flex gap-2 ${docMetadata[0].type === "heading" ? "font-bold text-base" : ""}`} 58 - > 59 - {docMetadata[0].listData && ( 60 - <ListMarker 61 - {...docMetadata[0]} 62 - className={ 63 - docMetadata[0].type === "heading" 64 - ? "!pt-[12px]" 65 - : "!pt-[8px]" 66 - } 67 - /> 68 - )} 69 - <RenderedTextBlock entityID={docMetadata[0].value} /> 70 - </div> 71 - )} 72 - {docMetadata[1] && ( 73 - <div 74 - className={`cardBlockLineTwo outline-none resize-none align-top flex gap-2 ${docMetadata[1].type === "heading" ? "font-bold" : ""}`} 75 - > 76 - {docMetadata[1].listData && ( 77 - <ListMarker {...docMetadata[1]} className="!pt-[8px]" /> 78 - )} 79 - <RenderedTextBlock entityID={docMetadata[1].value} /> 80 - </div> 81 - )} 82 - {docMetadata[2] && ( 83 - <div 84 - className={`cardBlockLineThree outline-none resize-none align-top flex gap-2 ${docMetadata[2].type === "heading" ? "font-bold" : ""}`} 85 - > 86 - {docMetadata[2].listData && ( 87 - <ListMarker {...docMetadata[2]} className="!pt-[8px]" /> 88 - )} 89 - <RenderedTextBlock entityID={docMetadata[2].value} /> 90 - </div> 91 - )} 92 - </div> 93 - {props.preview && <CardPreview entityID={cardEntity} />} 94 - </div> 95 - </> 96 - </div> 97 - ); 98 - } 99 - 100 - export function CardPreview(props: { entityID: string }) { 101 - let blocks = useBlocks(props.entityID); 102 - let previewRef = useRef<HTMLDivElement | null>(null); 103 - 104 - let cardWidth = `var(--card-width-unitless)`; 105 - return ( 106 - <div 107 - ref={previewRef} 108 - className={`cardBlockPreview w-[120px] overflow-clip mx-3 mt-3 -mb-2 bg-bg-card border rounded-md shrink-0 border-border-light flex flex-col gap-0.5 rotate-[4deg] origin-center`} 109 - > 110 - <div 111 - className="absolute top-0 left-0 h-full origin-top-left pointer-events-none" 112 - style={{ 113 - width: `calc(1px * ${cardWidth})`, 114 - transform: `scale(calc((120 / ${cardWidth} )))`, 115 - }} 116 - > 117 - {blocks.slice(0, 20).map((b, index, arr) => { 118 - return ( 119 - <BlockPreview 120 - entityID={b.value} 121 - previousBlock={arr[index - 1] || null} 122 - nextBlock={arr[index + 1] || null} 123 - nextPosition={""} 124 - previewRef={previewRef} 125 - {...b} 126 - key={b.factID} 127 - /> 128 - ); 129 - })} 130 - </div> 131 - </div> 132 - ); 133 - } 134 - 135 - export function BlockPreview( 136 - b: BlockProps & { 137 - previewRef: React.RefObject<HTMLDivElement>; 138 - size?: "small" | "large"; 139 - }, 140 - ) { 141 - let headingLevel = useEntity(b.value, "block/heading-level")?.data.value; 142 - let ref = useRef<HTMLDivElement | null>(null); 143 - let [isVisible, setIsVisible] = useState(true); 144 - useEffect(() => { 145 - if (!ref.current) return; 146 - let observer = new IntersectionObserver( 147 - (entries) => { 148 - entries.forEach((entry) => { 149 - if (entry.isIntersecting) { 150 - setIsVisible(true); 151 - } else { 152 - setIsVisible(false); 153 - } 154 - }); 155 - }, 156 - { threshold: 0.01, root: b.previewRef.current }, 157 - ); 158 - observer.observe(ref.current); 159 - return () => observer.disconnect(); 160 - }, [b.previewRef]); 161 - return <div ref={ref}>{isVisible && <Block {...b} preview />}</div>; 162 - }
+7 -7
components/Blocks/DeleteBlock.tsx
··· 89 89 let focusedBlock = useUIState.getState().focusedEntity; 90 90 91 91 // if the focused thing is a page and not a block, return 92 - if (!focusedBlock || focusedBlock?.entityType === "card") return; 92 + if (!focusedBlock || focusedBlock?.entityType === "page") return; 93 93 let [type] = await rep.query((tx) => 94 94 scanIndex(tx).eav(focusedBlock.entityID, "block/type"), 95 95 ); 96 96 97 - // get what cards we need to close as a result of deleting this block 98 - let cardsToClose = [] as string[]; 97 + // get what pagess we need to close as a result of deleting this block 98 + let pagesToClose = [] as string[]; 99 99 if (type.data.value === "card") { 100 - let [childCards] = await rep?.query( 100 + let [childPages] = await rep?.query( 101 101 (tx) => scanIndex(tx).eav(focusedBlock.entityID, "block/card") || [], 102 102 ); 103 - cardsToClose = [childCards?.data.value]; 103 + pagesToClose = [childPages?.data.value]; 104 104 } 105 105 if (type.data.value === "mailbox") { 106 106 let [archive] = await rep?.query( ··· 109 109 let [draft] = await rep?.query( 110 110 (tx) => scanIndex(tx).eav(focusedBlock.entityID, "mailbox/draft") || [], 111 111 ); 112 - cardsToClose = [archive?.data.value, draft?.data.value]; 112 + pagesToClose = [archive?.data.value, draft?.data.value]; 113 113 } 114 114 115 115 // the next and previous blocks in the block list ··· 164 164 ); 165 165 } 166 166 167 - cardsToClose.forEach((card) => card && useUIState.getState().closeCard(card)); 167 + pagesToClose.forEach((page) => page && useUIState.getState().closePage(page)); 168 168 await Promise.all( 169 169 entities.map((entity) => 170 170 rep?.mutate.removeBlock({
+1 -1
components/Blocks/ExternalLinkBlock.tsx
··· 17 17 target="_blank" 18 18 className={` 19 19 externalLinkBlock flex relative group/linkBlock 20 - h-[104px] w-full bg-bg-card overflow-hidden text-primary hover:no-underline no-underline 20 + h-[104px] w-full bg-bg-page overflow-hidden text-primary hover:no-underline no-underline 21 21 border hover:border-accent-contrast outline outline-1 -outline-offset-0 rounded-lg shadow-sm 22 22 ${isSelected ? "outline-accent-contrast border-accent-contrast" : "outline-transparent border-border-light"} 23 23 `}
+18 -19
components/Blocks/MailboxBlock.tsx
··· 11 11 import { useEntitySetContext } from "components/EntitySetProvider"; 12 12 import { subscribeToMailboxWithEmail } from "actions/subscriptions/subscribeToMailboxWithEmail"; 13 13 import { confirmEmailSubscription } from "actions/subscriptions/confirmEmailSubscription"; 14 - import { focusCard } from "components/Cards"; 14 + import { focusPage } from "components/Pages"; 15 15 import { v7 } from "uuid"; 16 16 import { sendPostToSubscribers } from "actions/subscriptions/sendPostToSubscribers"; 17 17 import { getBlocksWithType } from "src/hooks/queries/useBlocks"; ··· 23 23 unsubscribe, 24 24 useSubscriptionStatus, 25 25 } from "src/hooks/useSubscriptionStatus"; 26 - import { scanIndex } from "src/replicache/utils"; 27 - import { usePageTitle } from "components/utils/UpdatePageTitle"; 26 + import { usePageTitle } from "components/utils/UpdateLeafletTitle"; 28 27 29 28 export const MailboxBlock = (props: BlockProps) => { 30 29 let isSubscribed = useSubscriptionStatus(props.entityID); ··· 33 32 s.selectedBlocks.find((b) => b.value === props.entityID), 34 33 ); 35 34 36 - let card = useEntity(props.entityID, "block/card"); 37 - let cardEntity = card ? card.data.value : props.entityID; 35 + let page = useEntity(props.entityID, "block/card"); 36 + let pageEntity = page ? page.data.value : props.entityID; 38 37 let permission = useEntitySetContext().permissions.write; 39 38 40 39 let { rep } = useReplicache(); ··· 75 74 props.previousBlock && 76 75 focusBlock(props.previousBlock, { type: "end" }); 77 76 78 - draft && useUIState.getState().closeCard(draft.data.value); 79 - archive && useUIState.getState().closeCard(archive.data.value); 77 + draft && useUIState.getState().closePage(draft.data.value); 78 + archive && useUIState.getState().closePage(archive.data.value); 80 79 } 81 80 } 82 81 }; ··· 86 85 draft, 87 86 archive, 88 87 areYouSure, 89 - cardEntity, 88 + pageEntity, 90 89 isSelected, 91 90 permission, 92 91 props.entityID, ··· 110 109 }`} 111 110 style={{ 112 111 backgroundColor: 113 - "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-card)) 85%)", 112 + "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)", 114 113 }} 115 114 > 116 115 <div className="flex gap-2 p-4"> ··· 129 128 firstBlockFactID: v7(), 130 129 }); 131 130 } 132 - useUIState.getState().openCard(props.parent, entity); 133 - if (rep) focusCard(entity, rep, "focusFirstBlock"); 131 + useUIState.getState().openPage(props.parent, entity); 132 + if (rep) focusPage(entity, rep, "focusFirstBlock"); 134 133 return; 135 134 }} 136 135 > ··· 202 201 }`} 203 202 style={{ 204 203 backgroundColor: 205 - "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-card)) 85%)", 204 + "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)", 206 205 }} 207 206 > 208 207 <div className="flex flex-col w-full gap-2 p-4"> ··· 227 226 if (rep) { 228 227 useUIState 229 228 .getState() 230 - .openCard(props.parent, archive.data.value); 231 - focusCard(archive.data.value, rep); 229 + .openPage(props.parent, archive.data.value); 230 + focusPage(archive.data.value, rep); 232 231 } 233 232 }} 234 233 > ··· 378 377 <input 379 378 type="number" 380 379 value={code} 381 - className="appearance-none focus:outline-none focus:border-border w-20 border border-border-light bg-bg-card rounded-md p-1" 380 + className="appearance-none focus:outline-none focus:border-border w-20 border border-border-light bg-bg-page rounded-md p-1" 382 381 onChange={(e) => setCode(e.currentTarget.value)} 383 382 /> 384 383 ··· 415 414 }} 416 415 className={`mailboxSubscribeForm flex sm:flex-row flex-col ${props.compact && "sm:flex-col sm:gap-2"} gap-2 sm:gap-3 items-center place-self-center mx-auto`} 417 416 > 418 - <div className="mailboxChannelInput flex gap-2 border border-border-light bg-bg-card rounded-md py-1 px-2 grow max-w-72 "> 417 + <div className="mailboxChannelInput flex gap-2 border border-border-light bg-bg-page rounded-md py-1 px-2 grow max-w-72 "> 419 418 <input 420 419 value={email} 421 420 type="email" ··· 480 479 ); 481 480 if (!draft) return null; 482 481 483 - // once the send button is clicked, close the card and show a toast. 482 + // once the send button is clicked, close the page and show a toast. 484 483 return ( 485 484 <div className="flex justify-between items-center text-sm"> 486 485 <div className="flex gap-2"> ··· 543 542 onMouseDown={(e) => { 544 543 e.preventDefault(); 545 544 if (rep) { 546 - useUIState.getState().openCard(props.parent, archive.data.value); 547 - focusCard(archive.data.value, rep); 545 + useUIState.getState().openPage(props.parent, archive.data.value); 546 + focusPage(archive.data.value, rep); 548 547 } 549 548 }} 550 549 >
+161
components/Blocks/PageLinkBlock.tsx
··· 1 + "use client"; 2 + import { BlockProps, BaseBlock, ListMarker, Block } from "./Block"; 3 + import { focusBlock } from "src/utils/focusBlock"; 4 + 5 + import { focusPage } from "components/Pages"; 6 + import { useEntity, useReplicache } from "src/replicache"; 7 + import { useUIState } from "src/useUIState"; 8 + import { RenderedTextBlock } from "components/Blocks/TextBlock"; 9 + import { usePageMetadata } from "src/hooks/queries/usePageMetadata"; 10 + import { CSSProperties, useEffect, useRef, useState } from "react"; 11 + import { useBlocks } from "src/hooks/queries/useBlocks"; 12 + 13 + export function PageLinkBlock(props: BlockProps & { preview?: boolean }) { 14 + let { rep } = useReplicache(); 15 + let page = useEntity(props.entityID, "block/card"); 16 + let pageEntity = page ? page.data.value : props.entityID; 17 + let leafletMetadata = usePageMetadata(pageEntity); 18 + 19 + let isSelected = useUIState((s) => 20 + s.selectedBlocks.find((b) => b.value === props.entityID), 21 + ); 22 + 23 + let isOpen = useUIState((s) => s.openPages).includes(pageEntity); 24 + 25 + return ( 26 + <div 27 + style={{ "--list-marker-width": "20px" } as CSSProperties} 28 + className={` 29 + pageLinkBlockWrapper relative group/pageLinkBlock 30 + w-full h-[104px] 31 + bg-bg-page border shadow-sm outline outline-1 rounded-lg 32 + flex overflow-clip 33 + ${ 34 + isSelected 35 + ? "border-tertiary outline-tertiary" 36 + : isOpen 37 + ? "border-border outline-transparent hover:outline-border-light" 38 + : "border-border-light outline-transparent hover:outline-border-light" 39 + } 40 + `} 41 + > 42 + <> 43 + <div 44 + className="pageLinkBlockContent w-full flex overflow-clip cursor-pointer" 45 + onClick={(e) => { 46 + if (e.isDefaultPrevented()) return; 47 + if (e.shiftKey) return; 48 + e.preventDefault(); 49 + e.stopPropagation(); 50 + useUIState.getState().openPage(props.parent, pageEntity); 51 + if (rep) focusPage(pageEntity, rep); 52 + }} 53 + > 54 + <div className="my-2 ml-3 grow min-w-0 text-sm bg-transparent overflow-clip "> 55 + {leafletMetadata[0] && ( 56 + <div 57 + className={`pageBlockOne outline-none resize-none align-top flex gap-2 ${leafletMetadata[0].type === "heading" ? "font-bold text-base" : ""}`} 58 + > 59 + {leafletMetadata[0].listData && ( 60 + <ListMarker 61 + {...leafletMetadata[0]} 62 + className={ 63 + leafletMetadata[0].type === "heading" 64 + ? "!pt-[12px]" 65 + : "!pt-[8px]" 66 + } 67 + /> 68 + )} 69 + <RenderedTextBlock entityID={leafletMetadata[0].value} /> 70 + </div> 71 + )} 72 + {leafletMetadata[1] && ( 73 + <div 74 + className={`pageBlockLineTwo outline-none resize-none align-top flex gap-2 ${leafletMetadata[1].type === "heading" ? "font-bold" : ""}`} 75 + > 76 + {leafletMetadata[1].listData && ( 77 + <ListMarker {...leafletMetadata[1]} className="!pt-[8px]" /> 78 + )} 79 + <RenderedTextBlock entityID={leafletMetadata[1].value} /> 80 + </div> 81 + )} 82 + {leafletMetadata[2] && ( 83 + <div 84 + className={`pageBlockLineThree outline-none resize-none align-top flex gap-2 ${leafletMetadata[2].type === "heading" ? "font-bold" : ""}`} 85 + > 86 + {leafletMetadata[2].listData && ( 87 + <ListMarker {...leafletMetadata[2]} className="!pt-[8px]" /> 88 + )} 89 + <RenderedTextBlock entityID={leafletMetadata[2].value} /> 90 + </div> 91 + )} 92 + </div> 93 + {props.preview && <PagePreview entityID={pageEntity} />} 94 + </div> 95 + </> 96 + </div> 97 + ); 98 + } 99 + 100 + export function PagePreview(props: { entityID: string }) { 101 + let blocks = useBlocks(props.entityID); 102 + let previewRef = useRef<HTMLDivElement | null>(null); 103 + 104 + let pageWidth = `var(--page-width-unitless)`; 105 + return ( 106 + <div 107 + ref={previewRef} 108 + className={`pageLinkBlockPreview w-[120px] overflow-clip mx-3 mt-3 -mb-2 bg-bg-page border rounded-md shrink-0 border-border-light flex flex-col gap-0.5 rotate-[4deg] origin-center`} 109 + > 110 + <div 111 + className="absolute top-0 left-0 h-full origin-top-left pointer-events-none" 112 + style={{ 113 + width: `calc(1px * ${pageWidth})`, 114 + transform: `scale(calc((120 / ${pageWidth} )))`, 115 + }} 116 + > 117 + {blocks.slice(0, 20).map((b, index, arr) => { 118 + return ( 119 + <BlockPreview 120 + entityID={b.value} 121 + previousBlock={arr[index - 1] || null} 122 + nextBlock={arr[index + 1] || null} 123 + nextPosition={""} 124 + previewRef={previewRef} 125 + {...b} 126 + key={b.factID} 127 + /> 128 + ); 129 + })} 130 + </div> 131 + </div> 132 + ); 133 + } 134 + 135 + export function BlockPreview( 136 + b: BlockProps & { 137 + previewRef: React.RefObject<HTMLDivElement>; 138 + size?: "small" | "large"; 139 + }, 140 + ) { 141 + let ref = useRef<HTMLDivElement | null>(null); 142 + let [isVisible, setIsVisible] = useState(true); 143 + useEffect(() => { 144 + if (!ref.current) return; 145 + let observer = new IntersectionObserver( 146 + (entries) => { 147 + entries.forEach((entry) => { 148 + if (entry.isIntersecting) { 149 + setIsVisible(true); 150 + } else { 151 + setIsVisible(false); 152 + } 153 + }); 154 + }, 155 + { threshold: 0.01, root: b.previewRef.current }, 156 + ); 157 + observer.observe(ref.current); 158 + return () => observer.disconnect(); 159 + }, [b.previewRef]); 160 + return <div ref={ref}>{isVisible && <Block {...b} preview />}</div>; 161 + }
+4 -4
components/Blocks/TextBlock/index.tsx
··· 96 96 let vis = await isVisible(target as Element); 97 97 if (!vis) { 98 98 let parentEl = document.getElementById( 99 - elementId.card(props.parent).container, 99 + elementId.page(props.parent).container, 100 100 ); 101 101 if (!parentEl) return; 102 102 parentEl?.scrollBy({ ··· 121 121 // show a blank line if the block is empty. blocks with content are styled elsewhere! update both! 122 122 return ( 123 123 <pre className={`${props.className} italic text-tertiary`}> 124 - {/* Render a placeholder if there are no other blocks in the card, else just show the blank line*/} 124 + {/* Render a placeholder if there are no other blocks in the page, else just show the blank line*/} 125 125 {props.first ? "Title" : <br />} 126 126 </pre> 127 127 ); ··· 341 341 const coords = view.coordsAtPos(view.state.selection.anchor); 342 342 useEditorStates.setState({ lastXPosition: coords.left }); 343 343 344 - // scroll card if cursor is at the very top or very bottom of the card 344 + // scroll page if cursor is at the very top or very bottom of the page 345 345 let parentID = document.getElementById( 346 - elementId.card(props.parentID).container, 346 + elementId.page(props.parentID).container, 347 347 ); 348 348 let parentHeight = parentID?.clientHeight; 349 349 let cursorPosY = coords.top;
+36 -26
components/Blocks/TextBlock/keymap.ts
··· 12 12 import { schema } from "./schema"; 13 13 import { useUIState } from "src/useUIState"; 14 14 import { setEditorState, useEditorStates } from "src/state/useEditorState"; 15 - import { focusCard } from "components/Cards"; 15 + import { focusPage } from "components/Pages"; 16 16 import { v7 } from "uuid"; 17 17 import { scanIndex } from "src/replicache/utils"; 18 18 import { indent, outdent } from "src/utils/list-operations"; ··· 47 47 view?.dom.blur(); 48 48 useUIState.setState(() => ({ 49 49 focusedEntity: { 50 - entityType: "card", 50 + entityType: "page", 51 51 entityID: propsRef.current.parent, 52 52 }, 53 53 selectedBlocks: [], ··· 444 444 return true; 445 445 }; 446 446 447 - 448 - const metaA = ( 447 + const metaA = 448 + ( 449 449 propsRef: MutableRefObject<BlockProps & { entity_set: { set: string } }>, 450 450 repRef: MutableRefObject<Replicache<ReplicacheMutators> | null>, 451 - )=> (state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, view: EditorView | undefined) => { 452 - const { from, to } = state.selection; 453 - // Check if the entire content of the blockk is selected 454 - const isFullySelected = from === 0 && to === state.doc.content.size; 455 - 456 - if (!isFullySelected) { 457 - // If the entire block is selected, we don't need to do anything 458 - return false 459 - } else { 460 - // Remove the selection 461 - view?.dispatch(state.tr.setSelection(TextSelection.create(state.doc, from))); 462 - view?.dom.blur() 463 - repRef.current?.query(async tx=>{ 464 - let allBlocks = await getBlocksWithType(tx, propsRef.current.parent) ||[] 465 - console.log("allBlocks", allBlocks) 466 - useUIState.setState({ 467 - selectedBlocks: allBlocks.map(b=>({value: b.value, parent: propsRef.current.parent})) 468 - }) 469 - }) 470 - return true 471 - } 451 + ) => 452 + ( 453 + state: EditorState, 454 + dispatch: ((tr: Transaction) => void) | undefined, 455 + view: EditorView | undefined, 456 + ) => { 457 + const { from, to } = state.selection; 458 + // Check if the entire content of the blockk is selected 459 + const isFullySelected = from === 0 && to === state.doc.content.size; 472 460 473 - } 461 + if (!isFullySelected) { 462 + // If the entire block is selected, we don't need to do anything 463 + return false; 464 + } else { 465 + // Remove the selection 466 + view?.dispatch( 467 + state.tr.setSelection(TextSelection.create(state.doc, from)), 468 + ); 469 + view?.dom.blur(); 470 + repRef.current?.query(async (tx) => { 471 + let allBlocks = 472 + (await getBlocksWithType(tx, propsRef.current.parent)) || []; 473 + console.log("allBlocks", allBlocks); 474 + useUIState.setState({ 475 + selectedBlocks: allBlocks.map((b) => ({ 476 + value: b.value, 477 + parent: propsRef.current.parent, 478 + })), 479 + }); 480 + }); 481 + return true; 482 + } 483 + };
+2 -2
components/Blocks/index.tsx
··· 153 153 }, 10); 154 154 }} 155 155 > 156 - {/* this is here as a fail safe, in case a new card is created and there are no blocks in it yet, 157 - we render a newcardbutton with a textblock-like placeholder instead of a proper first block. */} 156 + {/* this is here as a fail safe, in case a new page is created and there are no blocks in it yet, 157 + we render a newblockbutton with a textblock-like placeholder instead of a proper first block. */} 158 158 {!props.lastBlock ? ( 159 159 <div className="pt-2 sm:pt-3">write something...</div> 160 160 ) : (
+2 -2
components/Blocks/useBlockKeyboardHandlers.ts
··· 105 105 } 106 106 // ... and areYouSure state is true, 107 107 // and the user is not in an input or textarea, 108 - // if there is a card to close, close it and remove the block 108 + // if there is a page to close, close it and remove the block 109 109 if (areYouSure) { 110 110 let el = e.target as HTMLElement; 111 111 ··· 122 122 123 123 e.preventDefault(); 124 124 rep.mutate.removeBlock({ blockEntity: props.entityID }); 125 - useUIState.getState().closeCard(props.entityID); 125 + useUIState.getState().closePage(props.entityID); 126 126 let prevBlock = props.previousBlock; 127 127 if (prevBlock) focusBlock(prevBlock, { type: "end" }); 128 128 }
+61 -61
components/Cards.tsx components/Pages.tsx
··· 6 6 import { elementId } from "src/utils/elementId"; 7 7 import { ThemePopover } from "./ThemeManager/ThemeSetter"; 8 8 import { Media } from "./Media"; 9 - import { DesktopCardFooter } from "./DesktopFooter"; 9 + import { DesktopPageFooter } from "./DesktopFooter"; 10 10 import { Replicache } from "replicache"; 11 11 import { 12 12 Fact, ··· 26 26 import { DraftPostOptions } from "./Blocks/MailboxBlock"; 27 27 import { useIsMobile } from "src/hooks/isMobile"; 28 28 29 - export function Cards(props: { rootCard: string }) { 30 - let openCards = useUIState((s) => s.openCards); 29 + export function Pages(props: { rootPage: string }) { 30 + let openPages = useUIState((s) => s.openPages); 31 31 let params = useSearchParams(); 32 - let openCard = params.get("openCard"); 32 + let openPage = params.get("openPage"); 33 33 useEffect(() => { 34 - if (openCard) { 34 + if (openPage) { 35 35 } 36 - }, [openCard, props.rootCard]); 37 - let cards = [...openCards]; 38 - if (openCard && !cards.includes(openCard)) cards.push(openCard); 36 + }, [openPage, props.rootPage]); 37 + let pages = [...openPages]; 38 + if (openPage && !pages.includes(openPage)) pages.push(openPage); 39 39 40 40 return ( 41 41 <div 42 - id="cards" 43 - className="cards flex pt-2 pb-8 sm:py-6" 42 + id="pages" 43 + className="pages flex pt-2 pb-8 sm:py-6" 44 44 onClick={(e) => { 45 - e.currentTarget === e.target && blurCard(); 45 + e.currentTarget === e.target && blurPage(); 46 46 }} 47 47 > 48 48 <div 49 49 className="spacer flex justify-end items-start" 50 - style={{ width: `calc(50vw - ((var(--card-width-units)/2))` }} 50 + style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 51 51 onClick={(e) => { 52 - e.currentTarget === e.target && blurCard(); 52 + e.currentTarget === e.target && blurPage(); 53 53 }} 54 54 > 55 55 <Media mobile={false} className="h-full"> 56 56 <div className="flex flex-col h-full justify-between mr-4 mt-1"> 57 57 <div className="flex flex-col justify-center gap-2 "> 58 - <ShareOptions rootEntity={props.rootCard} /> 59 - <PageOptions entityID={props.rootCard} /> 58 + <ShareOptions rootEntity={props.rootPage} /> 59 + <LeafletOptions entityID={props.rootPage} /> 60 60 <hr className="text-border my-3" /> 61 61 <HomeButton /> 62 62 </div> ··· 64 64 </Media> 65 65 </div> 66 66 <div className="flex items-stretch"> 67 - <Card entityID={props.rootCard} first /> 67 + <Page entityID={props.rootPage} first /> 68 68 </div> 69 - {cards.map((card) => ( 70 - <div className="flex items-stretch" key={card}> 71 - <Card entityID={card} /> 69 + {pages.map((page) => ( 70 + <div className="flex items-stretch" key={page}> 71 + <Page entityID={page} /> 72 72 </div> 73 73 ))} 74 74 <div 75 75 className="spacer" 76 - style={{ width: `calc(50vw - ((var(--card-width-units)/2))` }} 76 + style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 77 77 onClick={(e) => { 78 - e.currentTarget === e.target && blurCard(); 78 + e.currentTarget === e.target && blurPage(); 79 79 }} 80 80 /> 81 81 </div> 82 82 ); 83 83 } 84 84 85 - export const PageOptions = (props: { entityID: string }) => { 85 + export const LeafletOptions = (props: { entityID: string }) => { 86 86 return ( 87 87 <> 88 88 <ThemePopover entityID={props.entityID} /> ··· 90 90 ); 91 91 }; 92 92 93 - function Card(props: { entityID: string; first?: boolean }) { 93 + function Page(props: { entityID: string; first?: boolean }) { 94 94 let { rep } = useReplicache(); 95 95 let isDraft = useReferenceToEntity("mailbox/draft", props.entityID); 96 96 97 97 let focusedElement = useUIState((s) => s.focusedEntity); 98 - let focusedCardID = 99 - focusedElement?.entityType === "card" 98 + let focusedPageID = 99 + focusedElement?.entityType === "page" 100 100 ? focusedElement.entityID 101 101 : focusedElement?.parent; 102 - let isFocused = focusedCardID === props.entityID; 102 + let isFocused = focusedPageID === props.entityID; 103 103 let isMobile = useIsMobile(); 104 104 105 105 return ( ··· 108 108 <div 109 109 className="w-6 lg:snap-center" 110 110 onClick={(e) => { 111 - e.currentTarget === e.target && blurCard(); 111 + e.currentTarget === e.target && blurPage(); 112 112 }} 113 113 /> 114 114 )} 115 - <div className="cardWrapper w-fit flex relative snap-center"> 115 + <div className="pageWrapper w-fit flex relative snap-center"> 116 116 <div 117 117 onMouseDown={(e) => { 118 118 if (e.defaultPrevented) return; 119 119 if (!isMobile) return; 120 120 if (rep) { 121 - focusCard(props.entityID, rep); 121 + focusPage(props.entityID, rep); 122 122 } 123 123 }} 124 - id={elementId.card(props.entityID).container} 124 + id={elementId.page(props.entityID).container} 125 125 style={{ 126 - backgroundColor: "rgba(var(--bg-card), var(--bg-card-alpha))", 127 - width: "var(--card-width-units)", 126 + backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 127 + width: "var(--page-width-units)", 128 128 }} 129 129 className={` 130 - card 130 + page 131 131 grow flex flex-col 132 132 overscroll-y-none 133 133 overflow-y-scroll no-scrollbar ··· 136 136 `} 137 137 > 138 138 <Media mobile={true}> 139 - {!props.first && <CardOptions entityID={props.entityID} />} 139 + {!props.first && <PageOptionsMenu entityID={props.entityID} />} 140 140 </Media> 141 - <DesktopCardFooter cardID={props.entityID} /> 141 + <DesktopPageFooter pageID={props.entityID} /> 142 142 {isDraft.length > 0 && ( 143 143 <div 144 - className={`cardStatus pt-[6px] pb-1 ${!props.first ? "pr-10 pl-3 sm:px-4" : "px-3 sm:px-4"} border-b border-border text-tertiary`} 144 + className={`pageStatus pt-[6px] pb-1 ${!props.first ? "pr-10 pl-3 sm:px-4" : "px-3 sm:px-4"} border-b border-border text-tertiary`} 145 145 style={{ 146 146 backgroundColor: 147 - "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-card)) 85%)", 147 + "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)", 148 148 }} 149 149 > 150 150 <DraftPostOptions mailboxEntity={isDraft[0].entity} /> ··· 154 154 </div> 155 155 <Media mobile={false}> 156 156 {isFocused && !props.first && ( 157 - <CardOptions entityID={props.entityID} /> 157 + <PageOptionsMenu entityID={props.entityID} /> 158 158 )} 159 159 </Media> 160 160 </div> ··· 162 162 ); 163 163 } 164 164 165 - const CardOptions = (props: { entityID: string }) => { 165 + const PageOptionsMenu = (props: { entityID: string }) => { 166 166 let permission = useEntitySetContext().permissions.write; 167 167 return ( 168 168 <div className=" z-10 w-fit absolute sm:top-2 sm:-right-[18px] top-0 right-3 flex sm:flex-col flex-row-reverse gap-1 items-start"> 169 169 <button 170 - className="p-1 pt-[10px] sm:p-0.5 sm:pl-0 bg-border text-bg-card sm:rounded-r-md sm:rounded-l-none rounded-b-md hover:bg-accent-1 hover:text-accent-2" 170 + className="p-1 pt-[10px] sm:p-0.5 sm:pl-0 bg-border text-bg-page sm:rounded-r-md sm:rounded-l-none rounded-b-md hover:bg-accent-1 hover:text-accent-2" 171 171 onClick={() => { 172 - useUIState.getState().closeCard(props.entityID); 172 + useUIState.getState().closePage(props.entityID); 173 173 }} 174 174 > 175 175 <CloseTiny /> ··· 185 185 <Menu 186 186 trigger={ 187 187 <div 188 - className={`cardOptionsTrigger 188 + className={`pageOptionsTrigger 189 189 shrink-0 sm:h-8 sm:w-5 h-5 w-8 190 - bg-bg-card text-border 190 + bg-bg-page text-border 191 191 border sm:border-l-0 border-t-1 border-border sm:rounded-r-md sm:rounded-l-none rounded-b-md 192 192 sm:hover:border-r-2 hover:border-b-2 hover:border-y-2 hover:border-t-1 193 193 flex items-center justify-center`} ··· 198 198 > 199 199 <MenuItem 200 200 onSelect={(e) => { 201 - // TODO: Wire up delete card 202 - toaster(DeleteCardToast); 201 + // TODO: Wire up delete page 202 + toaster(DeletePageToast); 203 203 }} 204 204 > 205 205 Delete Page <DeleteSmall /> ··· 208 208 ); 209 209 }; 210 210 211 - const CardMenuItem = (props: { 211 + const PageMenuItem = (props: { 212 212 children: React.ReactNode; 213 213 onClick: () => void; 214 214 }) => { 215 215 return ( 216 216 <button 217 - className="cardOptionsMenuItem z-10 text-left text-secondary py-1 px-2 flex gap-2 hover:bg-accent-1 hover:text-accent-2" 217 + className="pageOptionsMenuItem z-10 text-left text-secondary py-1 px-2 flex gap-2 hover:bg-accent-1 hover:text-accent-2" 218 218 onClick={() => { 219 219 props.onClick(); 220 220 }} ··· 224 224 ); 225 225 }; 226 226 227 - const DeleteCardToast = { 227 + const DeletePageToast = { 228 228 content: ( 229 229 <div className="flex gap-2"> 230 - You deleted a card.{" "} 230 + You deleted a page.{" "} 231 231 <button 232 232 className="underline font-bold sm:font-normal sm:hover:font-bold italic" 233 233 onClick={() => { ··· 242 242 duration: 5000, 243 243 } as const; 244 244 245 - export async function focusCard( 246 - cardID: string, 245 + export async function focusPage( 246 + pageID: string, 247 247 rep: Replicache<ReplicacheMutators>, 248 248 focusFirstBlock?: "focusFirstBlock", 249 249 ) { 250 - // if this card is already focused, 250 + // if this page is already focused, 251 251 let focusedBlock = useUIState.getState().focusedEntity; 252 252 if ( 253 - (focusedBlock?.entityType == "card" && focusedBlock.entityID === cardID) || 254 - (focusedBlock?.entityType === "block" && focusedBlock.parent === cardID) 253 + (focusedBlock?.entityType == "page" && focusedBlock.entityID === pageID) || 254 + (focusedBlock?.entityType === "block" && focusedBlock.parent === pageID) 255 255 ) 256 256 return; 257 - // else set this card as focused 257 + // else set this page as focused 258 258 useUIState.setState(() => ({ 259 259 focusedEntity: { 260 - entityType: "card", 261 - entityID: cardID, 260 + entityType: "page", 261 + entityID: pageID, 262 262 }, 263 263 })); 264 264 265 265 setTimeout(async () => { 266 - //scroll to card 267 - document.getElementById(elementId.card(cardID).container)?.scrollIntoView({ 266 + //scroll to page 267 + document.getElementById(elementId.page(pageID).container)?.scrollIntoView({ 268 268 behavior: "smooth", 269 269 inline: "nearest", 270 270 }); ··· 275 275 let blocks = await tx 276 276 .scan< 277 277 Fact<"card/block"> 278 - >({ indexName: "eav", prefix: `${cardID}-card/block` }) 278 + >({ indexName: "eav", prefix: `${pageID}-page/block` }) 279 279 .toArray(); 280 280 281 281 let firstBlock = blocks.sort((a, b) => { ··· 313 313 }, 50); 314 314 } 315 315 316 - const blurCard = () => { 316 + const blurPage = () => { 317 317 useUIState.setState(() => ({ 318 318 focusedEntity: null, 319 319 selectedBlocks: [],
+5 -5
components/DesktopFooter.tsx
··· 4 4 import { Toolbar } from "./Toolbar"; 5 5 import { useEntitySetContext } from "./EntitySetProvider"; 6 6 7 - export function DesktopCardFooter(props: { cardID: string }) { 7 + export function DesktopPageFooter(props: { pageID: string }) { 8 8 let focusedBlock = useUIState((s) => s.focusedEntity); 9 9 let focusedBlockParentID = 10 - focusedBlock?.entityType === "card" 10 + focusedBlock?.entityType === "page" 11 11 ? focusedBlock.entityID 12 12 : focusedBlock?.parent; 13 13 let entity_set = useEntitySetContext(); ··· 19 19 {focusedBlock && 20 20 focusedBlock.entityType === "block" && 21 21 entity_set.permissions.write && 22 - focusedBlockParentID === props.cardID && ( 22 + focusedBlockParentID === props.pageID && ( 23 23 <div 24 - className="pointer-events-auto w-fit mx-auto py-1 px-3 h-9 bg-bg-card border border-border rounded-full shadow-sm" 24 + className="pointer-events-auto w-fit mx-auto py-1 px-3 h-9 bg-bg-page border border-border rounded-full shadow-sm" 25 25 onMouseDown={(e) => { 26 26 if (e.currentTarget === e.target) e.preventDefault(); 27 27 }} 28 28 > 29 29 <Toolbar 30 - cardID={focusedBlockParentID} 30 + pageID={focusedBlockParentID} 31 31 blockID={focusedBlock.entityID} 32 32 /> 33 33 </div>
+1 -1
components/Icons.tsx
··· 84 84 ); 85 85 }; 86 86 87 - export const BlockCardSmall = (props: Props) => { 87 + export const BlockPageLinkSmall = (props: Props) => { 88 88 return ( 89 89 <svg 90 90 width="24"
+2 -2
components/Layout.tsx
··· 24 24 align={props.align ? props.align : "center"} 25 25 sideOffset={4} 26 26 collisionPadding={16} 27 - className={`dropdownMenu z-20 bg-bg-card flex flex-col py-1 gap-0.5 border border-border rounded-md shadow-md ${props.className}`} 27 + className={`dropdownMenu z-20 bg-bg-page flex flex-col py-1 gap-0.5 border border-border rounded-md shadow-md ${props.className}`} 28 28 > 29 29 {props.children} 30 30 <DropdownMenu.Arrow asChild width={16} height={8} viewBox="0 0 16 8"> 31 31 <PopoverArrow 32 32 arrowFill={ 33 - props.background ? props.background : theme.colors["bg-card"] 33 + props.background ? props.background : theme.colors["bg-page"] 34 34 } 35 35 arrowStroke={props.border ? props.border : theme.colors["border"]} 36 36 />
+2 -2
components/MobileFooter.tsx
··· 17 17 focusedBlock.entityType == "block" && 18 18 entity_set.permissions.write ? ( 19 19 <div 20 - className="w-full z-10 p-2 flex bg-bg-card " 20 + className="w-full z-10 p-2 flex bg-bg-page " 21 21 onMouseDown={(e) => { 22 22 if (e.currentTarget === e.target) e.preventDefault(); 23 23 }} 24 24 > 25 25 <Toolbar 26 - cardID={focusedBlock.parent} 26 + pageID={focusedBlock.parent} 27 27 blockID={focusedBlock.entityID} 28 28 /> 29 29 </div>
+2 -2
components/Popover.tsx
··· 15 15 <RadixPopover.Trigger>{props.trigger}</RadixPopover.Trigger> 16 16 <RadixPopover.Portal> 17 17 <RadixPopover.Content 18 - className={`z-20 bg-bg-card border border-border rounded-md px-3 py-2 ${props.className}`} 18 + className={`z-20 bg-bg-page border border-border rounded-md px-3 py-2 ${props.className}`} 19 19 align={props.align ? props.align : "center"} 20 20 sideOffset={4} 21 21 collisionPadding={16} ··· 24 24 <RadixPopover.Arrow asChild width={16} height={8} viewBox="0 0 16 8"> 25 25 <PopoverArrow 26 26 arrowFill={ 27 - props.background ? props.background : theme.colors["bg-card"] 27 + props.background ? props.background : theme.colors["bg-page"] 28 28 } 29 29 arrowStroke={props.border ? props.border : theme.colors["border"]} 30 30 />
+3 -3
components/SelectionManager.tsx
··· 202 202 await rep?.mutate.removeBlock( 203 203 selectedBlocks.map((block) => ({ blockEntity: block.value })), 204 204 ); 205 - useUIState.getState().closeCard(selectedBlocks.map((b) => b.value)); 205 + useUIState.getState().closePage(selectedBlocks.map((b) => b.value)); 206 206 207 207 let nextBlock = 208 208 siblings?.[ ··· 253 253 if ( 254 254 sortedBlocks.length <= 1 || 255 255 !focusedBlock || 256 - focusedBlock.entityType === "card" 256 + focusedBlock.entityType === "page" 257 257 ) 258 258 return; 259 259 let b = focusedBlock; ··· 394 394 if ( 395 395 sortedSelection.length <= 1 || 396 396 !focusedBlock || 397 - focusedBlock.entityType === "card" 397 + focusedBlock.entityType === "page" 398 398 ) 399 399 return; 400 400 let b = focusedBlock;
+1 -1
components/ShareOptions/index.tsx
··· 71 71 Publish 72 72 </div> 73 73 <div className="text-sm font-normal text-tertiary group-hover/publish:text-accent-contrast"> 74 - Share a read only version of this doc 74 + Share a read only version of this leaflet 75 75 </div> 76 76 </div> 77 77 </MenuItem>
+25 -25
components/ThemeManager/ThemeProvider.tsx
··· 8 8 import { useEntity } from "src/replicache"; 9 9 10 10 type CSSVariables = { 11 + "--bg-leaflet": string; 11 12 "--bg-page": string; 12 - "--bg-card": string; 13 13 "--primary": string; 14 14 "--accent-1": string; 15 15 "--accent-2": string; ··· 37 37 function setCSSVariableToColor( 38 38 el: HTMLElement, 39 39 name: string, 40 - value: AriaColor, 40 + value: AriaColor 41 41 ) { 42 42 el?.style.setProperty(name, colorToString(value, "rgb")); 43 43 } ··· 46 46 local?: boolean; 47 47 children: React.ReactNode; 48 48 }) { 49 - let bgPage = useColorAttribute(props.entityID, "theme/page-background"); 50 - let bgCard = useColorAttribute(props.entityID, "theme/card-background"); 49 + let bgLeaflet = useColorAttribute(props.entityID, "theme/page-background"); 50 + let bgPage = useColorAttribute(props.entityID, "theme/card-background"); 51 51 let primary = useColorAttribute(props.entityID, "theme/primary"); 52 52 53 53 let highlight1 = useEntity(props.entityID, "theme/highlight-1"); ··· 56 56 57 57 let accent1 = useColorAttribute(props.entityID, "theme/accent-background"); 58 58 let accent2 = useColorAttribute(props.entityID, "theme/accent-text"); 59 - // set accent contrast to the accent color that has the highest contrast with the card background 59 + // set accent contrast to the accent color that has the highest contrast with the page background 60 60 let accentContrast = [accent1, accent2].sort((a, b) => { 61 61 return ( 62 - getColorContrast(colorToString(b, "rgb"), colorToString(bgCard, "rgb")) - 63 - getColorContrast(colorToString(a, "rgb"), colorToString(bgCard, "rgb")) 62 + getColorContrast(colorToString(b, "rgb"), colorToString(bgPage, "rgb")) - 63 + getColorContrast(colorToString(a, "rgb"), colorToString(bgPage, "rgb")) 64 64 ); 65 65 })[0]; 66 66 ··· 68 68 if (props.local) return; 69 69 let el = document.querySelector(":root") as HTMLElement; 70 70 if (!el) return; 71 + setCSSVariableToColor(el, "--bg-leaflet", bgLeaflet); 71 72 setCSSVariableToColor(el, "--bg-page", bgPage); 72 - setCSSVariableToColor(el, "--bg-card", bgCard); 73 73 el?.style.setProperty( 74 - "--bg-card-alpha", 75 - bgCard.getChannelValue("alpha").toString(), 74 + "--bg-page-alpha", 75 + bgPage.getChannelValue("alpha").toString() 76 76 ); 77 77 setCSSVariableToColor(el, "--primary", primary); 78 78 ··· 84 84 let color = parseColor(`hsba(${highlight1.data.value})`); 85 85 el?.style.setProperty( 86 86 "--highlight-1", 87 - `rgb(${colorToString(color, "rgb")})`, 87 + `rgb(${colorToString(color, "rgb")})` 88 88 ); 89 89 } else { 90 90 el?.style.setProperty( 91 91 "--highlight-1", 92 - "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-card)) 75%)", 92 + "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 75%)" 93 93 ); 94 94 } 95 95 setCSSVariableToColor(el, "--accent-1", accent1); 96 96 setCSSVariableToColor(el, "--accent-2", accent2); 97 97 el?.style.setProperty( 98 98 "--accent-contrast", 99 - colorToString(accentContrast, "rgb"), 99 + colorToString(accentContrast, "rgb") 100 100 ); 101 101 }, [ 102 102 props.local, 103 + bgLeaflet, 103 104 bgPage, 104 - bgCard, 105 105 primary, 106 106 highlight1, 107 107 highlight2, ··· 110 110 accent2, 111 111 accentContrast, 112 112 ]); 113 - let [canonicalCardWidth, setCanonicalCardWidth] = useState(0); 113 + let [canonicalPageWidth, setCanonicalPageWidth] = useState(0); 114 114 useEffect(() => { 115 115 let listener = () => { 116 - let el = document.getElementById("canonical-card-width"); 117 - setCanonicalCardWidth(el?.clientWidth || 0); 116 + let el = document.getElementById("canonical-page-width"); 117 + setCanonicalPageWidth(el?.clientWidth || 0); 118 118 }; 119 119 listener(); 120 120 window.addEventListener("resize", listener); ··· 122 122 }, []); 123 123 return ( 124 124 <div 125 - className="pageWrapper w-full text-primary h-full flex flex-col bg-center items-stretch" 125 + className="leafletWrapper w-full text-primary h-full flex flex-col bg-center items-stretch" 126 126 style={ 127 127 { 128 - "--card-width": canonicalCardWidth, 128 + "--page-width": canonicalPageWidth, 129 + "--bg-leaflet": colorToString(bgLeaflet, "rgb"), 129 130 "--bg-page": colorToString(bgPage, "rgb"), 130 - "--bg-card": colorToString(bgCard, "rgb"), 131 - "--bg-card-alpha": bgCard.getChannelValue("alpha"), 131 + "--bg-page-alpha": bgPage.getChannelValue("alpha"), 132 132 "--primary": colorToString(primary, "rgb"), 133 133 "--accent-1": colorToString(accent1, "rgb"), 134 134 "--accent-2": colorToString(accent2, "rgb"), 135 135 "--accent-contrast": colorToString(accentContrast, "rgb"), 136 136 "--highlight-1": highlight1 137 137 ? `rgb(${colorToString(parseColor(`hsba(${highlight1.data.value})`), "rgb")})` 138 - : "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-card)) 75%)", 138 + : "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 75%)", 139 139 "--highlight-2": colorToString(highlight2, "rgb"), 140 140 "--highlight-3": colorToString(highlight3, "rgb"), 141 141 } as CSSProperties ··· 143 143 > 144 144 <div 145 145 className="h-[0px] w-[calc(100vw-12px)] sm:w-[calc(100vw-128px)] lg:w-[calc(50vw-32px)] max-w-prose" 146 - id="canonical-card-width" 146 + id="canonical-page-width" 147 147 /> 148 148 {props.children} 149 149 </div> ··· 157 157 let backgroundImage = useEntity(props.entityID, "theme/background-image"); 158 158 let backgroundImageRepeat = useEntity( 159 159 props.entityID, 160 - "theme/background-image-repeat", 160 + "theme/background-image-repeat" 161 161 ); 162 162 return ( 163 163 <div 164 - className="pageBackgroundWrapper w-full bg-bg-page text-primary h-full flex flex-col bg-cover bg-center bg-no-repeat items-stretch" 164 + className="LeafletBackgroundWrapper w-full bg-bg-leaflet text-primary h-full flex flex-col bg-cover bg-center bg-no-repeat items-stretch" 165 165 style={ 166 166 { 167 167 backgroundImage: `url(${backgroundImage?.data.src}), url(${backgroundImage?.data.fallback})`,
+30 -30
components/ThemeManager/ThemeSetter.tsx
··· 39 39 40 40 export type pickers = 41 41 | "null" 42 + | "leaflet" 42 43 | "page" 43 - | "card" 44 44 | "accent-1" 45 45 | "accent-2" 46 46 | "text" ··· 50 50 51 51 export function setColorAttribute( 52 52 rep: Replicache<ReplicacheMutators> | null, 53 - entity: string, 53 + entity: string 54 54 ) { 55 55 return (attribute: keyof FilterAttributes<{ type: "color" }>) => 56 56 (color: Color) => ··· 63 63 export const ThemePopover = (props: { entityID: string; home?: boolean }) => { 64 64 let { rep } = useReplicache(); 65 65 // I need to get these variables from replicache and then write them to the DB. I also need to parse them into a state that can be used here. 66 - let pageValue = useColorAttribute(props.entityID, "theme/page-background"); 67 - let cardValue = useColorAttribute(props.entityID, "theme/card-background"); 66 + let leafletValue = useColorAttribute(props.entityID, "theme/page-background"); 67 + let pageValue = useColorAttribute(props.entityID, "theme/card-background"); 68 68 let primaryValue = useColorAttribute(props.entityID, "theme/primary"); 69 69 let accent1Value = useColorAttribute( 70 70 props.entityID, 71 - "theme/accent-background", 71 + "theme/accent-background" 72 72 ); 73 73 let accent2Value = useColorAttribute(props.entityID, "theme/accent-text"); 74 74 let permission = useEntitySetContext().permissions.write; 75 75 let backgroundImage = useEntity(props.entityID, "theme/background-image"); 76 76 let backgroundRepeat = useEntity( 77 77 props.entityID, 78 - "theme/background-image-repeat", 78 + "theme/background-image-repeat" 79 79 ); 80 80 81 81 let [openPicker, setOpenPicker] = useState<pickers>( 82 - props.home === true ? "page" : "null", 82 + props.home === true ? "leaflet" : "null" 83 83 ); 84 84 let set = useMemo(() => { 85 85 return setColorAttribute(rep, props.entityID); ··· 89 89 let values = [] as string[]; 90 90 for (let i = 0; i < 3; i++) { 91 91 values.push( 92 - `${Math.floor(Math.random() * 100)}% ${Math.floor(Math.random() * 100)}%`, 92 + `${Math.floor(Math.random() * 100)}% ${Math.floor(Math.random() * 100)}%` 93 93 ); 94 94 } 95 95 return values; ··· 97 97 98 98 let gradient = [ 99 99 `radial-gradient(at ${randomPositions[0]}, ${accent1Value.toString("hex")}80 2px, transparent 70%)`, 100 - `radial-gradient(at ${randomPositions[1]}, ${cardValue.toString("hex")}66 2px, transparent 60%)`, 100 + `radial-gradient(at ${randomPositions[1]}, ${pageValue.toString("hex")}66 2px, transparent 60%)`, 101 101 `radial-gradient(at ${randomPositions[2]}, ${primaryValue.toString("hex")}B3 2px, transparent 100%)`, 102 102 ].join(", "); 103 103 let viewheight = useViewportSize().height; ··· 110 110 <HoverButton 111 111 icon=<PaintSmall /> 112 112 label="Theme" 113 - background="bg-bg-card" 114 - text="text-bg-card" 113 + background="bg-bg-page" 114 + text="text-bg-page" 115 115 backgroundImage={{ 116 - backgroundColor: pageValue.toString("hex"), 116 + backgroundColor: leafletValue.toString("hex"), 117 117 backgroundImage: gradient, 118 118 }} 119 119 /> ··· 127 127 collisionPadding={16} 128 128 > 129 129 <div className="themeSetterContent flex flex-col w-full overflow-y-scroll no-scrollbar"> 130 - <div className="themeBGPage flex"> 130 + <div className="themeBGLeaflet flex"> 131 131 <BGPicker 132 132 entityID={props.entityID} 133 - thisPicker={"page"} 133 + thisPicker={"leaflet"} 134 134 openPicker={openPicker} 135 135 setOpenPicker={setOpenPicker} 136 136 closePicker={() => setOpenPicker("null")} ··· 146 146 ? "cover" 147 147 : `calc(${backgroundRepeat.data.value}px / 2 )`, 148 148 }} 149 - className={`bg-bg-page mx-2 p-3 mb-3 flex flex-col rounded-md border border-border ${props.home ? "" : "pb-0"}`} 149 + className={`bg-bg-leaflet mx-2 p-3 mb-3 flex flex-col rounded-md border border-border ${props.home ? "" : "pb-0"}`} 150 150 > 151 151 <div className={`flex flex-col z-10 mt-4 -mb-[6px] `}> 152 152 <div 153 - className="themePageControls text-accent-2 flex flex-col gap-2 h-full bg-bg-page p-2 rounded-md border border-accent-2 shadow-[0_0_0_1px_rgb(var(--accent))]" 153 + className="themeLeafletControls text-accent-2 flex flex-col gap-2 h-full bg-bg-leaflet p-2 rounded-md border border-accent-2 shadow-[0_0_0_1px_rgb(var(--accent))]" 154 154 style={{ 155 155 backgroundColor: "rgba(var(--accent-1), 0.6)", 156 156 }} ··· 189 189 {/* <hr className="my-3" /> */} 190 190 <div className="flex flex-col pt-8 -mb-[6px] z-10"> 191 191 <div 192 - className="themePageControls flex flex-col gap-2 h-full text-primary bg-bg-page p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-card))]" 193 - style={{ backgroundColor: "rgba(var(--bg-card), 0.6)" }} 192 + className="themeLeafletControls flex flex-col gap-2 h-full text-primary bg-bg-leaflet p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-page))]" 193 + style={{ backgroundColor: "rgba(var(--bg-page, 0.6)" }} 194 194 > 195 - <div className="themePageColor flex items-start "> 195 + <div className="themeLeafletColor flex items-start "> 196 196 <ColorPicker 197 - label="Page" 197 + label="Leaflet" 198 198 alpha 199 - value={cardValue} 199 + value={pageValue} 200 200 setValue={set("theme/card-background")} 201 - thisPicker={"card"} 201 + thisPicker={"page"} 202 202 openPicker={openPicker} 203 203 setOpenPicker={setOpenPicker} 204 204 closePicker={() => setOpenPicker("null")} 205 205 /> 206 206 </div> 207 - <div className="themePageTextColor w-full flex pr-2 items-start"> 207 + <div className="themeLeafletTextColor w-full flex pr-2 items-start"> 208 208 <ColorPicker 209 209 label="Text" 210 210 value={primaryValue} ··· 218 218 </div> 219 219 <SectionArrow 220 220 fill={theme.colors["primary"]} 221 - stroke={theme.colors["bg-card"]} 221 + stroke={theme.colors["bg-page"]} 222 222 className=" ml-2" 223 223 /> 224 224 </div> ··· 227 227 className="rounded-t-lg p-2 border border-border border-b-transparent shadow-md text-primary" 228 228 style={{ 229 229 backgroundColor: 230 - "rgba(var(--bg-card), var(--bg-card-alpha))", 230 + "rgba(var(--bg-page), var(--bg-page-page))", 231 231 }} 232 232 > 233 233 <p className="font-bold">Hello!</p> ··· 304 304 onFocus={(e) => { 305 305 e.currentTarget.setSelectionRange( 306 306 1, 307 - e.currentTarget.value.length, 307 + e.currentTarget.value.length 308 308 ); 309 309 }} 310 310 onKeyDown={(e) => { ··· 328 328 onFocus={(e) => { 329 329 e.currentTarget.setSelectionRange( 330 330 0, 331 - e.currentTarget.value.length - 1, 331 + e.currentTarget.value.length - 1 332 332 ); 333 333 }} 334 334 onKeyDown={(e) => { ··· 436 436 onFocus={(e) => { 437 437 e.currentTarget.setSelectionRange( 438 438 1, 439 - e.currentTarget.value.length, 439 + e.currentTarget.value.length 440 440 ); 441 441 }} 442 442 onKeyDown={(e) => { ··· 483 483 value={bgColor} 484 484 onChange={setColorAttribute( 485 485 rep, 486 - props.entityID, 486 + props.entityID 487 487 )("theme/page-background")} 488 488 > 489 489 <ColorArea ··· 528 528 <label className="hover:cursor-pointer "> 529 529 <div 530 530 className="flex gap-2 rounded-md px-2 py-1 text-accent-contrast font-bold" 531 - style={{ backgroundColor: "rgba(var(--bg-card), .6" }} 531 + style={{ backgroundColor: "rgba(var(--bg-page), .6" }} 532 532 > 533 533 <BlockImageSmall /> Change Image 534 534 </div>
+5 -5
components/Toolbar/HighlightToolbar.tsx
··· 244 244 collisionPadding={16} 245 245 > 246 246 <div 247 - className="bg-bg-page w-full m-2 p-3 pb-0 flex flex-col rounded-md border border-border" 247 + className="bg-bg-leaflet w-full m-2 p-3 pb-0 flex flex-col rounded-md border border-border" 248 248 style={{ 249 249 backgroundImage: `url(${backgroundImage?.data.src})`, 250 250 backgroundRepeat: backgroundRepeat ? "repeat" : "no-repeat", ··· 255 255 > 256 256 <div className="flex flex-col -mb-[6px] z-10"> 257 257 <div 258 - className="themeHighlightControls flex flex-col gap-2 h-full text-primary bg-bg-page p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-card))]" 259 - style={{ backgroundColor: "rgba(var(--bg-card), 0.6)" }} 258 + className="themeHighlightControls flex flex-col gap-2 h-full text-primary bg-bg-leaflet p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-page))]" 259 + style={{ backgroundColor: "rgba(var(--bg-page), 0.6)" }} 260 260 > 261 261 <ColorPicker 262 262 label="Highlight 1" ··· 292 292 </div> 293 293 <SectionArrow 294 294 fill={theme.colors["primary"]} 295 - stroke={theme.colors["bg-card"]} 295 + stroke={theme.colors["bg-page"]} 296 296 className="ml-2" 297 297 /> 298 298 </div> ··· 300 300 <div 301 301 className="rounded-t-lg p-2 border border-border border-b-transparent shadow-md text-primary" 302 302 style={{ 303 - backgroundColor: "rgba(var(--bg-card), var(--bg-card-alpha))", 303 + backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 304 304 }} 305 305 > 306 306 <p className="font-bold">Pick your highlights!</p>
+5 -5
components/Toolbar/index.tsx
··· 15 15 import { TextToolbar } from "./TextToolbar"; 16 16 import { BlockToolbar } from "./BlockToolbar"; 17 17 import { MultiselectToolbar } from "./MultiSelectToolbar"; 18 - import { focusCard } from "components/Cards"; 18 + import { focusPage } from "components/Pages"; 19 19 import { AreYouSure, deleteBlock } from "components/Blocks/DeleteBlock"; 20 20 21 21 export type ToolbarTypes = ··· 29 29 | "block" 30 30 | "multiselect"; 31 31 32 - export const Toolbar = (props: { cardID: string; blockID: string }) => { 32 + export const Toolbar = (props: { pageID: string; blockID: string }) => { 33 33 let { rep } = useReplicache(); 34 34 35 35 let [toolbarState, setToolbarState] = useState<ToolbarTypes>("default"); ··· 148 148 onClick={() => { 149 149 if (toolbarState === "multiselect" || toolbarState === "block") { 150 150 useUIState.setState({ selectedBlocks: [] }); 151 - rep && focusCard(props.cardID, rep); 151 + rep && focusPage(props.pageID, rep); 152 152 } 153 153 154 154 if (toolbarState === "default") { 155 155 useUIState.setState(() => ({ 156 156 focusedEntity: { 157 - entityType: "card", 158 - entityID: props.cardID, 157 + entityType: "page", 158 + entityID: props.pageID, 159 159 }, 160 160 selectedBlocks: [], 161 161 }));
+1 -1
components/utils/AddDocToHomepage.tsx components/utils/AddLeafletToHomepage.tsx
··· 4 4 import { useEffect } from "react"; 5 5 import { useReplicache } from "src/replicache"; 6 6 7 - export function AddDocToHomepage() { 7 + export function AddLeafletToHomepage() { 8 8 let { permission_token } = useReplicache(); 9 9 useEffect(() => { 10 10 if (permission_token.permission_token_rights[0].write) {
+1 -1
components/utils/UpdatePageTitle.tsx components/utils/UpdateLeafletTitle.tsx
··· 10 10 import { focusBlock } from "src/utils/focusBlock"; 11 11 import { useIsMobile } from "src/hooks/isMobile"; 12 12 13 - export function UpdatePageTitle(props: { entityID: string }) { 13 + export function UpdateLeafletTitle(props: { entityID: string }) { 14 14 let blocks = useBlocks(props.entityID).filter( 15 15 (b) => b.type === "text" || b.type === "heading", 16 16 );
+1 -1
src/hooks/queries/useDocMetadata.ts src/hooks/queries/usePageMetadata.ts
··· 1 1 import { useBlocks } from "./useBlocks"; 2 2 3 - export function useDocMetadata(entityID: string) { 3 + export function usePageMetadata(entityID: string) { 4 4 let blocks = useBlocks(entityID); 5 5 6 6 let textBlocks = blocks.filter(
+2 -3
src/replicache/attributes.ts
··· 1 - const CardAttributes = { 1 + const PageAttributes = { 2 2 "card/block": { 3 3 type: "ordered-reference", 4 4 cardinality: "many", ··· 22 22 type: "text", 23 23 cardinality: "one", 24 24 }, 25 - "page/awareness": { type: "awareness", cardinality: "one" }, 26 25 "block/heading-level": { 27 26 type: "number", 28 27 cardinality: "one", ··· 115 114 } as const; 116 115 117 116 export const Attributes = { 118 - ...CardAttributes, 117 + ...PageAttributes, 119 118 ...BlockAttributes, 120 119 ...LinkBlockAttributes, 121 120 ...ThemeAttributes,
+6 -6
src/replicache/mutations.ts
··· 217 217 }); 218 218 }; 219 219 220 - const addCardBlock: Mutation<{ 220 + const addPageLinkBlock: Mutation<{ 221 221 permission_set: string; 222 222 blockEntity: string; 223 223 firstBlockEntity: string; 224 224 firstBlockFactID: string; 225 - cardEntity: string; 225 + pageEntity: string; 226 226 }> = async (args, ctx) => { 227 227 await ctx.createEntity({ 228 - entityID: args.cardEntity, 228 + entityID: args.pageEntity, 229 229 permission_set: args.permission_set, 230 230 }); 231 231 await ctx.assertFact({ 232 232 entity: args.blockEntity, 233 233 attribute: "block/card", 234 - data: { type: "reference", value: args.cardEntity }, 234 + data: { type: "reference", value: args.pageEntity }, 235 235 }); 236 236 await addBlock( 237 237 { ··· 239 239 permission_set: args.permission_set, 240 240 newEntityID: args.firstBlockEntity, 241 241 type: "heading", 242 - parent: args.cardEntity, 242 + parent: args.pageEntity, 243 243 position: "a0", 244 244 }, 245 245 ctx, ··· 494 494 outdentBlock, 495 495 moveBlockUp, 496 496 moveBlockDown, 497 - addCardBlock, 497 + addPageLinkBlock, 498 498 moveBlock, 499 499 assertFact, 500 500 retractFact,
+10 -10
src/useUIState.ts
··· 8 8 { 9 9 lastUsedHighlight: "1" as "1" | "2" | "3", 10 10 focusedEntity: null as 11 - | { entityType: "card"; entityID: string } 11 + | { entityType: "page"; entityID: string } 12 12 | { entityType: "block"; entityID: string; parent: string } 13 13 | null, 14 14 foldedBlocks: [] as string[], 15 - openCards: [] as string[], 15 + openPages: [] as string[], 16 16 selectedBlocks: [] as SelectedBlock[], 17 17 }, 18 18 (set) => ({ ··· 25 25 }; 26 26 }); 27 27 }, 28 - openCard: (parent: string, card: string) => 28 + openPage: (parent: string, page: string) => 29 29 set((state) => { 30 - let parentPosition = state.openCards.findIndex((s) => s == parent); 30 + let parentPosition = state.openPages.findIndex((s) => s == parent); 31 31 return { 32 - openCards: 32 + openPages: 33 33 parentPosition === -1 34 - ? [card] 35 - : [...state.openCards.slice(0, parentPosition + 1), card], 34 + ? [page] 35 + : [...state.openPages.slice(0, parentPosition + 1), page], 36 36 }; 37 37 }), 38 - closeCard: (cards: string | string[]) => 38 + closePage: (pages: string | string[]) => 39 39 set((s) => ({ 40 - openCards: s.openCards.filter((c) => ![cards].flat().includes(c)), 40 + openPages: s.openPages.filter((c) => ![pages].flat().includes(c)), 41 41 })), 42 42 setFocusedBlock: ( 43 43 b: 44 - | { entityType: "card"; entityID: string } 44 + | { entityType: "page"; entityID: string } 45 45 | { entityType: "block"; entityID: string; parent: string } 46 46 | null, 47 47 ) => set(() => ({ focusedEntity: b })),
+2 -2
src/utils/addLinkBlock.ts
··· 1 - import { addLinkCard } from "actions/addLinkCard"; 1 + import { addPageLink } from "actions/addPageLink"; 2 2 import { Replicache } from "replicache"; 3 3 import { ReplicacheMutators } from "src/replicache"; 4 4 ··· 21 21 value: url, 22 22 }, 23 23 }); 24 - let data = await addLinkCard({ link: url }); 24 + let data = await addPageLink({ link: url }); 25 25 if (data.success) { 26 26 await rep?.mutate.assertFact({ 27 27 entity: entityID,
+1 -1
src/utils/elementId.ts
··· 3 3 text: `block/${id}/content`, 4 4 container: `block/${id}/container`, 5 5 }), 6 - card: (id: string) => ({ 6 + page: (id: string) => ({ 7 7 container: `card/${id}/container`, 8 8 }), 9 9 };
+4 -4
tailwind.config.js
··· 22 22 primary: "rgb(var(--primary))", 23 23 secondary: "color-mix(in oklab, rgb(var(--primary)), white 15%)", 24 24 tertiary: 25 - "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-card)) 55%)", 25 + "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 55%)", 26 26 border: 27 - "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-card)) 75%)", 27 + "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 75%)", 28 28 "border-light": 29 - "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-card)) 85%)", 29 + "color-mix(in oklab, rgb(var(--primary)), rgb(var(--bg-page)) 85%)", 30 30 31 31 white: "#FFFFFF", 32 32 ··· 36 36 "accent-contrast": "rgb(var(--accent-contrast))", 37 37 38 38 //BG COLORS (defined as css variables in global.css) 39 + "bg-leaflet": "rgb(var(--bg-leaflet))", 39 40 "bg-page": "rgb(var(--bg-page))", 40 - "bg-card": "rgb(var(--bg-card))", 41 41 42 42 // HIGHLIGHT COLORS 43 43 "highlight-1": "var(--highlight-1)",