diff --git a/src/hooks/useLocalizedDate.ts b/src/hooks/useLocalizedDate.ts index ad81a4cc..98d8af23 100644 --- a/src/hooks/useLocalizedDate.ts +++ b/src/hooks/useLocalizedDate.ts @@ -28,9 +28,11 @@ export function useLocalizedDate( // On initial page load, use header timezone. After hydration, use system timezone const effectiveTimezone = !hasPageLoaded - ? timezone + ? timezone || "UTC" : Intl.DateTimeFormat().resolvedOptions().timeZone; + console.log("tz", effectiveTimezone); + // Apply timezone if available if (effectiveTimezone) { dateTime = dateTime.setZone(effectiveTimezone); -- 2.51.2 From 6f65facbaa0a50fb3041da96efad5df1984319cd Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 10 Dec 2025 16:00:45 +0400 Subject: [PATCH 2/7] fix userpresevescroll on initial page load --- src/hooks/usePreserveScroll.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePreserveScroll.ts b/src/hooks/usePreserveScroll.ts index c64c83f0..49fdf7b4 100644 --- a/src/hooks/usePreserveScroll.ts +++ b/src/hooks/usePreserveScroll.ts @@ -6,9 +6,10 @@ export function usePreserveScroll(key: string | null) { useEffect(() => { if (!ref.current || !key) return; - window.requestAnimationFrame(() => { - ref.current?.scrollTo({ top: scrollPositions[key] || 0 }); - }); + if (scrollPositions[key] !== undefined) + window.requestAnimationFrame(() => { + ref.current?.scrollTo({ top: scrollPositions[key] || 0 }); + }); const listener = () => { if (!ref.current?.scrollTop) return; -- 2.51.2 From 6ba8aeaa6217b41fe303aba3085a258c561b3253 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 10 Dec 2025 22:07:33 +0400 Subject: [PATCH 3/7] fix placeholder and positioning for bsky mention popover --- .../publish/BskyPostEditorProsemirror.tsx | 22 ++- components/Mention.tsx | 133 +++++++++--------- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx b/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx index 37c62312..379208f5 100644 --- a/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx +++ b/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx @@ -148,10 +148,23 @@ export function BlueskyPostEditorProsemirror(props: { const pos = view.state.selection.from; setMentionInsertPos(pos); const coords = view.coordsAtPos(pos - 1); - setMentionCoords({ - top: coords.bottom + window.scrollY, - left: coords.left + window.scrollX, - }); + + // Get coordinates relative to the positioned parent container + const editorEl = view.dom; + const container = editorEl.closest(".relative") as HTMLElement | null; + + if (container) { + const containerRect = container.getBoundingClientRect(); + setMentionCoords({ + top: coords.bottom - containerRect.top, + left: coords.left - containerRect.left, + }); + } else { + setMentionCoords({ + top: coords.bottom, + left: coords.left, + }); + } setMentionOpen(true); }, []); @@ -270,6 +283,7 @@ export function BlueskyPostEditorProsemirror(props: { view={viewRef} onSelect={handleMentionSelect} coords={mentionCoords} + placeholder="Search people..." /> {editorState?.doc.textContent.length === 0 && (
diff --git a/components/Mention.tsx b/components/Mention.tsx index 2912169e..ee709f21 100644 --- a/components/Mention.tsx +++ b/components/Mention.tsx @@ -18,6 +18,7 @@ export function MentionAutocomplete(props: { view: React.RefObject; onSelect: (mention: Mention) => void; coords: { top: number; left: number } | null; + placeholder?: string; }) { const [searchQuery, setSearchQuery] = useState(""); const [noResults, setNoResults] = useState(false); @@ -207,7 +208,7 @@ export function MentionAutocomplete(props: { placeholder={ scope.type === "publication" ? "Search posts..." - : "Search people & publications..." + : props.placeholder ?? "Search people & publications..." } className="flex-1 w-full min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary" /> @@ -219,72 +220,72 @@ export function MentionAutocomplete(props: { No results found
)} -
    - {sortedSuggestions.map((result, index) => { - const prevResult = sortedSuggestions[index - 1]; - const showHeader = - index === 0 || - (prevResult && prevResult.type !== result.type); +
      + {sortedSuggestions.map((result, index) => { + const prevResult = sortedSuggestions[index - 1]; + const showHeader = + index === 0 || + (prevResult && prevResult.type !== result.type); - return ( - - {showHeader && ( - <> - {index > 0 && ( -
      - )} -
      - {getHeader(result.type, scope)} -
      - - )} - {result.type === "did" ? ( - { - props.onSelect(result); - props.onOpenChange(false); - }} - onMouseDown={(e) => e.preventDefault()} - displayName={result.displayName} - handle={result.handle} - avatar={result.avatar} - selected={index === suggestionIndex} - /> - ) : result.type === "publication" ? ( - { - props.onSelect(result); - props.onOpenChange(false); - }} - onMouseDown={(e) => e.preventDefault()} - pubName={result.name} - uri={result.uri} - selected={index === suggestionIndex} - onPostsClick={() => { - handleScopeChange({ - type: "publication", - uri: result.uri, - name: result.name, - }); - }} - /> - ) : ( - { - props.onSelect(result); - props.onOpenChange(false); - }} - onMouseDown={(e) => e.preventDefault()} - title={result.title} - selected={index === suggestionIndex} - /> - )} -
      - ); - })} -
    + return ( + + {showHeader && ( + <> + {index > 0 && ( +
    + )} +
    + {getHeader(result.type, scope)} +
    + + )} + {result.type === "did" ? ( + { + props.onSelect(result); + props.onOpenChange(false); + }} + onMouseDown={(e) => e.preventDefault()} + displayName={result.displayName} + handle={result.handle} + avatar={result.avatar} + selected={index === suggestionIndex} + /> + ) : result.type === "publication" ? ( + { + props.onSelect(result); + props.onOpenChange(false); + }} + onMouseDown={(e) => e.preventDefault()} + pubName={result.name} + uri={result.uri} + selected={index === suggestionIndex} + onPostsClick={() => { + handleScopeChange({ + type: "publication", + uri: result.uri, + name: result.name, + }); + }} + /> + ) : ( + { + props.onSelect(result); + props.onOpenChange(false); + }} + onMouseDown={(e) => e.preventDefault()} + title={result.title} + selected={index === suggestionIndex} + /> + )} +
    + ); + })} +
-- 2.51.2 From 571cb611a0a05e72d54820bb5e964dfe5abcc2e3 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 10 Dec 2025 22:26:43 +0400 Subject: [PATCH 4/7] handle pub and post urls in bsky post editor --- .../publish/BskyPostEditorProsemirror.tsx | 45 +++++++++++++------ .../[command]/search_publication_documents.ts | 21 ++++++--- .../rpc/[command]/search_publication_names.ts | 18 ++++---- components/Mention.tsx | 6 ++- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx b/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx index 379208f5..6496c57b 100644 --- a/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx +++ b/app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx @@ -170,7 +170,6 @@ export function BlueskyPostEditorProsemirror(props: { const handleMentionSelect = useCallback( (mention: Mention) => { - if (mention.type !== "did") return; if (!viewRef.current || mentionInsertPos === null) return; const view = viewRef.current; const from = mentionInsertPos - 1; @@ -180,19 +179,37 @@ export function BlueskyPostEditorProsemirror(props: { // Delete the @ symbol tr.delete(from, to); - // Insert @handle - const mentionText = "@" + mention.handle; - tr.insertText(mentionText, from); - - // Apply mention mark - tr.addMark( - from, - from + mentionText.length, - bskyPostSchema.marks.mention.create({ did: mention.did }), - ); - - // Add a space after the mention - tr.insertText(" ", from + mentionText.length); + if (mention.type === "did") { + // Insert @handle with mention mark + const mentionText = "@" + mention.handle; + tr.insertText(mentionText, from); + tr.addMark( + from, + from + mentionText.length, + bskyPostSchema.marks.mention.create({ did: mention.did }), + ); + tr.insertText(" ", from + mentionText.length); + } else if (mention.type === "publication") { + // Insert publication name as a link + const linkText = mention.name; + tr.insertText(linkText, from); + tr.addMark( + from, + from + linkText.length, + bskyPostSchema.marks.link.create({ href: mention.url }), + ); + tr.insertText(" ", from + linkText.length); + } else if (mention.type === "post") { + // Insert post title as a link + const linkText = mention.title; + tr.insertText(linkText, from); + tr.addMark( + from, + from + linkText.length, + bskyPostSchema.marks.link.create({ href: mention.url }), + ); + tr.insertText(" ", from + linkText.length); + } view.dispatch(tr); view.focus(); diff --git a/app/api/rpc/[command]/search_publication_documents.ts b/app/api/rpc/[command]/search_publication_documents.ts index 382820f7..a58f6867 100644 --- a/app/api/rpc/[command]/search_publication_documents.ts +++ b/app/api/rpc/[command]/search_publication_documents.ts @@ -1,6 +1,8 @@ +import { AtUri } from "@atproto/api"; import { z } from "zod"; import { makeRoute } from "../lib"; import type { Env } from "./route"; +import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; export type SearchPublicationDocumentsReturnType = Awaited< ReturnType<(typeof search_publication_documents)["handler"]> @@ -18,9 +20,12 @@ export const search_publication_documents = makeRoute({ { supabase }: Pick, ) => { // Get documents in the publication, filtering by title using JSON operator + // Also join with publications to get the record for URL construction const { data: documents, error } = await supabase .from("documents_in_publications") - .select("document, documents!inner(uri, data)") + .select( + "document, documents!inner(uri, data), publications!inner(uri, record)", + ) .eq("publication", publication_uri) .ilike("documents.data->>title", `%${query}%`) .limit(limit); @@ -31,10 +36,16 @@ export const search_publication_documents = makeRoute({ ); } - const result = documents.map((d) => ({ - uri: d.documents.uri, - title: (d.documents.data as { title?: string })?.title || "Untitled", - })); + const result = documents.map((d) => { + const docUri = new AtUri(d.documents.uri); + const pubUrl = getPublicationURL(d.publications); + + return { + uri: d.documents.uri, + title: (d.documents.data as { title?: string })?.title || "Untitled", + url: `${pubUrl}/${docUri.rkey}`, + }; + }); return { result: { documents: result } }; }, diff --git a/app/api/rpc/[command]/search_publication_names.ts b/app/api/rpc/[command]/search_publication_names.ts index cd042311..72636497 100644 --- a/app/api/rpc/[command]/search_publication_names.ts +++ b/app/api/rpc/[command]/search_publication_names.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { makeRoute } from "../lib"; import type { Env } from "./route"; +import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; export type SearchPublicationNamesReturnType = Awaited< ReturnType<(typeof search_publication_names)["handler"]> @@ -12,10 +13,7 @@ export const search_publication_names = makeRoute({ query: z.string(), limit: z.number().optional().default(10), }), - handler: async ( - { query, limit }, - { supabase }: Pick, - ) => { + handler: async ({ query, limit }, { supabase }: Pick) => { // Search publications by name in record (case-insensitive partial match) const { data: publications, error } = await supabase .from("publications") @@ -27,10 +25,14 @@ export const search_publication_names = makeRoute({ throw new Error(`Failed to search publications: ${error.message}`); } - const result = publications.map((p) => ({ - uri: p.uri, - name: (p.record as { name?: string })?.name || "Untitled", - })); + const result = publications.map((p) => { + const record = p.record as { name?: string }; + return { + uri: p.uri, + name: record.name || "Untitled", + url: getPublicationURL(p), + }; + }); return { result: { publications: result } }; }, diff --git a/components/Mention.tsx b/components/Mention.tsx index ee709f21..32e0075a 100644 --- a/components/Mention.tsx +++ b/components/Mention.tsx @@ -457,8 +457,8 @@ export type Mention = displayName?: string; avatar?: string; } - | { type: "publication"; uri: string; name: string } - | { type: "post"; uri: string; title: string }; + | { type: "publication"; uri: string; name: string; url: string } + | { type: "post"; uri: string; title: string; url: string }; export type MentionScope = | { type: "default" } @@ -493,6 +493,7 @@ function useMentionSuggestions(query: string | null) { type: "post" as const, uri: d.uri, title: d.title, + url: d.url, })), ); } else { @@ -517,6 +518,7 @@ function useMentionSuggestions(query: string | null) { type: "publication" as const, uri: p.uri, name: p.name, + url: p.url, })), ]); } -- 2.51.2 From afa115e890034c33fb036d1ebcb531b7f9e2245d Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 11 Dec 2025 07:17:44 +0400 Subject: [PATCH 5/7] fix pub favicons --- app/lish/[did]/[publication]/[rkey]/page.tsx | 7 +++++++ .../[publication]/{icon.ts => icon/route.ts} | 15 +++++++-------- app/lish/[did]/[publication]/layout.tsx | 8 ++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) rename app/lish/[did]/[publication]/{icon.ts => icon/route.ts} (90%) diff --git a/app/lish/[did]/[publication]/[rkey]/page.tsx b/app/lish/[did]/[publication]/[rkey]/page.tsx index 0d7f22e5..8c2ec4b4 100644 --- a/app/lish/[did]/[publication]/[rkey]/page.tsx +++ b/app/lish/[did]/[publication]/[rkey]/page.tsx @@ -25,6 +25,13 @@ export async function generateMetadata(props: { return { icons: { + icon: { + + url: + process.env.NODE_ENV === "development" + ? `/lish/${did}/${params.publication}/icon` + : "/icon", + , sizes: "32x32", type: "image/png" }, other: { rel: "alternate", url: document.uri, diff --git a/app/lish/[did]/[publication]/icon.ts b/app/lish/[did]/[publication]/icon/route.ts similarity index 90% rename from app/lish/[did]/[publication]/icon.ts rename to app/lish/[did]/[publication]/icon/route.ts index 106f239c..e7d85852 100644 --- a/app/lish/[did]/[publication]/icon.ts +++ b/app/lish/[did]/[publication]/icon/route.ts @@ -8,15 +8,13 @@ import { redirect } from "next/navigation"; let idResolver = new IdResolver(); -export const size = { - width: 32, - height: 32, -}; +export const dynamic = "force-dynamic"; -export const contentType = "image/png"; -export default async function Icon(props: { - params: Promise<{ did: string; publication: string }>; -}) { +export async function GET( + request: NextRequest, + props: { params: Promise<{ did: string; publication: string }> } +) { + console.log("are we getting here?"); const params = await props.params; try { let did = decodeURIComponent(params.did); @@ -63,6 +61,7 @@ export default async function Icon(props: { }, }); } catch (e) { + console.log(e); return redirect("/icon.png"); } } diff --git a/app/lish/[did]/[publication]/layout.tsx b/app/lish/[did]/[publication]/layout.tsx index 7166e89d..1518b718 100644 --- a/app/lish/[did]/[publication]/layout.tsx +++ b/app/lish/[did]/[publication]/layout.tsx @@ -47,6 +47,14 @@ export async function generateMetadata(props: { title: pubRecord?.name || "Untitled Publication", description: pubRecord?.description || "", icons: { + icon: { + url: + process.env.NODE_ENV === "development" + ? `/lish/${did}/${publication_name}/icon` + : "/icon", + sizes: "32x32", + type: "image/png", + }, other: { rel: "alternate", url: publication.uri, -- 2.51.2 From 7d66e0033ae2508aa36a38d40e48914c07c938ec Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 11 Dec 2025 07:17:49 +0400 Subject: [PATCH 6/7] fix null bg-image --- components/ThemeManager/PublicationThemeProvider.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/ThemeManager/PublicationThemeProvider.tsx b/components/ThemeManager/PublicationThemeProvider.tsx index b8ff543c..cf29abbb 100644 --- a/components/ThemeManager/PublicationThemeProvider.tsx +++ b/components/ThemeManager/PublicationThemeProvider.tsx @@ -84,7 +84,9 @@ export function PublicationBackgroundProvider(props: {
Date: Thu, 11 Dec 2025 08:42:54 +0400 Subject: [PATCH 7/7] fix syntax error --- app/lish/[did]/[publication]/[rkey]/page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lish/[did]/[publication]/[rkey]/page.tsx b/app/lish/[did]/[publication]/[rkey]/page.tsx index 8c2ec4b4..bf1ea7e8 100644 --- a/app/lish/[did]/[publication]/[rkey]/page.tsx +++ b/app/lish/[did]/[publication]/[rkey]/page.tsx @@ -26,12 +26,13 @@ export async function generateMetadata(props: { return { icons: { icon: { - url: process.env.NODE_ENV === "development" ? `/lish/${did}/${params.publication}/icon` : "/icon", - , sizes: "32x32", type: "image/png" }, + sizes: "32x32", + type: "image/png", + }, other: { rel: "alternate", url: document.uri,