Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
10 kB · 297 lines
TSX
at perf/paste
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298"use client";import { BlockProps, ListMarker, Block, BlockLayout } from "./Block";import { focusBlock } from "src/utils/focusBlock";
import { focusPage } from "src/utils/focusPage";import { useEntity, useReplicache } from "src/replicache";import { useUIState } from "src/useUIState";import { RenderedTextBlock } from "components/Blocks/TextBlock";import { usePageMetadata } from "src/hooks/queries/usePageMetadata";import { CSSProperties, useEffect, useRef, useState } from "react";import { useBlocks } from "src/hooks/queries/useBlocks";import { Canvas, CanvasBackground, CanvasContent } from "components/Canvas";import { CardThemeProvider } from "components/ThemeManager/ThemeProvider";import { useCardBorderHidden } from "components/Pages/useCardBorderHidden";
export function PageLinkBlock( props: BlockProps & { preview?: boolean; areYouSure?: boolean; setAreYouSure?: (value: boolean) => void; },) { let page = useEntity(props.entityID, "block/card"); let type = useEntity(page?.data.value || null, "page/type")?.data.value || "doc"; let { rep } = useReplicache();
let isSelected = useUIState((s) => s.selectedBlocks.find((b) => b.value === props.entityID), );
let isOpen = useUIState((s) => s.openPages).includes(page?.data.value || ""); if (!page) return <div>An error occured, there should be a page linked here!</div>;
return ( <CardThemeProvider entityID={page?.data.value}> <BlockLayout hasBackground="page" isSelected={!!isSelected} areYouSure={props.areYouSure} setAreYouSure={props.setAreYouSure} className={`cursor-pointer pageLinkBlockWrapper relative group/pageLinkBlock flex overflow-clip p-0! ${isOpen && "border-accent-contrast! outline-accent-contrast!"} `} > <div className="w-full h-full" onClick={(e) => { if (!page) return; if (e.isDefaultPrevented()) return; if (e.shiftKey) return; e.preventDefault(); e.stopPropagation(); useUIState.getState().openPage(props.parent, page.data.value); if (rep) focusPage(page.data.value, rep); }} > {type === "canvas" && page ? ( <CanvasLinkBlock entityID={page?.data.value} /> ) : ( <DocLinkBlock {...props} /> )} </div> </BlockLayout> </CardThemeProvider> );}function DocLinkBlock(props: BlockProps & { preview?: boolean }) { let { rep } = useReplicache(); let page = useEntity(props.entityID, "block/card"); let pageEntity = page ? page.data.value : props.entityID; let leafletMetadata = usePageMetadata(pageEntity);
return ( <div style={{ "--list-marker-width": "20px" } as CSSProperties} className={` w-full h-[104px] `} > <> <div className="pageLinkBlockContent w-full flex overflow-clip cursor-pointer h-full" onClick={(e) => { if (e.isDefaultPrevented()) return; if (e.shiftKey) return; e.preventDefault(); e.stopPropagation(); useUIState.getState().openPage(props.parent, pageEntity); if (rep) focusPage(pageEntity, rep); }} > <div className="my-2 ml-3 grow min-w-0 text-sm bg-transparent overflow-clip "> {leafletMetadata[0] && ( <div className={`pageBlockOne outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[0].type === "heading" ? "font-bold" : ""}`} > {leafletMetadata[0].listData && ( <ListMarker {...leafletMetadata[0]} className={ leafletMetadata[0].type === "heading" ? "pt-[12px]!" : "pt-[8px]!" } /> )} <RenderedTextBlock entityID={leafletMetadata[0].value} type="text" /> </div> )} {leafletMetadata[1] && ( <div className={`pageBlockLineTwo outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[1].type === "heading" ? "font-bold" : ""}`} > {leafletMetadata[1].listData && ( <ListMarker {...leafletMetadata[1]} className="pt-[8px]!" /> )} <RenderedTextBlock entityID={leafletMetadata[1].value} type="text" /> </div> )} {leafletMetadata[2] && ( <div className={`pageBlockLineThree outline-hidden resize-none align-top flex gap-2 ${leafletMetadata[2].type === "heading" ? "font-bold" : ""}`} > {leafletMetadata[2].listData && ( <ListMarker {...leafletMetadata[2]} className="pt-[8px]!" /> )} <RenderedTextBlock entityID={leafletMetadata[2].value} type="text" /> </div> )} </div> {!props.preview && <PagePreview entityID={pageEntity} />} </div> </> </div> );}
function PagePreview(props: { entityID: string }) { let blocks = useBlocks(props.entityID); let previewRef = useRef<HTMLDivElement | null>(null); let { rootEntity } = useReplicache();
let cardBorderHidden = useCardBorderHidden(props.entityID); let rootBackgroundImage = useEntity( rootEntity, "theme/card-background-image", ); let rootBackgroundRepeat = useEntity( rootEntity, "theme/card-background-image-repeat", ); let rootBackgroundOpacity = useEntity( rootEntity, "theme/card-background-image-opacity", );
let cardBackgroundImage = useEntity( props.entityID, "theme/card-background-image", );
let cardBackgroundImageRepeat = useEntity( props.entityID, "theme/card-background-image-repeat", );
let cardBackgroundImageOpacity = useEntity( props.entityID, "theme/card-background-image-opacity", );
let backgroundImage = cardBackgroundImage || rootBackgroundImage; let backgroundImageRepeat = cardBackgroundImage ? cardBackgroundImageRepeat?.data?.value : rootBackgroundRepeat?.data.value; let backgroundImageOpacity = cardBackgroundImage ? cardBackgroundImageOpacity?.data.value : rootBackgroundOpacity?.data.value || 1;
let pageWidth = `var(--page-width-unitless)`; return ( <div ref={previewRef} inert className={`pageLinkBlockPreview w-[120px] overflow-clip mx-3 mt-3 -mb-2 border rounded-md shrink-0 border-border-light flex flex-col gap-0.5 rotate-[4deg] origin-center ${cardBorderHidden ? "" : "bg-bg-page"}`} > <div className="absolute top-0 left-0 origin-top-left pointer-events-none " style={{ width: `calc(1px * ${pageWidth})`, height: `calc(100vh - 64px)`, transform: `scale(calc((120 / ${pageWidth} )))`, backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", }} > {!cardBorderHidden && ( <div className={`pageLinkBlockBackground absolute top-0 left-0 right-0 bottom-0 pointer-events-none `} style={{ backgroundImage: backgroundImage ? `url(${backgroundImage.data.src}), url(${backgroundImage.data.fallback})` : undefined, backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", backgroundPosition: "center", backgroundSize: !backgroundImageRepeat ? "cover" : backgroundImageRepeat, opacity: backgroundImage?.data.src ? backgroundImageOpacity : 1, }} /> )} {blocks.slice(0, 20).map((b, index, arr) => { return ( <BlockPreview pageType="doc" entityID={b.value} previousBlock={arr[index - 1] || null} nextBlock={arr[index + 1] || null} nextPosition={""} previewRef={previewRef} {...b} key={b.factID} /> ); })} </div> </div> );}
const CanvasLinkBlock = (props: { entityID: string; preview?: boolean }) => { let pageWidth = `var(--page-width-unitless)`; return ( <div style={{ contain: "size layout paint" }} className={`pageLinkBlockPreview shrink-0 h-[200px] w-full overflow-clip relative`} > <div className={`absolute top-0 left-0 origin-top-left pointer-events-none w-full`} style={{ width: `calc(1px * ${pageWidth})`, height: "calc(1150px * 2)", transform: `scale(calc(((${pageWidth} - 36) / 1272 )))`, }} > {props.preview ? ( <CanvasBackground entityID={props.entityID} /> ) : ( <CanvasContent entityID={props.entityID} preview /> )} </div> </div> );};
export function BlockPreview( b: BlockProps & { previewRef: React.RefObject<HTMLDivElement | null>; },) { let ref = useRef<HTMLDivElement | null>(null); let [isVisible, setIsVisible] = useState(true); useEffect(() => { if (!ref.current) return; let observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setIsVisible(true); } else { setIsVisible(false); } }); }, { threshold: 0.01, root: b.previewRef.current }, ); observer.observe(ref.current); return () => observer.disconnect(); }, [b.previewRef]); return <div ref={ref}>{isVisible && <Block {...b} preview />}</div>;}