diff --git a/.gitignore b/.gitignore index a944f0e..c0ab772 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ Thumbs.db # Go backend/vendor/ +# Wrangler +.wrangler/ + # other NOTES.md extension.zip diff --git a/backend/internal/api/readingroom.go b/backend/internal/api/readingroom.go index ecd5ecc..47fc1aa 100644 --- a/backend/internal/api/readingroom.go +++ b/backend/internal/api/readingroom.go @@ -879,7 +879,12 @@ func (h *Handler) AddCustomDomain(w http.ResponseWriter, r *http.Request) { h.db.UpsertReadingRoomConfig(r.Context(), rrConfig) if status == "active" { - h.cloudflare.EnsureRouting(domain, session.DID) + var handle string + h.db.Pool().QueryRow(r.Context(), "SELECT handle FROM oauth_sessions WHERE did = $1 LIMIT 1", session.DID).Scan(&handle) + if handle == "" { + handle = session.DID + } + h.cloudflare.EnsureRouting(domain, handle) } WriteSuccess(w, CustomDomainResponse{ @@ -926,7 +931,12 @@ func (h *Handler) PollCustomDomain(w http.ResponseWriter, r *http.Request) { h.db.UpdateReadingRoomDomain(r.Context(), session.DID, rrConfig.CustomDomain, rrConfig.CFHostnameID, status, string(recordsJSON)) if status == "active" && rrConfig.DomainStatus != "active" { - h.cloudflare.EnsureRouting(rrConfig.CustomDomain, session.DID) + var handle string + h.db.Pool().QueryRow(r.Context(), "SELECT handle FROM oauth_sessions WHERE did = $1 LIMIT 1", session.DID).Scan(&handle) + if handle == "" { + handle = session.DID + } + h.cloudflare.EnsureRouting(rrConfig.CustomDomain, handle) rrConfig.DomainStatus = "active" h.writeReadingRoomPublication(r, session, rrConfig.Title, rrConfig.Description, rrConfig) } diff --git a/edge/worker.js b/edge/worker.js index ade4194..046e709 100644 --- a/edge/worker.js +++ b/edge/worker.js @@ -18,6 +18,7 @@ export default { originUrl.hostname = ROOT_DOMAIN; let pathPrefix = "/reading-room/" + handle; + let isNote = false; if (url.pathname === "/" || url.pathname === "") { originUrl.pathname = pathPrefix; @@ -25,10 +26,23 @@ export default { originUrl.pathname = "/api/reading-room/rss/" + handle; } else if (url.pathname === "/.well-known/site.standard.publication" || url.pathname.startsWith("/.well-known/site.standard.publication/")) { originUrl.pathname = "/.well-known/site.standard.publication/reading-room/" + handle; - } else if (url.pathname.startsWith("/api/")) { + } else if ( + url.pathname.startsWith("/api/") || + url.pathname.startsWith("/_astro/") || + url.pathname.startsWith("/dist/") || + url.pathname.startsWith("/fonts/") || + url.pathname.startsWith("/.well-known/") || + /\.(js|css|woff2?|ttf|svg|png|jpg|jpeg|gif|ico|webp|webmanifest|map)$/.test(url.pathname) + ) { + originUrl.pathname = url.pathname; + } else if (url.pathname === "/note" || url.pathname.startsWith("/note/")) { + isNote = true; + originUrl.pathname = pathPrefix + "/note"; + } else if (url.pathname.startsWith("/reading-room/")) { originUrl.pathname = url.pathname; } else { - originUrl.pathname = pathPrefix + url.pathname; + const redirectUrl = new URL(url.pathname + url.search, "https://" + ROOT_DOMAIN); + return Response.redirect(redirectUrl.toString(), 302); } const modifiedRequest = new Request(originUrl, { @@ -44,6 +58,20 @@ export default { const response = await fetch(modifiedRequest); + const contentType = response.headers.get("content-type") || ""; + if (contentType.includes("text/html")) { + let html = await response.text(); + const inject = ``; + html = html.replace("", inject + ""); + const newResponse = new Response(html, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); + newResponse.headers.set("X-Reading-Room-Domain", hostname); + return newResponse; + } + const newResponse = new Response(response.body, response); newResponse.headers.set("X-Reading-Room-Domain", hostname); return newResponse; diff --git a/web/src/views/AppShell.tsx b/web/src/views/AppShell.tsx index 829792a..5f54dfd 100644 --- a/web/src/views/AppShell.tsx +++ b/web/src/views/AppShell.tsx @@ -183,21 +183,38 @@ function AppLayout() { return () => document.removeEventListener("click", handleClick); }, [navigate]); + const rrHandle = (window as unknown as { __READING_ROOM_HANDLE__?: string }) + .__READING_ROOM_HANDLE__; + const rrIsNote = (window as unknown as { __READING_ROOM_IS_NOTE__?: boolean }) + .__READING_ROOM_IS_NOTE__; + const isReadingRoom = + location.pathname.startsWith("/reading-room/") || !!rrHandle; + return ( -
- {location.pathname.startsWith("/reading-room/") && ( +
+ {isReadingRoom && (
- - } - /> - } /> - + {rrHandle ? ( + rrIsNote ? ( + + ) : ( + + ) + ) : ( + + } + /> + } /> + + )}
)} - {!location.pathname.startsWith("/reading-room/") && ( + {!isReadingRoom && ( <> diff --git a/web/src/views/reading-room/ReadingRoom.tsx b/web/src/views/reading-room/ReadingRoom.tsx index e0232c9..3f383c3 100644 --- a/web/src/views/reading-room/ReadingRoom.tsx +++ b/web/src/views/reading-room/ReadingRoom.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { useParams, useSearchParams, Link } from "react-router-dom"; +import { useSearchParams, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Rss, X, Loader2 } from "lucide-react"; import { getPublicReadingRoom, type ReadingRoomPublic } from "../../api/client"; @@ -8,6 +8,14 @@ import { ensureFontLoaded, fontStack } from "../../lib/fonts"; import { buildPalette, noteKind, type NoteKind, type RRNote } from "./theme"; import ReadingRoomCard from "./ReadingRoomCard"; +const ROOT_DOMAIN = "margin.at"; +const onCustomDomain = + typeof window !== "undefined" && + !!(window as unknown as { __READING_ROOM_HANDLE__?: string }) + .__READING_ROOM_HANDLE__; +const externalHref = (path: string) => + onCustomDomain ? `https://${ROOT_DOMAIN}${path}` : path; + const TYPE_ORDER: (NoteKind | "all")[] = [ "all", "highlight", @@ -21,8 +29,11 @@ const FILTER_KEY: Record = { bookmark: "bookmarks", }; -export default function ReadingRoom() { - const { handle } = useParams<{ handle: string }>(); +export default function ReadingRoom({ + handle: propHandle, +}: { handle?: string } = {}) { + const { handle: paramHandle } = useParams<{ handle: string }>(); + const handle = propHandle || paramHandle; return ; } @@ -99,12 +110,12 @@ function ReadingRoomView({ handle }: { handle?: string }) {

{t("readingRoom.notFoundDesc")}

- ← Back to Margin - +
); } @@ -164,14 +175,17 @@ function ReadingRoomView({ handle }: { handle?: string }) {
{data.avatar && ( - + {data.displayName - + )}

)}
- @{displayHandle(data.handle)} - + - Powered by Margin - - + @{displayHandle(data.handle)} - +

diff --git a/web/src/views/reading-room/ReadingRoomNote.tsx b/web/src/views/reading-room/ReadingRoomNote.tsx index 46c5204..7e960e1 100644 --- a/web/src/views/reading-room/ReadingRoomNote.tsx +++ b/web/src/views/reading-room/ReadingRoomNote.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { useParams, useSearchParams, Link } from "react-router-dom"; +import { useSearchParams, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { ArrowLeft, Loader2 } from "lucide-react"; import { getReadingRoomNote } from "../../api/client"; @@ -8,8 +8,20 @@ import { ensureFontLoaded, fontStack } from "../../lib/fonts"; import { buildPalette, type RRNote } from "./theme"; import ReadingRoomCard from "./ReadingRoomCard"; -export default function ReadingRoomNote() { - const { handle } = useParams<{ handle: string }>(); +const ROOT_DOMAIN = "margin.at"; +const onCustomDomain = + typeof window !== "undefined" && + !!(window as unknown as { __READING_ROOM_HANDLE__?: string }) + .__READING_ROOM_HANDLE__; +const externalHref = (path: string) => + onCustomDomain ? `https://${ROOT_DOMAIN}${path}` : path; +const roomHref = (path: string) => (onCustomDomain ? path : path); + +export default function ReadingRoomNote({ + handle: propHandle, +}: { handle?: string } = {}) { + const { handle: paramHandle } = useParams<{ handle: string }>(); + const handle = propHandle || paramHandle; const [searchParams] = useSearchParams(); const uri = searchParams.get("uri") || ""; return ( @@ -94,12 +106,12 @@ function ReadingRoomNoteView({

{t("readingRoom.notFoundDesc")}

- ← Back to Margin - +
); } @@ -119,22 +131,24 @@ function ReadingRoomNoteView({ >
- {roomTitle} - +
{data.avatar && ( - {data.displayName + + {data.displayName + )}

{data.displayName || data.handle}

- @{displayHandle(data.handle)} - +
@@ -175,20 +189,20 @@ function ReadingRoomNoteView({ className="w-full max-w-2xl mx-auto px-6 py-6 flex items-center justify-between text-sm" style={{ color: pal.muted }} > - Powered by Margin - - + {t("readingRoom.viewFullRoom")} - +