···1111import type { Attribute } from "src/replicache/attributes";
1212import { Database } from "supabase/database.types";
1313import * as Y from "yjs";
1414-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
1414+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
1515import { pool } from "supabase/pool";
16161717let supabase = createServerClient<Database>(
+1-4
app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
···1010export async function getDocumentsByTag(
1111 tag: string,
1212): Promise<{ posts: Post[] }> {
1313- // Normalize tag to lowercase for case-insensitive search
1414- const normalizedTag = tag.toLowerCase();
1515-1613 // Query documents that have this tag
1714 const { data: documents, error } = await supabaseServerClient
1815 .from("documents")
···2219 document_mentions_in_bsky(count),
2320 documents_in_publications(publications(*))`,
2421 )
2525- .contains("data->tags", `["${normalizedTag}"]`)
2222+ .contains("data->tags", `["${tag}"]`)
2623 .order("indexed_at", { ascending: false })
2724 .limit(50);
2825
+1-1
app/[leaflet_id]/actions/PublishButton.tsx
···3535} from "src/hooks/queries/useBlocks";
3636import * as Y from "yjs";
3737import * as base64 from "base64-js";
3838-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
3838+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
3939import { BlueskyLogin } from "app/login/LoginForm";
4040import { moveLeafletToPublication } from "actions/publications/moveLeafletToPublication";
4141import { AddTiny } from "components/Icons/AddTiny";
+1-1
app/[leaflet_id]/page.tsx
···4455import type { Fact } from "src/replicache";
66import type { Attribute } from "src/replicache/attributes";
77-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
77+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
88import { Leaflet } from "./Leaflet";
99import { scanIndexLocal } from "src/replicache/utils";
1010import { getRSVPData } from "actions/getRSVPData";
···324324 return (
325325 // all this margin stuff is a highly unfortunate hack so that the border-l on blockquote is the height of just the text rather than the height of the block, which includes padding.
326326 <blockquote
327327- className={`blockquoteBlock py-0! mb-2! ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "-mt-2! pt-3!" : "mt-1!"}`}
327327+ className={`blockquote py-0! mb-2! ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "-mt-2! pt-3!" : "mt-1!"}`}
328328 {...blockProps}
329329 >
330330 <TextBlock
+2-2
app/lish/[did]/[publication]/icon/route.ts
···12121313export async function GET(
1414 request: NextRequest,
1515- props: { params: Promise<{ did: string; publication: string }> }
1515+ props: { params: Promise<{ did: string; publication: string }> },
1616) {
1717 console.log("are we getting here?");
1818 const params = await props.params;
···43434444 let identity = await idResolver.did.resolve(did);
4545 let service = identity?.service?.find((f) => f.id === "#atproto_pds");
4646- if (!service) return null;
4646+ if (!service) return redirect("/icon.png");
4747 let cid = (record.icon.ref as unknown as { $link: string })["$link"];
4848 const response = await fetch(
4949 `${service.serviceEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`,
+1-1
components/ActionBar/ActionButton.tsx
···33import { useContext, useEffect } from "react";
44import { SidebarContext } from "./Sidebar";
55import React, { forwardRef, type JSX } from "react";
66-import { PopoverOpenContext } from "components/Popover";
66+import { PopoverOpenContext } from "components/Popover/PopoverContext";
7788type ButtonProps = Omit<JSX.IntrinsicElements["button"], "content">;
99
+3-3
components/Blocks/BlockCommands.tsx
···22import { useUIState } from "src/useUIState";
3344import { generateKeyBetween } from "fractional-indexing";
55-import { focusPage } from "components/Pages";
55+import { focusPage } from "src/utils/focusPage";
66import { v7 } from "uuid";
77import { Replicache } from "replicache";
88import { useEditorStates } from "src/state/useEditorState";
99import { elementId } from "src/utils/elementId";
1010import { UndoManager } from "src/undoManager";
1111import { focusBlock } from "src/utils/focusBlock";
1212-import { usePollBlockUIState } from "./PollBlock";
1313-import { focusElement } from "components/Input";
1212+import { usePollBlockUIState } from "./PollBlock/pollBlockState";
1313+import { focusElement } from "src/utils/focusElement";
1414import { BlockBlueskySmall } from "components/Icons/BlockBlueskySmall";
1515import { BlockButtonSmall } from "components/Icons/BlockButtonSmall";
1616import { BlockCalendarSmall } from "components/Icons/BlockCalendarSmall";
+2-120
components/Blocks/DeleteBlock.tsx
···11-import {
22- Fact,
33- ReplicacheMutators,
44- useEntity,
55- useReplicache,
66-} from "src/replicache";
77-import { Replicache } from "replicache";
88-import { useUIState } from "src/useUIState";
99-import { scanIndex } from "src/replicache/utils";
1010-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
1111-import { focusBlock } from "src/utils/focusBlock";
11+import { Fact, useReplicache } from "src/replicache";
122import { ButtonPrimary } from "components/Buttons";
133import { CloseTiny } from "components/Icons/CloseTiny";
44+import { deleteBlock } from "src/utils/deleteBlock";
145156export const AreYouSure = (props: {
167 entityID: string[] | string;
···8273 );
8374};
84758585-export async function deleteBlock(
8686- entities: string[],
8787- rep: Replicache<ReplicacheMutators>,
8888-) {
8989- // get what pagess we need to close as a result of deleting this block
9090- let pagesToClose = [] as string[];
9191- for (let entity of entities) {
9292- let [type] = await rep.query((tx) =>
9393- scanIndex(tx).eav(entity, "block/type"),
9494- );
9595- if (type.data.value === "card") {
9696- let [childPages] = await rep?.query(
9797- (tx) => scanIndex(tx).eav(entity, "block/card") || [],
9898- );
9999- pagesToClose = [childPages?.data.value];
100100- }
101101- if (type.data.value === "mailbox") {
102102- let [archive] = await rep?.query(
103103- (tx) => scanIndex(tx).eav(entity, "mailbox/archive") || [],
104104- );
105105- let [draft] = await rep?.query(
106106- (tx) => scanIndex(tx).eav(entity, "mailbox/draft") || [],
107107- );
108108- pagesToClose = [archive?.data.value, draft?.data.value];
109109- }
110110- }
111111-112112- // the next and previous blocks in the block list
113113- // if the focused thing is a page and not a block, return
114114- let focusedBlock = useUIState.getState().focusedEntity;
115115- let parent =
116116- focusedBlock?.entityType === "page"
117117- ? focusedBlock.entityID
118118- : focusedBlock?.parent;
119119-120120- if (parent) {
121121- let parentType = await rep?.query((tx) =>
122122- scanIndex(tx).eav(parent, "page/type"),
123123- );
124124- if (parentType[0]?.data.value === "canvas") {
125125- useUIState
126126- .getState()
127127- .setFocusedBlock({ entityType: "page", entityID: parent });
128128- useUIState.getState().setSelectedBlocks([]);
129129- } else {
130130- let siblings =
131131- (await rep?.query((tx) => getBlocksWithType(tx, parent))) || [];
132132-133133- let selectedBlocks = useUIState.getState().selectedBlocks;
134134- let firstSelected = selectedBlocks[0];
135135- let lastSelected = selectedBlocks[entities.length - 1];
136136-137137- let prevBlock =
138138- siblings?.[
139139- siblings.findIndex((s) => s.value === firstSelected?.value) - 1
140140- ];
141141- let prevBlockType = await rep?.query((tx) =>
142142- scanIndex(tx).eav(prevBlock?.value, "block/type"),
143143- );
144144-145145- let nextBlock =
146146- siblings?.[
147147- siblings.findIndex((s) => s.value === lastSelected.value) + 1
148148- ];
149149- let nextBlockType = await rep?.query((tx) =>
150150- scanIndex(tx).eav(nextBlock?.value, "block/type"),
151151- );
152152-153153- if (prevBlock) {
154154- useUIState.getState().setSelectedBlock({
155155- value: prevBlock.value,
156156- parent: prevBlock.parent,
157157- });
158158-159159- focusBlock(
160160- {
161161- value: prevBlock.value,
162162- type: prevBlockType?.[0].data.value,
163163- parent: prevBlock.parent,
164164- },
165165- { type: "end" },
166166- );
167167- } else {
168168- useUIState.getState().setSelectedBlock({
169169- value: nextBlock.value,
170170- parent: nextBlock.parent,
171171- });
172172-173173- focusBlock(
174174- {
175175- value: nextBlock.value,
176176- type: nextBlockType?.[0]?.data.value,
177177- parent: nextBlock.parent,
178178- },
179179- { type: "start" },
180180- );
181181- }
182182- }
183183- }
184184-185185- pagesToClose.forEach((page) => page && useUIState.getState().closePage(page));
186186- await Promise.all(
187187- entities.map((entity) =>
188188- rep?.mutate.removeBlock({
189189- blockEntity: entity,
190190- }),
191191- ),
192192- );
193193-}
+2-1
components/Blocks/ExternalLinkBlock.tsx
···88import { v7 } from "uuid";
99import { useSmoker } from "components/Toast";
1010import { Separator } from "components/Layout";
1111-import { focusElement, Input } from "components/Input";
1111+import { Input } from "components/Input";
1212+import { focusElement } from "src/utils/focusElement";
1213import { isUrl } from "src/utils/isURL";
1314import { elementId } from "src/utils/elementId";
1415import { focusBlock } from "src/utils/focusBlock";
+1-1
components/Blocks/MailboxBlock.tsx
···99import { useEntitySetContext } from "components/EntitySetProvider";
1010import { subscribeToMailboxWithEmail } from "actions/subscriptions/subscribeToMailboxWithEmail";
1111import { confirmEmailSubscription } from "actions/subscriptions/confirmEmailSubscription";
1212-import { focusPage } from "components/Pages";
1212+import { focusPage } from "src/utils/focusPage";
1313import { v7 } from "uuid";
1414import { sendPostToSubscribers } from "actions/subscriptions/sendPostToSubscribers";
1515import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+1-1
components/Blocks/PageLinkBlock.tsx
···22import { BlockProps, BaseBlock, ListMarker, Block } from "./Block";
33import { focusBlock } from "src/utils/focusBlock";
4455-import { focusPage } from "components/Pages";
55+import { focusPage } from "src/utils/focusPage";
66import { useEntity, useReplicache } from "src/replicache";
77import { useUIState } from "src/useUIState";
88import { RenderedTextBlock } from "components/Blocks/TextBlock";
···22import { useMemo, useState } from "react";
33import { parseColor } from "react-aria-components";
44import { useEntity } from "src/replicache";
55-import { getColorContrast } from "./ThemeProvider";
55+import { getColorContrast } from "./themeUtils";
66import { useColorAttribute, colorToString } from "./useColorAttribute";
77import { BaseThemeProvider } from "./ThemeProvider";
88import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
+1-40
components/ThemeManager/ThemeProvider.tsx
···55 CSSProperties,
66 useContext,
77 useEffect,
88- useMemo,
99- useState,
108} from "react";
119import {
1210 colorToString,
···1412 useColorAttributeNullable,
1513} from "./useColorAttribute";
1614import { Color as AriaColor, parseColor } from "react-aria-components";
1717-import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
18151916import { useEntity } from "src/replicache";
2017import { useLeafletPublicationData } from "components/PageSWRDataProvider";
···2320 PublicationThemeProvider,
2421} from "./PublicationThemeProvider";
2522import { PubLeafletPublication } from "lexicons/api";
2626-2727-type CSSVariables = {
2828- "--bg-leaflet": string;
2929- "--bg-page": string;
3030- "--primary": string;
3131- "--accent-1": string;
3232- "--accent-2": string;
3333- "--accent-contrast": string;
3434- "--highlight-1": string;
3535- "--highlight-2": string;
3636- "--highlight-3": string;
3737-};
3838-3939-// define the color defaults for everything
4040-export const ThemeDefaults = {
4141- "theme/page-background": "#FDFCFA",
4242- "theme/card-background": "#FFFFFF",
4343- "theme/primary": "#272727",
4444- "theme/highlight-1": "#FFFFFF",
4545- "theme/highlight-2": "#EDD280",
4646- "theme/highlight-3": "#FFCDC3",
4747-4848- //everywhere else, accent-background = accent-1 and accent-text = accent-2.
4949- // we just need to create a migration pipeline before we can change this
5050- "theme/accent-text": "#FFFFFF",
5151- "theme/accent-background": "#0000FF",
5252- "theme/accent-contrast": "#0000FF",
5353-};
2323+import { getColorContrast } from "./themeUtils";
54245525// define a function to set an Aria Color to a CSS Variable in RGB
5626function setCSSVariableToColor(
···368338 );
369339};
370340371371-// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast
372372-export function getColorContrast(color1: string, color2: string) {
373373- ColorSpace.register(sRGB);
374374-375375- let parsedColor1 = parse(`rgb(${color1})`);
376376- let parsedColor2 = parse(`rgb(${color2})`);
377377-378378- return contrastLstar(parsedColor1, parsedColor2);
379379-}
+27
components/ThemeManager/themeUtils.ts
···11+import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
22+33+// define the color defaults for everything
44+export const ThemeDefaults = {
55+ "theme/page-background": "#FDFCFA",
66+ "theme/card-background": "#FFFFFF",
77+ "theme/primary": "#272727",
88+ "theme/highlight-1": "#FFFFFF",
99+ "theme/highlight-2": "#EDD280",
1010+ "theme/highlight-3": "#FFCDC3",
1111+1212+ //everywhere else, accent-background = accent-1 and accent-text = accent-2.
1313+ // we just need to create a migration pipeline before we can change this
1414+ "theme/accent-text": "#FFFFFF",
1515+ "theme/accent-background": "#0000FF",
1616+ "theme/accent-contrast": "#0000FF",
1717+};
1818+1919+// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast
2020+export function getColorContrast(color1: string, color2: string) {
2121+ ColorSpace.register(sRGB);
2222+2323+ let parsedColor1 = parse(`rgb(${color1})`);
2424+ let parsedColor2 = parse(`rgb(${color2})`);
2525+2626+ return contrastLstar(parsedColor1, parsedColor2);
2727+}
+1-1
components/ThemeManager/useColorAttribute.ts
···22import { Color, parseColor } from "react-aria-components";
33import { useEntity, useReplicache } from "src/replicache";
44import { FilterAttributes } from "src/replicache/attributes";
55-import { ThemeDefaults } from "./ThemeProvider";
55+import { ThemeDefaults } from "./themeUtils";
6677export function useColorAttribute(
88 entity: string | null,
+5-14
components/Toolbar/BlockToolbar.tsx
···22import { ToolbarButton } from ".";
33import { Separator, ShortcutKey } from "components/Layout";
44import { metaKey } from "src/utils/metaKey";
55-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
65import { useUIState } from "src/useUIState";
76import { LockBlockButton } from "./LockBlockButton";
87import { TextAlignmentButton } from "./TextAlignmentToolbar";
98import { ImageFullBleedButton, ImageAltTextButton } from "./ImageToolbar";
109import { DeleteSmall } from "components/Icons/DeleteSmall";
1010+import { getSortedSelection } from "components/SelectionManager/selectionState";
11111212export const BlockToolbar = (props: {
1313 setToolbarState: (
···66666767const MoveBlockButtons = () => {
6868 let { rep } = useReplicache();
6969- const getSortedSelection = async () => {
7070- let selectedBlocks = useUIState.getState().selectedBlocks;
7171- let siblings =
7272- (await rep?.query((tx) =>
7373- getBlocksWithType(tx, selectedBlocks[0].parent),
7474- )) || [];
7575- let sortedBlocks = siblings.filter((s) =>
7676- selectedBlocks.find((sb) => sb.value === s.value),
7777- );
7878- return [sortedBlocks, siblings];
7979- };
8069 return (
8170 <>
8271 <ToolbarButton
8372 hiddenOnCanvas
8473 onClick={async () => {
8585- let [sortedBlocks, siblings] = await getSortedSelection();
7474+ if (!rep) return;
7575+ let [sortedBlocks, siblings] = await getSortedSelection(rep);
8676 if (sortedBlocks.length > 1) return;
8777 let block = sortedBlocks[0];
8878 let previousBlock =
···139129 <ToolbarButton
140130 hiddenOnCanvas
141131 onClick={async () => {
142142- let [sortedBlocks, siblings] = await getSortedSelection();
132132+ if (!rep) return;
133133+ let [sortedBlocks, siblings] = await getSortedSelection(rep);
143134 if (sortedBlocks.length > 1) return;
144135 let block = sortedBlocks[0];
145136 let nextBlock = siblings
+1-1
components/Toolbar/MultiSelectToolbar.tsx
···88import { LockBlockButton } from "./LockBlockButton";
99import { Props } from "components/Icons/Props";
1010import { TextAlignmentButton } from "./TextAlignmentToolbar";
1111-import { getSortedSelection } from "components/SelectionManager";
1111+import { getSortedSelection } from "components/SelectionManager/selectionState";
12121313export const MultiselectToolbar = (props: {
1414 setToolbarState: (
+2-1
components/Toolbar/index.tsx
···1313import { TextToolbar } from "./TextToolbar";
1414import { BlockToolbar } from "./BlockToolbar";
1515import { MultiselectToolbar } from "./MultiSelectToolbar";
1616-import { AreYouSure, deleteBlock } from "components/Blocks/DeleteBlock";
1616+import { AreYouSure } from "components/Blocks/DeleteBlock";
1717+import { deleteBlock } from "src/utils/deleteBlock";
1718import { TooltipButton } from "components/Buttons";
1819import { TextAlignmentToolbar } from "./TextAlignmentToolbar";
1920import { useIsMobile } from "src/hooks/isMobile";
+1-1
components/utils/UpdateLeafletTitle.tsx
···88import { useEntity, useReplicache } from "src/replicache";
99import * as Y from "yjs";
1010import * as base64 from "base64-js";
1111-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
1111+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
1212import { useParams, useRouter, useSearchParams } from "next/navigation";
1313import { focusBlock } from "src/utils/focusBlock";
1414import { useIsMobile } from "src/hooks/isMobile";
+116
src/utils/deleteBlock.ts
···11+import { Replicache } from "replicache";
22+import { ReplicacheMutators } from "src/replicache";
33+import { useUIState } from "src/useUIState";
44+import { scanIndex } from "src/replicache/utils";
55+import { getBlocksWithType } from "src/hooks/queries/useBlocks";
66+import { focusBlock } from "src/utils/focusBlock";
77+88+export async function deleteBlock(
99+ entities: string[],
1010+ rep: Replicache<ReplicacheMutators>,
1111+) {
1212+ // get what pagess we need to close as a result of deleting this block
1313+ let pagesToClose = [] as string[];
1414+ for (let entity of entities) {
1515+ let [type] = await rep.query((tx) =>
1616+ scanIndex(tx).eav(entity, "block/type"),
1717+ );
1818+ if (type.data.value === "card") {
1919+ let [childPages] = await rep?.query(
2020+ (tx) => scanIndex(tx).eav(entity, "block/card") || [],
2121+ );
2222+ pagesToClose = [childPages?.data.value];
2323+ }
2424+ if (type.data.value === "mailbox") {
2525+ let [archive] = await rep?.query(
2626+ (tx) => scanIndex(tx).eav(entity, "mailbox/archive") || [],
2727+ );
2828+ let [draft] = await rep?.query(
2929+ (tx) => scanIndex(tx).eav(entity, "mailbox/draft") || [],
3030+ );
3131+ pagesToClose = [archive?.data.value, draft?.data.value];
3232+ }
3333+ }
3434+3535+ // the next and previous blocks in the block list
3636+ // if the focused thing is a page and not a block, return
3737+ let focusedBlock = useUIState.getState().focusedEntity;
3838+ let parent =
3939+ focusedBlock?.entityType === "page"
4040+ ? focusedBlock.entityID
4141+ : focusedBlock?.parent;
4242+4343+ if (parent) {
4444+ let parentType = await rep?.query((tx) =>
4545+ scanIndex(tx).eav(parent, "page/type"),
4646+ );
4747+ if (parentType[0]?.data.value === "canvas") {
4848+ useUIState
4949+ .getState()
5050+ .setFocusedBlock({ entityType: "page", entityID: parent });
5151+ useUIState.getState().setSelectedBlocks([]);
5252+ } else {
5353+ let siblings =
5454+ (await rep?.query((tx) => getBlocksWithType(tx, parent))) || [];
5555+5656+ let selectedBlocks = useUIState.getState().selectedBlocks;
5757+ let firstSelected = selectedBlocks[0];
5858+ let lastSelected = selectedBlocks[entities.length - 1];
5959+6060+ let prevBlock =
6161+ siblings?.[
6262+ siblings.findIndex((s) => s.value === firstSelected?.value) - 1
6363+ ];
6464+ let prevBlockType = await rep?.query((tx) =>
6565+ scanIndex(tx).eav(prevBlock?.value, "block/type"),
6666+ );
6767+6868+ let nextBlock =
6969+ siblings?.[
7070+ siblings.findIndex((s) => s.value === lastSelected.value) + 1
7171+ ];
7272+ let nextBlockType = await rep?.query((tx) =>
7373+ scanIndex(tx).eav(nextBlock?.value, "block/type"),
7474+ );
7575+7676+ if (prevBlock) {
7777+ useUIState.getState().setSelectedBlock({
7878+ value: prevBlock.value,
7979+ parent: prevBlock.parent,
8080+ });
8181+8282+ focusBlock(
8383+ {
8484+ value: prevBlock.value,
8585+ type: prevBlockType?.[0].data.value,
8686+ parent: prevBlock.parent,
8787+ },
8888+ { type: "end" },
8989+ );
9090+ } else {
9191+ useUIState.getState().setSelectedBlock({
9292+ value: nextBlock.value,
9393+ parent: nextBlock.parent,
9494+ });
9595+9696+ focusBlock(
9797+ {
9898+ value: nextBlock.value,
9999+ type: nextBlockType?.[0]?.data.value,
100100+ parent: nextBlock.parent,
101101+ },
102102+ { type: "start" },
103103+ );
104104+ }
105105+ }
106106+ }
107107+108108+ pagesToClose.forEach((page) => page && useUIState.getState().closePage(page));
109109+ await Promise.all(
110110+ entities.map((entity) =>
111111+ rep?.mutate.removeBlock({
112112+ blockEntity: entity,
113113+ }),
114114+ ),
115115+ );
116116+}