From 2648890566c24cad2bae4495c2591f6b2e89d3aa Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 19 Nov 2025 11:37:59 -0500 Subject: [PATCH] handle publishing doc theme --- actions/publishToPublication.ts | 105 +++--------------- .../[rkey]/DocumentPageRenderer.tsx | 16 ++- components/Layout.tsx | 1 + components/ThemeManager/PubThemeSetter.tsx | 43 +------ components/ThemeManager/ThemeSetter.tsx | 4 +- components/ThemeManager/colorToLexicons.ts | 44 ++++++++ 6 files changed, 76 insertions(+), 137 deletions(-) create mode 100644 components/ThemeManager/colorToLexicons.ts diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts index e863c78e..3ce55907 100644 --- a/actions/publishToPublication.ts +++ b/actions/publishToPublication.ts @@ -45,6 +45,12 @@ import { List, parseBlocksToList } from "src/utils/parseBlocksToList"; import { getBlocksWithTypeLocal } from "src/hooks/queries/useBlocks"; import { Lock } from "src/utils/lock"; import type { PubLeafletPublication } from "lexicons/api"; +import { + ColorToRGB, + ColorToRGBA, +} from "components/ThemeManager/colorToLexicons"; +import Color from "colorjs.io/types"; +import { parseColor } from "react-aria-components"; export async function publishToPublication({ root_entity, @@ -119,12 +125,12 @@ export async function publishToPublication({ } let record: PubLeafletDocument.Record = { + publishedAt: new Date().toISOString(), + ...existingRecord, $type: "pub.leaflet.document", author: credentialSession.did!, ...(publication_uri && { publication: publication_uri }), ...(theme && { theme }), - publishedAt: new Date().toISOString(), - ...existingRecord, title: title || "Untitled", description: description || "", pages: [ @@ -642,7 +648,6 @@ async function extractThemeFromFacts( agent: AtpBaseClient, ): Promise { let scan = scanIndexLocal(facts); - let pageBackground = scan.eav(root_entity, "theme/page-background")?.[0]?.data .value; let cardBackground = scan.eav(root_entity, "theme/card-background")?.[0]?.data @@ -661,95 +666,21 @@ async function extractThemeFromFacts( "theme/background-image-repeat", )?.[0]; - // Helper to convert hex/hsba color string to RGB/RGBA object - const parseColorToRGB = ( - colorStr: string, - ): - | { $type: "pub.leaflet.theme.color#rgb"; r: number; g: number; b: number } - | { - $type: "pub.leaflet.theme.color#rgba"; - r: number; - g: number; - b: number; - a: number; - } - | undefined => { - // Try hex format first: #RRGGBB - const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(colorStr); - if (hexMatch) { - return { - $type: "pub.leaflet.theme.color#rgb" as const, - r: parseInt(hexMatch[1], 16), - g: parseInt(hexMatch[2], 16), - b: parseInt(hexMatch[3], 16), - }; - } - - // Try hsba format: hsba(h, s%, b%, a) - const hsbaMatch = - /^hsba\((\d+),\s*(\d+)%,\s*(\d+)%,\s*(\d+(?:\.\d+)?)\)$/i.exec(colorStr); - if (hsbaMatch) { - const h = parseInt(hsbaMatch[1]); - const s = parseInt(hsbaMatch[2]) / 100; - const b = parseInt(hsbaMatch[3]) / 100; - const a = Math.round(parseFloat(hsbaMatch[4]) * 100); - - // Convert HSB to RGB - const c = b * s; - const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); - const m = b - c; - - let r = 0, - g = 0, - bl = 0; - if (h >= 0 && h < 60) { - r = c; - g = x; - bl = 0; - } else if (h >= 60 && h < 120) { - r = x; - g = c; - bl = 0; - } else if (h >= 120 && h < 180) { - r = 0; - g = c; - bl = x; - } else if (h >= 180 && h < 240) { - r = 0; - g = x; - bl = c; - } else if (h >= 240 && h < 300) { - r = x; - g = 0; - bl = c; - } else { - r = c; - g = 0; - bl = x; - } - - return { - $type: "pub.leaflet.theme.color#rgba" as const, - r: Math.round((r + m) * 255), - g: Math.round((g + m) * 255), - b: Math.round((bl + m) * 255), - a, - }; - } - - return undefined; - }; - let theme: PubLeafletPublication.Theme = { showPageBackground: showPageBackground ?? true, }; - if (pageBackground) theme.backgroundColor = parseColorToRGB(pageBackground); - if (cardBackground) theme.pageBackground = parseColorToRGB(cardBackground); - if (primary) theme.primary = parseColorToRGB(primary); + if (pageBackground) + theme.backgroundColor = ColorToRGBA(parseColor(`hsba(${pageBackground})`)); + if (cardBackground) + theme.pageBackground = ColorToRGBA(parseColor(`hsba(${cardBackground})`)); + if (primary) theme.primary = ColorToRGB(parseColor(`hsba(${primary})`)); if (accentBackground) - theme.accentBackground = parseColorToRGB(accentBackground); - if (accentText) theme.accentText = parseColorToRGB(accentText); + theme.accentBackground = ColorToRGB( + parseColor(`hsba(${accentBackground})`), + ); + if (accentText) + theme.accentText = ColorToRGB(parseColor(`hsba(${accentText})`)); // Upload background image if present if (backgroundImage?.data) { diff --git a/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx b/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx index ec13e593..5213fad8 100644 --- a/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx +++ b/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx @@ -19,7 +19,13 @@ import { extractCodeBlocks } from "./extractCodeBlocks"; import { LeafletLayout } from "components/LeafletLayout"; import { fetchPollData } from "./fetchPollData"; -export async function DocumentPageRenderer({ did, rkey }: { did: string; rkey: string }) { +export async function DocumentPageRenderer({ + did, + rkey, +}: { + did: string; + rkey: string; +}) { let agent = new AtpAgent({ service: "https://public.api.bsky.app", fetch: (...args) => @@ -30,9 +36,7 @@ export async function DocumentPageRenderer({ did, rkey }: { did: string; rkey: s }); let [document, profile] = await Promise.all([ - getPostPageData( - AtUri.make(did, ids.PubLeafletDocument, rkey).toString(), - ), + getPostPageData(AtUri.make(did, ids.PubLeafletDocument, rkey).toString()), agent.getProfile({ actor: did }), ]); @@ -101,8 +105,8 @@ export async function DocumentPageRenderer({ did, rkey }: { did: string; rkey: s let pubRecord = document.documents_in_publications[0]?.publications ?.record as PubLeafletPublication.Record | undefined; let theme = pubRecord?.theme || record.theme || null; - let pub_creator = document.documents_in_publications[0]?.publications - ?.identity_did || did; + let pub_creator = + document.documents_in_publications[0]?.publications?.identity_did || did; let firstPage = record.pages[0]; let blocks: PubLeafletPagesLinearDocument.Block[] = []; diff --git a/components/Layout.tsx b/components/Layout.tsx index 52184571..269e5aa7 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -1,3 +1,4 @@ +"use client"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { theme } from "tailwind.config"; import { NestedCardThemeProvider } from "./ThemeManager/ThemeProvider"; diff --git a/components/ThemeManager/PubThemeSetter.tsx b/components/ThemeManager/PubThemeSetter.tsx index ece02f4a..9339345b 100644 --- a/components/ThemeManager/PubThemeSetter.tsx +++ b/components/ThemeManager/PubThemeSetter.tsx @@ -16,6 +16,7 @@ import { BackgroundPicker } from "./PubPickers/PubBackgroundPickers"; import { PubAccentPickers } from "./PubPickers/PubAcccentPickers"; import { Separator } from "components/Layout"; import { PubSettingsHeader } from "app/lish/[did]/[publication]/dashboard/PublicationSettings"; +import { ColorToRGB, ColorToRGBA } from "./colorToLexicons"; export type ImageState = { src: string; @@ -343,45 +344,3 @@ const SamplePost = (props: { ); }; - -export function ColorToRGBA(color: Color) { - if (!color) - return { - $type: "pub.leaflet.theme.color#rgba" as const, - r: 0, - g: 0, - b: 0, - a: 1, - }; - let c = color.toFormat("rgba"); - const r = c.getChannelValue("red"); - const g = c.getChannelValue("green"); - const b = c.getChannelValue("blue"); - const a = c.getChannelValue("alpha"); - return { - $type: "pub.leaflet.theme.color#rgba" as const, - r: Math.round(r), - g: Math.round(g), - b: Math.round(b), - a: Math.round(a * 100), - }; -} -function ColorToRGB(color: Color) { - if (!color) - return { - $type: "pub.leaflet.theme.color#rgb" as const, - r: 0, - g: 0, - b: 0, - }; - let c = color.toFormat("rgb"); - const r = c.getChannelValue("red"); - const g = c.getChannelValue("green"); - const b = c.getChannelValue("blue"); - return { - $type: "pub.leaflet.theme.color#rgb" as const, - r: Math.round(r), - g: Math.round(g), - b: Math.round(b), - }; -} diff --git a/components/ThemeManager/ThemeSetter.tsx b/components/ThemeManager/ThemeSetter.tsx index 9a90bfaa..4afc5917 100644 --- a/components/ThemeManager/ThemeSetter.tsx +++ b/components/ThemeManager/ThemeSetter.tsx @@ -70,7 +70,7 @@ export const ThemePopover = (props: { entityID: string; home?: boolean }) => { }, [rep, props.entityID]); if (!permission) return null; - if (pub) return null; + if (pub?.publications) return null; return ( <> @@ -111,7 +111,7 @@ export const ThemeSetterContent = (props: { }, [rep, props.entityID]); if (!permission) return null; - if (pub) return null; + if (pub?.publications) return null; return (
diff --git a/components/ThemeManager/colorToLexicons.ts b/components/ThemeManager/colorToLexicons.ts new file mode 100644 index 00000000..d1f44dcd --- /dev/null +++ b/components/ThemeManager/colorToLexicons.ts @@ -0,0 +1,44 @@ +import { Color } from "react-aria-components"; + +export function ColorToRGBA(color: Color) { + if (!color) + return { + $type: "pub.leaflet.theme.color#rgba" as const, + r: 0, + g: 0, + b: 0, + a: 1, + }; + let c = color.toFormat("rgba"); + const r = c.getChannelValue("red"); + const g = c.getChannelValue("green"); + const b = c.getChannelValue("blue"); + const a = c.getChannelValue("alpha"); + return { + $type: "pub.leaflet.theme.color#rgba" as const, + r: Math.round(r), + g: Math.round(g), + b: Math.round(b), + a: Math.round(a * 100), + }; +} + +export function ColorToRGB(color: Color) { + if (!color) + return { + $type: "pub.leaflet.theme.color#rgb" as const, + r: 0, + g: 0, + b: 0, + }; + let c = color.toFormat("rgb"); + const r = c.getChannelValue("red"); + const g = c.getChannelValue("green"); + const b = c.getChannelValue("blue"); + return { + $type: "pub.leaflet.theme.color#rgb" as const, + r: Math.round(r), + g: Math.round(g), + b: Math.round(b), + }; +} -- 2.51.2