a tool for shared writing and social publishing

handle publishing doc theme

+76 -137
+18 -87
actions/publishToPublication.ts
··· 45 45 import { getBlocksWithTypeLocal } from "src/hooks/queries/useBlocks"; 46 46 import { Lock } from "src/utils/lock"; 47 47 import type { PubLeafletPublication } from "lexicons/api"; 48 + import { 49 + ColorToRGB, 50 + ColorToRGBA, 51 + } from "components/ThemeManager/colorToLexicons"; 52 + import Color from "colorjs.io/types"; 53 + import { parseColor } from "react-aria-components"; 48 54 49 55 export async function publishToPublication({ 50 56 root_entity, ··· 119 125 } 120 126 121 127 let record: PubLeafletDocument.Record = { 128 + publishedAt: new Date().toISOString(), 129 + ...existingRecord, 122 130 $type: "pub.leaflet.document", 123 131 author: credentialSession.did!, 124 132 ...(publication_uri && { publication: publication_uri }), 125 133 ...(theme && { theme }), 126 - publishedAt: new Date().toISOString(), 127 - ...existingRecord, 128 134 title: title || "Untitled", 129 135 description: description || "", 130 136 pages: [ ··· 642 648 agent: AtpBaseClient, 643 649 ): Promise<PubLeafletPublication.Theme | undefined> { 644 650 let scan = scanIndexLocal(facts); 645 - 646 651 let pageBackground = scan.eav(root_entity, "theme/page-background")?.[0]?.data 647 652 .value; 648 653 let cardBackground = scan.eav(root_entity, "theme/card-background")?.[0]?.data ··· 661 666 "theme/background-image-repeat", 662 667 )?.[0]; 663 668 664 - // Helper to convert hex/hsba color string to RGB/RGBA object 665 - const parseColorToRGB = ( 666 - colorStr: string, 667 - ): 668 - | { $type: "pub.leaflet.theme.color#rgb"; r: number; g: number; b: number } 669 - | { 670 - $type: "pub.leaflet.theme.color#rgba"; 671 - r: number; 672 - g: number; 673 - b: number; 674 - a: number; 675 - } 676 - | undefined => { 677 - // Try hex format first: #RRGGBB 678 - const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(colorStr); 679 - if (hexMatch) { 680 - return { 681 - $type: "pub.leaflet.theme.color#rgb" as const, 682 - r: parseInt(hexMatch[1], 16), 683 - g: parseInt(hexMatch[2], 16), 684 - b: parseInt(hexMatch[3], 16), 685 - }; 686 - } 687 - 688 - // Try hsba format: hsba(h, s%, b%, a) 689 - const hsbaMatch = 690 - /^hsba\((\d+),\s*(\d+)%,\s*(\d+)%,\s*(\d+(?:\.\d+)?)\)$/i.exec(colorStr); 691 - if (hsbaMatch) { 692 - const h = parseInt(hsbaMatch[1]); 693 - const s = parseInt(hsbaMatch[2]) / 100; 694 - const b = parseInt(hsbaMatch[3]) / 100; 695 - const a = Math.round(parseFloat(hsbaMatch[4]) * 100); 696 - 697 - // Convert HSB to RGB 698 - const c = b * s; 699 - const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); 700 - const m = b - c; 701 - 702 - let r = 0, 703 - g = 0, 704 - bl = 0; 705 - if (h >= 0 && h < 60) { 706 - r = c; 707 - g = x; 708 - bl = 0; 709 - } else if (h >= 60 && h < 120) { 710 - r = x; 711 - g = c; 712 - bl = 0; 713 - } else if (h >= 120 && h < 180) { 714 - r = 0; 715 - g = c; 716 - bl = x; 717 - } else if (h >= 180 && h < 240) { 718 - r = 0; 719 - g = x; 720 - bl = c; 721 - } else if (h >= 240 && h < 300) { 722 - r = x; 723 - g = 0; 724 - bl = c; 725 - } else { 726 - r = c; 727 - g = 0; 728 - bl = x; 729 - } 730 - 731 - return { 732 - $type: "pub.leaflet.theme.color#rgba" as const, 733 - r: Math.round((r + m) * 255), 734 - g: Math.round((g + m) * 255), 735 - b: Math.round((bl + m) * 255), 736 - a, 737 - }; 738 - } 739 - 740 - return undefined; 741 - }; 742 - 743 669 let theme: PubLeafletPublication.Theme = { 744 670 showPageBackground: showPageBackground ?? true, 745 671 }; 746 672 747 - if (pageBackground) theme.backgroundColor = parseColorToRGB(pageBackground); 748 - if (cardBackground) theme.pageBackground = parseColorToRGB(cardBackground); 749 - if (primary) theme.primary = parseColorToRGB(primary); 673 + if (pageBackground) 674 + theme.backgroundColor = ColorToRGBA(parseColor(`hsba(${pageBackground})`)); 675 + if (cardBackground) 676 + theme.pageBackground = ColorToRGBA(parseColor(`hsba(${cardBackground})`)); 677 + if (primary) theme.primary = ColorToRGB(parseColor(`hsba(${primary})`)); 750 678 if (accentBackground) 751 - theme.accentBackground = parseColorToRGB(accentBackground); 752 - if (accentText) theme.accentText = parseColorToRGB(accentText); 679 + theme.accentBackground = ColorToRGB( 680 + parseColor(`hsba(${accentBackground})`), 681 + ); 682 + if (accentText) 683 + theme.accentText = ColorToRGB(parseColor(`hsba(${accentText})`)); 753 684 754 685 // Upload background image if present 755 686 if (backgroundImage?.data) {
+10 -6
app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx
··· 19 19 import { LeafletLayout } from "components/LeafletLayout"; 20 20 import { fetchPollData } from "./fetchPollData"; 21 21 22 - export async function DocumentPageRenderer({ did, rkey }: { did: string; rkey: string }) { 22 + export async function DocumentPageRenderer({ 23 + did, 24 + rkey, 25 + }: { 26 + did: string; 27 + rkey: string; 28 + }) { 23 29 let agent = new AtpAgent({ 24 30 service: "https://public.api.bsky.app", 25 31 fetch: (...args) => ··· 30 36 }); 31 37 32 38 let [document, profile] = await Promise.all([ 33 - getPostPageData( 34 - AtUri.make(did, ids.PubLeafletDocument, rkey).toString(), 35 - ), 39 + getPostPageData(AtUri.make(did, ids.PubLeafletDocument, rkey).toString()), 36 40 agent.getProfile({ actor: did }), 37 41 ]); 38 42 ··· 101 105 let pubRecord = document.documents_in_publications[0]?.publications 102 106 ?.record as PubLeafletPublication.Record | undefined; 103 107 let theme = pubRecord?.theme || record.theme || null; 104 - let pub_creator = document.documents_in_publications[0]?.publications 105 - ?.identity_did || did; 108 + let pub_creator = 109 + document.documents_in_publications[0]?.publications?.identity_did || did; 106 110 107 111 let firstPage = record.pages[0]; 108 112 let blocks: PubLeafletPagesLinearDocument.Block[] = [];
+1
components/Layout.tsx
··· 1 + "use client"; 1 2 import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; 2 3 import { theme } from "tailwind.config"; 3 4 import { NestedCardThemeProvider } from "./ThemeManager/ThemeProvider";
+1 -42
components/ThemeManager/PubThemeSetter.tsx
··· 16 16 import { PubAccentPickers } from "./PubPickers/PubAcccentPickers"; 17 17 import { Separator } from "components/Layout"; 18 18 import { PubSettingsHeader } from "app/lish/[did]/[publication]/dashboard/PublicationSettings"; 19 + import { ColorToRGB, ColorToRGBA } from "./colorToLexicons"; 19 20 20 21 export type ImageState = { 21 22 src: string; ··· 343 344 </div> 344 345 ); 345 346 }; 346 - 347 - export function ColorToRGBA(color: Color) { 348 - if (!color) 349 - return { 350 - $type: "pub.leaflet.theme.color#rgba" as const, 351 - r: 0, 352 - g: 0, 353 - b: 0, 354 - a: 1, 355 - }; 356 - let c = color.toFormat("rgba"); 357 - const r = c.getChannelValue("red"); 358 - const g = c.getChannelValue("green"); 359 - const b = c.getChannelValue("blue"); 360 - const a = c.getChannelValue("alpha"); 361 - return { 362 - $type: "pub.leaflet.theme.color#rgba" as const, 363 - r: Math.round(r), 364 - g: Math.round(g), 365 - b: Math.round(b), 366 - a: Math.round(a * 100), 367 - }; 368 - } 369 - function ColorToRGB(color: Color) { 370 - if (!color) 371 - return { 372 - $type: "pub.leaflet.theme.color#rgb" as const, 373 - r: 0, 374 - g: 0, 375 - b: 0, 376 - }; 377 - let c = color.toFormat("rgb"); 378 - const r = c.getChannelValue("red"); 379 - const g = c.getChannelValue("green"); 380 - const b = c.getChannelValue("blue"); 381 - return { 382 - $type: "pub.leaflet.theme.color#rgb" as const, 383 - r: Math.round(r), 384 - g: Math.round(g), 385 - b: Math.round(b), 386 - }; 387 - }
+2 -2
components/ThemeManager/ThemeSetter.tsx
··· 70 70 }, [rep, props.entityID]); 71 71 72 72 if (!permission) return null; 73 - if (pub) return null; 73 + if (pub?.publications) return null; 74 74 75 75 return ( 76 76 <> ··· 111 111 }, [rep, props.entityID]); 112 112 113 113 if (!permission) return null; 114 - if (pub) return null; 114 + if (pub?.publications) return null; 115 115 return ( 116 116 <div className="themeSetterContent flex flex-col w-full overflow-y-scroll no-scrollbar"> 117 117 <div className="themeBGLeaflet flex">
+44
components/ThemeManager/colorToLexicons.ts
··· 1 + import { Color } from "react-aria-components"; 2 + 3 + export function ColorToRGBA(color: Color) { 4 + if (!color) 5 + return { 6 + $type: "pub.leaflet.theme.color#rgba" as const, 7 + r: 0, 8 + g: 0, 9 + b: 0, 10 + a: 1, 11 + }; 12 + let c = color.toFormat("rgba"); 13 + const r = c.getChannelValue("red"); 14 + const g = c.getChannelValue("green"); 15 + const b = c.getChannelValue("blue"); 16 + const a = c.getChannelValue("alpha"); 17 + return { 18 + $type: "pub.leaflet.theme.color#rgba" as const, 19 + r: Math.round(r), 20 + g: Math.round(g), 21 + b: Math.round(b), 22 + a: Math.round(a * 100), 23 + }; 24 + } 25 + 26 + export function ColorToRGB(color: Color) { 27 + if (!color) 28 + return { 29 + $type: "pub.leaflet.theme.color#rgb" as const, 30 + r: 0, 31 + g: 0, 32 + b: 0, 33 + }; 34 + let c = color.toFormat("rgb"); 35 + const r = c.getChannelValue("red"); 36 + const g = c.getChannelValue("green"); 37 + const b = c.getChannelValue("blue"); 38 + return { 39 + $type: "pub.leaflet.theme.color#rgb" as const, 40 + r: Math.round(r), 41 + g: Math.round(g), 42 + b: Math.round(b), 43 + }; 44 + }