···11import type { Attribute } from "src/replicache/attributes";
12import { Database } from "supabase/database.types";
13import * as Y from "yjs";
14-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
15import { pool } from "supabase/pool";
1617let supabase = createServerClient<Database>(
···11import type { Attribute } from "src/replicache/attributes";
12import { Database } from "supabase/database.types";
13import * as Y from "yjs";
14+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
15import { pool } from "supabase/pool";
1617let supabase = createServerClient<Database>(
+1-4
app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
···10export async function getDocumentsByTag(
11 tag: string,
12): Promise<{ posts: Post[] }> {
13- // Normalize tag to lowercase for case-insensitive search
14- const normalizedTag = tag.toLowerCase();
15-16 // Query documents that have this tag
17 const { data: documents, error } = await supabaseServerClient
18 .from("documents")
···22 document_mentions_in_bsky(count),
23 documents_in_publications(publications(*))`,
24 )
25- .contains("data->tags", `["${normalizedTag}"]`)
26 .order("indexed_at", { ascending: false })
27 .limit(50);
28
···35} from "src/hooks/queries/useBlocks";
36import * as Y from "yjs";
37import * as base64 from "base64-js";
38-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
39import { BlueskyLogin } from "app/login/LoginForm";
40import { moveLeafletToPublication } from "actions/publications/moveLeafletToPublication";
41import { AddTiny } from "components/Icons/AddTiny";
···35} from "src/hooks/queries/useBlocks";
36import * as Y from "yjs";
37import * as base64 from "base64-js";
38+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
39import { BlueskyLogin } from "app/login/LoginForm";
40import { moveLeafletToPublication } from "actions/publications/moveLeafletToPublication";
41import { AddTiny } from "components/Icons/AddTiny";
+1-1
app/[leaflet_id]/page.tsx
···45import type { Fact } from "src/replicache";
6import type { Attribute } from "src/replicache/attributes";
7-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
8import { Leaflet } from "./Leaflet";
9import { scanIndexLocal } from "src/replicache/utils";
10import { getRSVPData } from "actions/getRSVPData";
···45import type { Fact } from "src/replicache";
6import type { Attribute } from "src/replicache/attributes";
7+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
8import { Leaflet } from "./Leaflet";
9import { scanIndexLocal } from "src/replicache/utils";
10import { getRSVPData } from "actions/getRSVPData";
···324 return (
325 // 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.
326 <blockquote
327- className={`blockquoteBlock py-0! mb-2! ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "-mt-2! pt-3!" : "mt-1!"}`}
328 {...blockProps}
329 >
330 <TextBlock
···324 return (
325 // 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.
326 <blockquote
327+ className={`blockquote py-0! mb-2! ${className} ${PubLeafletBlocksBlockquote.isMain(previousBlock?.block) ? "-mt-2! pt-3!" : "mt-1!"}`}
328 {...blockProps}
329 >
330 <TextBlock
+2-2
app/lish/[did]/[publication]/icon/route.ts
···1213export async function GET(
14 request: NextRequest,
15- props: { params: Promise<{ did: string; publication: string }> }
16) {
17 console.log("are we getting here?");
18 const params = await props.params;
···4344 let identity = await idResolver.did.resolve(did);
45 let service = identity?.service?.find((f) => f.id === "#atproto_pds");
46- if (!service) return null;
47 let cid = (record.icon.ref as unknown as { $link: string })["$link"];
48 const response = await fetch(
49 `${service.serviceEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`,
···1213export async function GET(
14 request: NextRequest,
15+ props: { params: Promise<{ did: string; publication: string }> },
16) {
17 console.log("are we getting here?");
18 const params = await props.params;
···4344 let identity = await idResolver.did.resolve(did);
45 let service = identity?.service?.find((f) => f.id === "#atproto_pds");
46+ if (!service) return redirect("/icon.png");
47 let cid = (record.icon.ref as unknown as { $link: string })["$link"];
48 const response = await fetch(
49 `${service.serviceEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`,
+1-1
components/ActionBar/ActionButton.tsx
···3import { useContext, useEffect } from "react";
4import { SidebarContext } from "./Sidebar";
5import React, { forwardRef, type JSX } from "react";
6-import { PopoverOpenContext } from "components/Popover";
78type ButtonProps = Omit<JSX.IntrinsicElements["button"], "content">;
9
···3import { useContext, useEffect } from "react";
4import { SidebarContext } from "./Sidebar";
5import React, { forwardRef, type JSX } from "react";
6+import { PopoverOpenContext } from "components/Popover/PopoverContext";
78type ButtonProps = Omit<JSX.IntrinsicElements["button"], "content">;
9
+3-3
components/Blocks/BlockCommands.tsx
···2import { useUIState } from "src/useUIState";
34import { generateKeyBetween } from "fractional-indexing";
5-import { focusPage } from "components/Pages";
6import { v7 } from "uuid";
7import { Replicache } from "replicache";
8import { useEditorStates } from "src/state/useEditorState";
9import { elementId } from "src/utils/elementId";
10import { UndoManager } from "src/undoManager";
11import { focusBlock } from "src/utils/focusBlock";
12-import { usePollBlockUIState } from "./PollBlock";
13-import { focusElement } from "components/Input";
14import { BlockBlueskySmall } from "components/Icons/BlockBlueskySmall";
15import { BlockButtonSmall } from "components/Icons/BlockButtonSmall";
16import { BlockCalendarSmall } from "components/Icons/BlockCalendarSmall";
···2import { useUIState } from "src/useUIState";
34import { generateKeyBetween } from "fractional-indexing";
5+import { focusPage } from "src/utils/focusPage";
6import { v7 } from "uuid";
7import { Replicache } from "replicache";
8import { useEditorStates } from "src/state/useEditorState";
9import { elementId } from "src/utils/elementId";
10import { UndoManager } from "src/undoManager";
11import { focusBlock } from "src/utils/focusBlock";
12+import { usePollBlockUIState } from "./PollBlock/pollBlockState";
13+import { focusElement } from "src/utils/focusElement";
14import { BlockBlueskySmall } from "components/Icons/BlockBlueskySmall";
15import { BlockButtonSmall } from "components/Icons/BlockButtonSmall";
16import { BlockCalendarSmall } from "components/Icons/BlockCalendarSmall";
+2-120
components/Blocks/DeleteBlock.tsx
···1-import {
2- Fact,
3- ReplicacheMutators,
4- useEntity,
5- useReplicache,
6-} from "src/replicache";
7-import { Replicache } from "replicache";
8-import { useUIState } from "src/useUIState";
9-import { scanIndex } from "src/replicache/utils";
10-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
11-import { focusBlock } from "src/utils/focusBlock";
12import { ButtonPrimary } from "components/Buttons";
13import { CloseTiny } from "components/Icons/CloseTiny";
01415export const AreYouSure = (props: {
16 entityID: string[] | string;
···82 );
83};
8485-export async function deleteBlock(
86- entities: string[],
87- rep: Replicache<ReplicacheMutators>,
88-) {
89- // get what pagess we need to close as a result of deleting this block
90- let pagesToClose = [] as string[];
91- for (let entity of entities) {
92- let [type] = await rep.query((tx) =>
93- scanIndex(tx).eav(entity, "block/type"),
94- );
95- if (type.data.value === "card") {
96- let [childPages] = await rep?.query(
97- (tx) => scanIndex(tx).eav(entity, "block/card") || [],
98- );
99- pagesToClose = [childPages?.data.value];
100- }
101- if (type.data.value === "mailbox") {
102- let [archive] = await rep?.query(
103- (tx) => scanIndex(tx).eav(entity, "mailbox/archive") || [],
104- );
105- let [draft] = await rep?.query(
106- (tx) => scanIndex(tx).eav(entity, "mailbox/draft") || [],
107- );
108- pagesToClose = [archive?.data.value, draft?.data.value];
109- }
110- }
111-112- // the next and previous blocks in the block list
113- // if the focused thing is a page and not a block, return
114- let focusedBlock = useUIState.getState().focusedEntity;
115- let parent =
116- focusedBlock?.entityType === "page"
117- ? focusedBlock.entityID
118- : focusedBlock?.parent;
119-120- if (parent) {
121- let parentType = await rep?.query((tx) =>
122- scanIndex(tx).eav(parent, "page/type"),
123- );
124- if (parentType[0]?.data.value === "canvas") {
125- useUIState
126- .getState()
127- .setFocusedBlock({ entityType: "page", entityID: parent });
128- useUIState.getState().setSelectedBlocks([]);
129- } else {
130- let siblings =
131- (await rep?.query((tx) => getBlocksWithType(tx, parent))) || [];
132-133- let selectedBlocks = useUIState.getState().selectedBlocks;
134- let firstSelected = selectedBlocks[0];
135- let lastSelected = selectedBlocks[entities.length - 1];
136-137- let prevBlock =
138- siblings?.[
139- siblings.findIndex((s) => s.value === firstSelected?.value) - 1
140- ];
141- let prevBlockType = await rep?.query((tx) =>
142- scanIndex(tx).eav(prevBlock?.value, "block/type"),
143- );
144-145- let nextBlock =
146- siblings?.[
147- siblings.findIndex((s) => s.value === lastSelected.value) + 1
148- ];
149- let nextBlockType = await rep?.query((tx) =>
150- scanIndex(tx).eav(nextBlock?.value, "block/type"),
151- );
152-153- if (prevBlock) {
154- useUIState.getState().setSelectedBlock({
155- value: prevBlock.value,
156- parent: prevBlock.parent,
157- });
158-159- focusBlock(
160- {
161- value: prevBlock.value,
162- type: prevBlockType?.[0].data.value,
163- parent: prevBlock.parent,
164- },
165- { type: "end" },
166- );
167- } else {
168- useUIState.getState().setSelectedBlock({
169- value: nextBlock.value,
170- parent: nextBlock.parent,
171- });
172-173- focusBlock(
174- {
175- value: nextBlock.value,
176- type: nextBlockType?.[0]?.data.value,
177- parent: nextBlock.parent,
178- },
179- { type: "start" },
180- );
181- }
182- }
183- }
184-185- pagesToClose.forEach((page) => page && useUIState.getState().closePage(page));
186- await Promise.all(
187- entities.map((entity) =>
188- rep?.mutate.removeBlock({
189- blockEntity: entity,
190- }),
191- ),
192- );
193-}
···1+import { Fact, useReplicache } from "src/replicache";
00000000002import { ButtonPrimary } from "components/Buttons";
3import { CloseTiny } from "components/Icons/CloseTiny";
4+import { deleteBlock } from "src/utils/deleteBlock";
56export const AreYouSure = (props: {
7 entityID: string[] | string;
···73 );
74};
750000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+2-1
components/Blocks/ExternalLinkBlock.tsx
···8import { v7 } from "uuid";
9import { useSmoker } from "components/Toast";
10import { Separator } from "components/Layout";
11-import { focusElement, Input } from "components/Input";
012import { isUrl } from "src/utils/isURL";
13import { elementId } from "src/utils/elementId";
14import { focusBlock } from "src/utils/focusBlock";
···8import { v7 } from "uuid";
9import { useSmoker } from "components/Toast";
10import { Separator } from "components/Layout";
11+import { Input } from "components/Input";
12+import { focusElement } from "src/utils/focusElement";
13import { isUrl } from "src/utils/isURL";
14import { elementId } from "src/utils/elementId";
15import { focusBlock } from "src/utils/focusBlock";
+1-1
components/Blocks/MailboxBlock.tsx
···9import { useEntitySetContext } from "components/EntitySetProvider";
10import { subscribeToMailboxWithEmail } from "actions/subscriptions/subscribeToMailboxWithEmail";
11import { confirmEmailSubscription } from "actions/subscriptions/confirmEmailSubscription";
12-import { focusPage } from "components/Pages";
13import { v7 } from "uuid";
14import { sendPostToSubscribers } from "actions/subscriptions/sendPostToSubscribers";
15import { getBlocksWithType } from "src/hooks/queries/useBlocks";
···9import { useEntitySetContext } from "components/EntitySetProvider";
10import { subscribeToMailboxWithEmail } from "actions/subscriptions/subscribeToMailboxWithEmail";
11import { confirmEmailSubscription } from "actions/subscriptions/confirmEmailSubscription";
12+import { focusPage } from "src/utils/focusPage";
13import { v7 } from "uuid";
14import { sendPostToSubscribers } from "actions/subscriptions/sendPostToSubscribers";
15import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+1-1
components/Blocks/PageLinkBlock.tsx
···2import { BlockProps, BaseBlock, ListMarker, Block } from "./Block";
3import { focusBlock } from "src/utils/focusBlock";
45-import { focusPage } from "components/Pages";
6import { useEntity, useReplicache } from "src/replicache";
7import { useUIState } from "src/useUIState";
8import { RenderedTextBlock } from "components/Blocks/TextBlock";
···2import { BlockProps, BaseBlock, ListMarker, Block } from "./Block";
3import { focusBlock } from "src/utils/focusBlock";
45+import { focusPage } from "src/utils/focusPage";
6import { useEntity, useReplicache } from "src/replicache";
7import { useUIState } from "src/useUIState";
8import { RenderedTextBlock } from "components/Blocks/TextBlock";
···2import { useMemo, useState } from "react";
3import { parseColor } from "react-aria-components";
4import { useEntity } from "src/replicache";
5-import { getColorContrast } from "./ThemeProvider";
6import { useColorAttribute, colorToString } from "./useColorAttribute";
7import { BaseThemeProvider } from "./ThemeProvider";
8import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
···2import { useMemo, useState } from "react";
3import { parseColor } from "react-aria-components";
4import { useEntity } from "src/replicache";
5+import { getColorContrast } from "./themeUtils";
6import { useColorAttribute, colorToString } from "./useColorAttribute";
7import { BaseThemeProvider } from "./ThemeProvider";
8import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
+1-40
components/ThemeManager/ThemeProvider.tsx
···5 CSSProperties,
6 useContext,
7 useEffect,
8- useMemo,
9- useState,
10} from "react";
11import {
12 colorToString,
···14 useColorAttributeNullable,
15} from "./useColorAttribute";
16import { Color as AriaColor, parseColor } from "react-aria-components";
17-import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
1819import { useEntity } from "src/replicache";
20import { useLeafletPublicationData } from "components/PageSWRDataProvider";
···23 PublicationThemeProvider,
24} from "./PublicationThemeProvider";
25import { PubLeafletPublication } from "lexicons/api";
26-27-type CSSVariables = {
28- "--bg-leaflet": string;
29- "--bg-page": string;
30- "--primary": string;
31- "--accent-1": string;
32- "--accent-2": string;
33- "--accent-contrast": string;
34- "--highlight-1": string;
35- "--highlight-2": string;
36- "--highlight-3": string;
37-};
38-39-// define the color defaults for everything
40-export const ThemeDefaults = {
41- "theme/page-background": "#FDFCFA",
42- "theme/card-background": "#FFFFFF",
43- "theme/primary": "#272727",
44- "theme/highlight-1": "#FFFFFF",
45- "theme/highlight-2": "#EDD280",
46- "theme/highlight-3": "#FFCDC3",
47-48- //everywhere else, accent-background = accent-1 and accent-text = accent-2.
49- // we just need to create a migration pipeline before we can change this
50- "theme/accent-text": "#FFFFFF",
51- "theme/accent-background": "#0000FF",
52- "theme/accent-contrast": "#0000FF",
53-};
5455// define a function to set an Aria Color to a CSS Variable in RGB
56function setCSSVariableToColor(
···368 );
369};
370371-// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast
372-export function getColorContrast(color1: string, color2: string) {
373- ColorSpace.register(sRGB);
374-375- let parsedColor1 = parse(`rgb(${color1})`);
376- let parsedColor2 = parse(`rgb(${color2})`);
377-378- return contrastLstar(parsedColor1, parsedColor2);
379-}
···5 CSSProperties,
6 useContext,
7 useEffect,
008} from "react";
9import {
10 colorToString,
···12 useColorAttributeNullable,
13} from "./useColorAttribute";
14import { Color as AriaColor, parseColor } from "react-aria-components";
01516import { useEntity } from "src/replicache";
17import { useLeafletPublicationData } from "components/PageSWRDataProvider";
···20 PublicationThemeProvider,
21} from "./PublicationThemeProvider";
22import { PubLeafletPublication } from "lexicons/api";
23+import { getColorContrast } from "./themeUtils";
0000000000000000000000000002425// define a function to set an Aria Color to a CSS Variable in RGB
26function setCSSVariableToColor(
···338 );
339};
340000000000
+27
components/ThemeManager/themeUtils.ts
···000000000000000000000000000
···1+import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
2+3+// define the color defaults for everything
4+export const ThemeDefaults = {
5+ "theme/page-background": "#FDFCFA",
6+ "theme/card-background": "#FFFFFF",
7+ "theme/primary": "#272727",
8+ "theme/highlight-1": "#FFFFFF",
9+ "theme/highlight-2": "#EDD280",
10+ "theme/highlight-3": "#FFCDC3",
11+12+ //everywhere else, accent-background = accent-1 and accent-text = accent-2.
13+ // we just need to create a migration pipeline before we can change this
14+ "theme/accent-text": "#FFFFFF",
15+ "theme/accent-background": "#0000FF",
16+ "theme/accent-contrast": "#0000FF",
17+};
18+19+// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast
20+export function getColorContrast(color1: string, color2: string) {
21+ ColorSpace.register(sRGB);
22+23+ let parsedColor1 = parse(`rgb(${color1})`);
24+ let parsedColor2 = parse(`rgb(${color2})`);
25+26+ return contrastLstar(parsedColor1, parsedColor2);
27+}
+1-1
components/ThemeManager/useColorAttribute.ts
···2import { Color, parseColor } from "react-aria-components";
3import { useEntity, useReplicache } from "src/replicache";
4import { FilterAttributes } from "src/replicache/attributes";
5-import { ThemeDefaults } from "./ThemeProvider";
67export function useColorAttribute(
8 entity: string | null,
···2import { Color, parseColor } from "react-aria-components";
3import { useEntity, useReplicache } from "src/replicache";
4import { FilterAttributes } from "src/replicache/attributes";
5+import { ThemeDefaults } from "./themeUtils";
67export function useColorAttribute(
8 entity: string | null,
+5-14
components/Toolbar/BlockToolbar.tsx
···2import { ToolbarButton } from ".";
3import { Separator, ShortcutKey } from "components/Layout";
4import { metaKey } from "src/utils/metaKey";
5-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
6import { useUIState } from "src/useUIState";
7import { LockBlockButton } from "./LockBlockButton";
8import { TextAlignmentButton } from "./TextAlignmentToolbar";
9import { ImageFullBleedButton, ImageAltTextButton } from "./ImageToolbar";
10import { DeleteSmall } from "components/Icons/DeleteSmall";
01112export const BlockToolbar = (props: {
13 setToolbarState: (
···6667const MoveBlockButtons = () => {
68 let { rep } = useReplicache();
69- const getSortedSelection = async () => {
70- let selectedBlocks = useUIState.getState().selectedBlocks;
71- let siblings =
72- (await rep?.query((tx) =>
73- getBlocksWithType(tx, selectedBlocks[0].parent),
74- )) || [];
75- let sortedBlocks = siblings.filter((s) =>
76- selectedBlocks.find((sb) => sb.value === s.value),
77- );
78- return [sortedBlocks, siblings];
79- };
80 return (
81 <>
82 <ToolbarButton
83 hiddenOnCanvas
84 onClick={async () => {
85- let [sortedBlocks, siblings] = await getSortedSelection();
086 if (sortedBlocks.length > 1) return;
87 let block = sortedBlocks[0];
88 let previousBlock =
···139 <ToolbarButton
140 hiddenOnCanvas
141 onClick={async () => {
142- let [sortedBlocks, siblings] = await getSortedSelection();
0143 if (sortedBlocks.length > 1) return;
144 let block = sortedBlocks[0];
145 let nextBlock = siblings
···2import { ToolbarButton } from ".";
3import { Separator, ShortcutKey } from "components/Layout";
4import { metaKey } from "src/utils/metaKey";
05import { useUIState } from "src/useUIState";
6import { LockBlockButton } from "./LockBlockButton";
7import { TextAlignmentButton } from "./TextAlignmentToolbar";
8import { ImageFullBleedButton, ImageAltTextButton } from "./ImageToolbar";
9import { DeleteSmall } from "components/Icons/DeleteSmall";
10+import { getSortedSelection } from "components/SelectionManager/selectionState";
1112export const BlockToolbar = (props: {
13 setToolbarState: (
···6667const MoveBlockButtons = () => {
68 let { rep } = useReplicache();
0000000000069 return (
70 <>
71 <ToolbarButton
72 hiddenOnCanvas
73 onClick={async () => {
74+ if (!rep) return;
75+ let [sortedBlocks, siblings] = await getSortedSelection(rep);
76 if (sortedBlocks.length > 1) return;
77 let block = sortedBlocks[0];
78 let previousBlock =
···129 <ToolbarButton
130 hiddenOnCanvas
131 onClick={async () => {
132+ if (!rep) return;
133+ let [sortedBlocks, siblings] = await getSortedSelection(rep);
134 if (sortedBlocks.length > 1) return;
135 let block = sortedBlocks[0];
136 let nextBlock = siblings
+1-1
components/Toolbar/MultiSelectToolbar.tsx
···8import { LockBlockButton } from "./LockBlockButton";
9import { Props } from "components/Icons/Props";
10import { TextAlignmentButton } from "./TextAlignmentToolbar";
11-import { getSortedSelection } from "components/SelectionManager";
1213export const MultiselectToolbar = (props: {
14 setToolbarState: (
···8import { LockBlockButton } from "./LockBlockButton";
9import { Props } from "components/Icons/Props";
10import { TextAlignmentButton } from "./TextAlignmentToolbar";
11+import { getSortedSelection } from "components/SelectionManager/selectionState";
1213export const MultiselectToolbar = (props: {
14 setToolbarState: (
+2-1
components/Toolbar/index.tsx
···13import { TextToolbar } from "./TextToolbar";
14import { BlockToolbar } from "./BlockToolbar";
15import { MultiselectToolbar } from "./MultiSelectToolbar";
16-import { AreYouSure, deleteBlock } from "components/Blocks/DeleteBlock";
017import { TooltipButton } from "components/Buttons";
18import { TextAlignmentToolbar } from "./TextAlignmentToolbar";
19import { useIsMobile } from "src/hooks/isMobile";
···13import { TextToolbar } from "./TextToolbar";
14import { BlockToolbar } from "./BlockToolbar";
15import { MultiselectToolbar } from "./MultiSelectToolbar";
16+import { AreYouSure } from "components/Blocks/DeleteBlock";
17+import { deleteBlock } from "src/utils/deleteBlock";
18import { TooltipButton } from "components/Buttons";
19import { TextAlignmentToolbar } from "./TextAlignmentToolbar";
20import { useIsMobile } from "src/hooks/isMobile";
+1-1
components/utils/UpdateLeafletTitle.tsx
···8import { useEntity, useReplicache } from "src/replicache";
9import * as Y from "yjs";
10import * as base64 from "base64-js";
11-import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment";
12import { useParams, useRouter, useSearchParams } from "next/navigation";
13import { focusBlock } from "src/utils/focusBlock";
14import { useIsMobile } from "src/hooks/isMobile";
···8import { useEntity, useReplicache } from "src/replicache";
9import * as Y from "yjs";
10import * as base64 from "base64-js";
11+import { YJSFragmentToString } from "src/utils/yjsFragmentToString";
12import { useParams, useRouter, useSearchParams } from "next/navigation";
13import { focusBlock } from "src/utils/focusBlock";
14import { useIsMobile } from "src/hooks/isMobile";