From dbf7cf24e7b43fd2541d024c7c92848929e15f02 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 8 Feb 2026 10:07:24 +0400 Subject: [PATCH] add per post interactions --- actions/publishToPublication.ts | 21 + app/[leaflet_id]/Sidebar.tsx | 2 + app/[leaflet_id]/actions/PublishButton.tsx | 10 + app/[leaflet_id]/publish/PublishPost.tsx | 10 + app/api/rpc/[command]/pull.ts | 7 + .../[rkey]/DocumentPageRenderer.tsx | 3 +- .../[did]/[publication]/[rkey]/PostPages.tsx | 8 +- components/Canvas.tsx | 21 +- components/Pages/PublicationMetadata.tsx | 25 +- components/PostListing.tsx | 13 +- components/PostSettings.tsx | 96 +++++ lexicons/api/lexicons.ts | 17 + lexicons/api/types/pub/leaflet/document.ts | 1 + lexicons/api/types/pub/leaflet/publication.ts | 84 ++-- lexicons/api/types/site/standard/document.ts | 1 + .../api/types/site/standard/publication.ts | 62 ++- lexicons/pub/leaflet/document.json | 4 + lexicons/site/standard/document.json | 5 + lexicons/src/document.ts | 4 + lexicons/src/normalize.ts | 10 + package-lock.json | 397 +----------------- package.json | 1 - src/replicache/mutations.ts | 13 + src/utils/mergePreferences.ts | 24 ++ supabase/database.types.ts | 6 + ...260208000000_add_preferences_to_drafts.sql | 2 + 26 files changed, 356 insertions(+), 491 deletions(-) create mode 100644 components/PostSettings.tsx create mode 100644 src/utils/mergePreferences.ts create mode 100644 supabase/migrations/20260208000000_add_preferences_to_drafts.sql diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts index db3c789e..a1a93592 100644 --- a/actions/publishToPublication.ts +++ b/actions/publishToPublication.ts @@ -78,6 +78,7 @@ export async function publishToPublication({ cover_image, entitiesToDelete, publishedAt, + postPreferences, }: { root_entity: string; publication_uri?: string; @@ -88,6 +89,11 @@ export async function publishToPublication({ cover_image?: string | null; entitiesToDelete?: string[]; publishedAt?: string; + postPreferences?: { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null; }): Promise { let identity = await getIdentityData(); if (!identity || !identity.atp_did) { @@ -175,6 +181,9 @@ export async function publishToPublication({ }; } + // Resolve preferences: explicit param > draft DB value + const preferences = postPreferences ?? draft?.preferences; + // Extract theme for standalone documents (not for publications) let theme: PubLeafletPublication.Theme | undefined; if (!publication_uri) { @@ -245,6 +254,12 @@ export async function publishToPublication({ ...(coverImageBlob && { coverImage: coverImageBlob }), // Include theme for standalone documents (not for publication documents) ...(!publication_uri && theme && { theme }), + ...(preferences && { + preferences: { + $type: "pub.leaflet.publication#preferences" as const, + ...preferences, + }, + }), content: { $type: "pub.leaflet.content" as const, pages: pagesArray, @@ -257,6 +272,12 @@ export async function publishToPublication({ author: credentialSession.did!, ...(publication_uri && { publication: publication_uri }), ...(theme && { theme }), + ...(preferences && { + preferences: { + $type: "pub.leaflet.publication#preferences" as const, + ...preferences, + }, + }), title: title || "Untitled", description: description || "", ...(tags !== undefined && { tags }), diff --git a/app/[leaflet_id]/Sidebar.tsx b/app/[leaflet_id]/Sidebar.tsx index 568ed74d..14ac86cc 100644 --- a/app/[leaflet_id]/Sidebar.tsx +++ b/app/[leaflet_id]/Sidebar.tsx @@ -8,6 +8,7 @@ import { useLeafletPublicationData } from "components/PageSWRDataProvider"; import { ShareOptions } from "app/[leaflet_id]/actions/ShareOptions"; import { ThemePopover } from "components/ThemeManager/ThemeSetter"; import { PublishButton } from "./actions/PublishButton"; +import { PostSettings } from "components/PostSettings"; import { Watermark } from "components/Watermark"; import { BackToPubButton } from "./actions/BackToPubButton"; import { useIdentityData } from "components/IdentityProvider"; @@ -30,6 +31,7 @@ export function LeafletSidebar() { +
diff --git a/app/[leaflet_id]/actions/PublishButton.tsx b/app/[leaflet_id]/actions/PublishButton.tsx index 0e6c4bc2..7ba596ef 100644 --- a/app/[leaflet_id]/actions/PublishButton.tsx +++ b/app/[leaflet_id]/actions/PublishButton.tsx @@ -96,6 +96,15 @@ const UpdateButton = () => { tx.get("publication_cover_image"), ); + // Get post preferences from Replicache state + let postPreferences = useSubscribe(rep, (tx) => + tx.get<{ + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null>("post_preferences"), + ); + // Get local published at from Replicache (session-only state, not persisted to DB) let publishedAt = useLocalPublishedAt((s) => pub?.doc ? s[pub?.doc] : undefined, @@ -118,6 +127,7 @@ const UpdateButton = () => { tags: currentTags, cover_image: coverImage, publishedAt: publishedAt?.toISOString(), + postPreferences, }); setIsLoading(false); mutate(); diff --git a/app/[leaflet_id]/publish/PublishPost.tsx b/app/[leaflet_id]/publish/PublishPost.tsx index adf48c04..20c3e202 100644 --- a/app/[leaflet_id]/publish/PublishPost.tsx +++ b/app/[leaflet_id]/publish/PublishPost.tsx @@ -91,6 +91,15 @@ const PublishPostForm = ( tx.get("publication_cover_image"), ); + // Get post preferences from Replicache state + let postPreferences = useSubscribe(rep, (tx) => + tx.get<{ + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null>("post_preferences"), + ); + // Use Replicache tags only when we have a draft const currentTags = props.hasDraft ? Array.isArray(replicacheTags) @@ -124,6 +133,7 @@ const PublishPostForm = ( cover_image: replicacheCoverImage, entitiesToDelete: props.entitiesToDelete, publishedAt: localPublishedAt?.toISOString() || new Date().toISOString(), + postPreferences, }); if (!result.success) { diff --git a/app/api/rpc/[command]/pull.ts b/app/api/rpc/[command]/pull.ts index ea3b3845..31ae3ac6 100644 --- a/app/api/rpc/[command]/pull.ts +++ b/app/api/rpc/[command]/pull.ts @@ -9,6 +9,7 @@ import { FactWithIndexes } from "src/replicache/utils"; import type { Attribute } from "src/replicache/attributes"; import { makeRoute } from "../lib"; import type { Env } from "./route"; +import type { Json } from "supabase/database.types"; // First define the sub-types for V0 and V1 requests const pullRequestV0 = z.object({ @@ -75,6 +76,7 @@ export const pull = makeRoute({ title: string; tags: string[]; cover_image: string | null; + preferences: Json | null; }[]; let pub_patch = publication_data?.[0] ? [ @@ -98,6 +100,11 @@ export const pull = makeRoute({ key: "publication_cover_image", value: publication_data[0].cover_image || null, }, + { + op: "put", + key: "post_preferences", + value: publication_data[0].preferences || null, + }, ] : []; diff --git a/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx b/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx index bf57101e..7c9a5d55 100644 --- a/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx +++ b/app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx @@ -21,6 +21,7 @@ import { } from "src/utils/normalizeRecords"; import { DocumentProvider } from "contexts/DocumentContext"; import { LeafletContentProvider } from "contexts/LeafletContentContext"; +import { mergePreferences } from "src/utils/mergePreferences"; export async function DocumentPageRenderer({ did, @@ -133,7 +134,7 @@ export async function DocumentPageRenderer({ { let { data: pub, normalizedPublication } = useLeafletPublicationData(); + let { rep } = useReplicache(); + let postPreferences = useSubscribe(rep, (tx) => + tx.get<{ + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null>("post_preferences"), + ); if (!pub || !pub.publications) return null; if (!normalizedPublication) return null; - let showComments = normalizedPublication.preferences?.showComments !== false; - let showMentions = normalizedPublication.preferences?.showMentions !== false; - let showRecommends = - normalizedPublication.preferences?.showRecommends !== false; + let merged = mergePreferences( + postPreferences || undefined, + normalizedPublication.preferences, + ); + let showComments = merged.showComments !== false; + let showMentions = merged.showMentions !== false; + let showRecommends = merged.showRecommends !== false; return (
diff --git a/components/Pages/PublicationMetadata.tsx b/components/Pages/PublicationMetadata.tsx index 2caa7d15..27803b9b 100644 --- a/components/Pages/PublicationMetadata.tsx +++ b/components/Pages/PublicationMetadata.tsx @@ -21,6 +21,7 @@ import { useIdentityData } from "components/IdentityProvider"; import { PostHeaderLayout } from "app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader"; import { Backdater } from "./Backdater"; import { RecommendTinyEmpty } from "components/Icons/RecommendTiny"; +import { mergePreferences } from "src/utils/mergePreferences"; export const PublicationMetadata = (props: { noInteractions?: boolean }) => { let { rep } = useReplicache(); @@ -34,6 +35,17 @@ export const PublicationMetadata = (props: { noInteractions?: boolean }) => { let description = useSubscribe(rep, (tx) => tx.get("publication_description"), ); + let postPreferences = useSubscribe(rep, (tx) => + tx.get<{ + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null>("post_preferences"), + ); + let merged = mergePreferences( + postPreferences || undefined, + normalizedPublication?.preferences, + ); let publishedAt = normalizedDocument?.publishedAt; if (!pub) return null; @@ -121,28 +133,27 @@ export const PublicationMetadata = (props: { noInteractions?: boolean }) => { )} {!props.noInteractions && (
- {normalizedPublication?.preferences?.showRecommends !== false && ( + {merged.showRecommends !== false && (
—
)} - {normalizedPublication?.preferences?.showMentions !== false && ( + {merged.showMentions !== false && (
—
)} - {normalizedPublication?.preferences?.showComments !== false && ( + {merged.showComments !== false && (
—
)} {tags && ( <> - {normalizedPublication?.preferences?.showRecommends !== - false || - normalizedPublication?.preferences?.showMentions !== false || - normalizedPublication?.preferences?.showComments !== false ? ( + {merged.showRecommends !== false || + merged.showMentions !== false || + merged.showComments !== false ? ( ) : null} diff --git a/components/PostListing.tsx b/components/PostListing.tsx index ef6105a4..32bd9bd4 100644 --- a/components/PostListing.tsx +++ b/components/PostListing.tsx @@ -17,6 +17,7 @@ import type { Post } from "app/(home-pages)/reader/getReaderFeed"; import Link from "next/link"; import { InteractionPreview } from "./InteractionsPreview"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; +import { mergePreferences } from "src/utils/mergePreferences"; export const PostListing = (props: Post) => { let pubRecord = props.publication?.pubRecord as @@ -48,9 +49,11 @@ export const PostListing = (props: Post) => { ? pubRecord?.theme?.showPageBackground : postRecord.theme?.showPageBackground ?? true; + let mergedPrefs = mergePreferences(postRecord?.preferences, pubRecord?.preferences); + let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0; let comments = - pubRecord?.preferences?.showComments === false + mergedPrefs.showComments === false ? 0 : props.documents.comments_on_documents?.[0]?.count || 0; let recommends = props.documents.recommends_on_documents?.[0]?.count || 0; @@ -109,11 +112,9 @@ export const PostListing = (props: Post) => { recommendsCount={recommends} documentUri={props.documents.uri} tags={tags} - showComments={pubRecord?.preferences?.showComments !== false} - showMentions={pubRecord?.preferences?.showMentions !== false} - showRecommends={ - pubRecord?.preferences?.showRecommends !== false - } + showComments={mergedPrefs.showComments !== false} + showMentions={mergedPrefs.showMentions !== false} + showRecommends={mergedPrefs.showRecommends !== false} share />
diff --git a/components/PostSettings.tsx b/components/PostSettings.tsx new file mode 100644 index 00000000..d8a92a0b --- /dev/null +++ b/components/PostSettings.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { ActionButton } from "components/ActionBar/ActionButton"; +import { SettingsSmall } from "components/Icons/SettingsSmall"; +import { Toggle } from "components/Toggle"; +import { Popover } from "components/Popover"; +import { useLeafletPublicationData } from "components/PageSWRDataProvider"; +import { useReplicache } from "src/replicache"; +import { useSubscribe } from "src/replicache/useSubscribe"; + +type PostPreferences = { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; +}; + +export function PostSettings() { + let { data: pub, normalizedPublication } = useLeafletPublicationData(); + let { rep } = useReplicache(); + + let postPreferences = useSubscribe(rep, (tx) => + tx.get("post_preferences"), + ); + + if (!pub || !pub.publications) return null; + + let pubPrefs = normalizedPublication?.preferences; + + let showComments = + postPreferences?.showComments ?? pubPrefs?.showComments ?? true; + let showMentions = + postPreferences?.showMentions ?? pubPrefs?.showMentions ?? true; + let showRecommends = + postPreferences?.showRecommends ?? pubPrefs?.showRecommends ?? true; + + const updatePreference = ( + field: keyof PostPreferences, + value: boolean, + ) => { + let current: PostPreferences = postPreferences || {}; + rep?.mutate.updatePublicationDraft({ + preferences: { ...current, [field]: value }, + }); + }; + + return ( + } + label="Settings" + /> + } + > +
+
+ This Post Settings +
+
+ updatePreference("showComments", !showComments)} + > +
Show Comments
+
+ updatePreference("showMentions", !showMentions)} + > +
+
Show Mentions
+
+ Display a list Bluesky mentions about your post +
+
+
+ updatePreference("showRecommends", !showRecommends)} + > +
+
Show Recommends
+
+ Allow readers to recommend/like your post +
+
+
+
+
+
+ ); +} diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts index a197ea95..a4b2a81f 100644 --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -1469,6 +1469,10 @@ export const schemaDict = { type: 'ref', ref: 'lex:pub.leaflet.publication#theme', }, + preferences: { + type: 'ref', + ref: 'lex:pub.leaflet.publication#preferences', + }, tags: { type: 'array', items: { @@ -1868,6 +1872,10 @@ export const schemaDict = { type: 'boolean', default: true, }, + showRecommends: { + type: 'boolean', + default: true, + }, }, }, theme: { @@ -2194,6 +2202,11 @@ export const schemaDict = { maxLength: 5000, type: 'string', }, + preferences: { + type: 'union', + refs: ['lex:pub.leaflet.publication#preferences'], + closed: false, + }, updatedAt: { format: 'datetime', type: 'string', @@ -2290,6 +2303,10 @@ export const schemaDict = { default: false, type: 'boolean', }, + showRecommends: { + default: true, + type: 'boolean', + }, }, type: 'object', }, diff --git a/lexicons/api/types/pub/leaflet/document.ts b/lexicons/api/types/pub/leaflet/document.ts index 6f6805da..ff587ec7 100644 --- a/lexicons/api/types/pub/leaflet/document.ts +++ b/lexicons/api/types/pub/leaflet/document.ts @@ -23,6 +23,7 @@ export interface Record { publication?: string author: string theme?: PubLeafletPublication.Theme + preferences?: PubLeafletPublication.Preferences tags?: string[] coverImage?: BlobRef pages: ( diff --git a/lexicons/api/types/pub/leaflet/publication.ts b/lexicons/api/types/pub/leaflet/publication.ts index 51143ee0..a2aaee34 100644 --- a/lexicons/api/types/pub/leaflet/publication.ts +++ b/lexicons/api/types/pub/leaflet/publication.ts @@ -1,94 +1,90 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { type ValidationResult, BlobRef } from "@atproto/lexicon"; -import { CID } from "multiformats/cid"; -import { validate as _validate } from "../../../lexicons"; -import { - type $Typed, - is$typed as _is$typed, - type OmitKey, -} from "../../../util"; -import type * as PubLeafletThemeColor from "./theme/color"; -import type * as PubLeafletThemeBackgroundImage from "./theme/backgroundImage"; +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../lexicons' +import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' +import type * as PubLeafletThemeColor from './theme/color' +import type * as PubLeafletThemeBackgroundImage from './theme/backgroundImage' const is$typed = _is$typed, - validate = _validate; -const id = "pub.leaflet.publication"; + validate = _validate +const id = 'pub.leaflet.publication' export interface Record { - $type: "pub.leaflet.publication"; - name: string; - base_path?: string; - description?: string; - icon?: BlobRef; - theme?: Theme; - preferences?: Preferences; - [k: string]: unknown; + $type: 'pub.leaflet.publication' + name: string + base_path?: string + description?: string + icon?: BlobRef + theme?: Theme + preferences?: Preferences + [k: string]: unknown } -const hashRecord = "main"; +const hashRecord = 'main' export function isRecord(v: V) { - return is$typed(v, id, hashRecord); + return is$typed(v, id, hashRecord) } export function validateRecord(v: V) { - return validate(v, id, hashRecord, true); + return validate(v, id, hashRecord, true) } export interface Preferences { - $type?: "pub.leaflet.publication#preferences"; - showInDiscover: boolean; - showComments: boolean; - showMentions: boolean; - showPrevNext: boolean; - showRecommends: boolean; + $type?: 'pub.leaflet.publication#preferences' + showInDiscover: boolean + showComments: boolean + showMentions: boolean + showPrevNext: boolean + showRecommends: boolean } -const hashPreferences = "preferences"; +const hashPreferences = 'preferences' export function isPreferences(v: V) { - return is$typed(v, id, hashPreferences); + return is$typed(v, id, hashPreferences) } export function validatePreferences(v: V) { - return validate(v, id, hashPreferences); + return validate(v, id, hashPreferences) } export interface Theme { - $type?: "pub.leaflet.publication#theme"; + $type?: 'pub.leaflet.publication#theme' backgroundColor?: | $Typed | $Typed - | { $type: string }; - backgroundImage?: PubLeafletThemeBackgroundImage.Main; - pageWidth?: number; + | { $type: string } + backgroundImage?: PubLeafletThemeBackgroundImage.Main + pageWidth?: number primary?: | $Typed | $Typed - | { $type: string }; + | { $type: string } pageBackground?: | $Typed | $Typed - | { $type: string }; - showPageBackground: boolean; + | { $type: string } + showPageBackground: boolean accentBackground?: | $Typed | $Typed - | { $type: string }; + | { $type: string } accentText?: | $Typed | $Typed - | { $type: string }; + | { $type: string } } -const hashTheme = "theme"; +const hashTheme = 'theme' export function isTheme(v: V) { - return is$typed(v, id, hashTheme); + return is$typed(v, id, hashTheme) } export function validateTheme(v: V) { - return validate(v, id, hashTheme); + return validate(v, id, hashTheme) } diff --git a/lexicons/api/types/site/standard/document.ts b/lexicons/api/types/site/standard/document.ts index dccb1dfe..b6ff4fc0 100644 --- a/lexicons/api/types/site/standard/document.ts +++ b/lexicons/api/types/site/standard/document.ts @@ -28,6 +28,7 @@ export interface Record { textContent?: string theme?: PubLeafletPublication.Theme title: string + preferences?: $Typed | { $type: string } updatedAt?: string [k: string]: unknown } diff --git a/lexicons/api/types/site/standard/publication.ts b/lexicons/api/types/site/standard/publication.ts index 08f80d9c..42211459 100644 --- a/lexicons/api/types/site/standard/publication.ts +++ b/lexicons/api/types/site/standard/publication.ts @@ -1,58 +1,54 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { type ValidationResult, BlobRef } from "@atproto/lexicon"; -import { CID } from "multiformats/cid"; -import { validate as _validate } from "../../../lexicons"; -import { - type $Typed, - is$typed as _is$typed, - type OmitKey, -} from "../../../util"; -import type * as SiteStandardThemeBasic from "./theme/basic"; -import type * as PubLeafletPublication from "../../pub/leaflet/publication"; +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../lexicons' +import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' +import type * as SiteStandardThemeBasic from './theme/basic' +import type * as PubLeafletPublication from '../../pub/leaflet/publication' const is$typed = _is$typed, - validate = _validate; -const id = "site.standard.publication"; + validate = _validate +const id = 'site.standard.publication' export interface Record { - $type: "site.standard.publication"; - basicTheme?: SiteStandardThemeBasic.Main; - theme?: $Typed | { $type: string }; - description?: string; - icon?: BlobRef; - name: string; - preferences?: Preferences; - url: string; - [k: string]: unknown; + $type: 'site.standard.publication' + basicTheme?: SiteStandardThemeBasic.Main + theme?: $Typed | { $type: string } + description?: string + icon?: BlobRef + name: string + preferences?: Preferences + url: string + [k: string]: unknown } -const hashRecord = "main"; +const hashRecord = 'main' export function isRecord(v: V) { - return is$typed(v, id, hashRecord); + return is$typed(v, id, hashRecord) } export function validateRecord(v: V) { - return validate(v, id, hashRecord, true); + return validate(v, id, hashRecord, true) } export interface Preferences { - $type?: "site.standard.publication#preferences"; - showInDiscover: boolean; - showComments: boolean; - showMentions: boolean; - showPrevNext: boolean; - showRecommends: boolean; + $type?: 'site.standard.publication#preferences' + showInDiscover: boolean + showComments: boolean + showMentions: boolean + showPrevNext: boolean + showRecommends: boolean } -const hashPreferences = "preferences"; +const hashPreferences = 'preferences' export function isPreferences(v: V) { - return is$typed(v, id, hashPreferences); + return is$typed(v, id, hashPreferences) } export function validatePreferences(v: V) { - return validate(v, id, hashPreferences); + return validate(v, id, hashPreferences) } diff --git a/lexicons/pub/leaflet/document.json b/lexicons/pub/leaflet/document.json index 2e714226..7ab00088 100644 --- a/lexicons/pub/leaflet/document.json +++ b/lexicons/pub/leaflet/document.json @@ -46,6 +46,10 @@ "type": "ref", "ref": "pub.leaflet.publication#theme" }, + "preferences": { + "type": "ref", + "ref": "pub.leaflet.publication#preferences" + }, "tags": { "type": "array", "items": { diff --git a/lexicons/site/standard/document.json b/lexicons/site/standard/document.json index 433f19dc..8305dd47 100644 --- a/lexicons/site/standard/document.json +++ b/lexicons/site/standard/document.json @@ -57,6 +57,11 @@ "maxLength": 5000, "type": "string" }, + "preferences": { + "type": "union", + "refs": ["pub.leaflet.publication#preferences"], + "closed": false + }, "updatedAt": { "format": "datetime", "type": "string" diff --git a/lexicons/src/document.ts b/lexicons/src/document.ts index f1beabff..15e3f00b 100644 --- a/lexicons/src/document.ts +++ b/lexicons/src/document.ts @@ -23,6 +23,10 @@ export const PubLeafletDocument: LexiconDoc = { publication: { type: "string", format: "at-uri" }, author: { type: "string", format: "at-identifier" }, theme: { type: "ref", ref: "pub.leaflet.publication#theme" }, + preferences: { + type: "ref", + ref: "pub.leaflet.publication#preferences", + }, tags: { type: "array", items: { type: "string", maxLength: 50 } }, coverImage: { type: "blob", diff --git a/lexicons/src/normalize.ts b/lexicons/src/normalize.ts index 4f7294c7..9e8755ec 100644 --- a/lexicons/src/normalize.ts +++ b/lexicons/src/normalize.ts @@ -28,6 +28,7 @@ import { AtUri } from "@atproto/syntax"; export type NormalizedDocument = SiteStandardDocument.Record & { // Keep the original theme for components that need leaflet-specific styling theme?: PubLeafletPublication.Theme; + preferences?: SiteStandardPublication.Preferences; }; // Normalized publication type - uses the generated site.standard.publication type @@ -169,9 +170,13 @@ export function normalizeDocument( // Pass through site.standard records directly (theme is already in correct format if present) if (isStandardDocument(record)) { + const preferences = record.preferences as + | SiteStandardPublication.Preferences + | undefined; return { ...record, theme: record.theme, + preferences, } as NormalizedDocument; } @@ -198,6 +203,10 @@ export function normalizeDocument( } : undefined; + // Extract preferences if present (available after lexicon rebuild) + const leafletPrefs = (record as Record) + .preferences as SiteStandardPublication.Preferences | undefined; + return { $type: "site.standard.document", title: record.title, @@ -210,6 +219,7 @@ export function normalizeDocument( bskyPostRef: record.postRef, content, theme: record.theme, + preferences: leafletPrefs, }; } diff --git a/package-lock.json b/package-lock.json index bffdd153..9fb9d0cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@atproto/oauth-client-node": "^0.3.8", "@atproto/sync": "^0.1.34", "@atproto/syntax": "^0.3.3", - "@atproto/tap": "^0.1.1", "@atproto/xrpc": "^0.7.5", "@atproto/xrpc-server": "^0.9.5", "@hono/node-server": "^1.14.3", @@ -396,92 +395,6 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, - "node_modules/@atproto/lex": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@atproto/lex/-/lex-0.0.9.tgz", - "integrity": "sha512-o6gauf1lz0iyzJR0rqSj4VHOrO+Nt8+/iPb0KPojw1ieXk13zOSTSxotAoDzO/dP6y8Ey5jxwuCQGuzab/4XnQ==", - "license": "MIT", - "dependencies": { - "@atproto/lex-builder": "0.0.9", - "@atproto/lex-client": "0.0.7", - "@atproto/lex-data": "0.0.6", - "@atproto/lex-installer": "0.0.9", - "@atproto/lex-json": "0.0.6", - "@atproto/lex-schema": "0.0.7", - "tslib": "^2.8.1", - "yargs": "^17.0.0" - }, - "bin": { - "lex": "bin/lex", - "ts-lex": "bin/lex" - } - }, - "node_modules/@atproto/lex-builder": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@atproto/lex-builder/-/lex-builder-0.0.9.tgz", - "integrity": "sha512-buOFk1JpuW3twI7To7f/67zQQ1NulLHf/oasH/kTOPUAd0dNyeAa13t9eRSVGbwi0BcZYxRxBm0QzPmdLKyuyw==", - "license": "MIT", - "dependencies": { - "@atproto/lex-document": "0.0.8", - "@atproto/lex-schema": "0.0.7", - "prettier": "^3.2.5", - "ts-morph": "^27.0.0", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-builder/node_modules/@ts-morph/common": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", - "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", - "license": "MIT", - "dependencies": { - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1", - "tinyglobby": "^0.2.14" - } - }, - "node_modules/@atproto/lex-builder/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@atproto/lex-builder/node_modules/ts-morph": { - "version": "27.0.2", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", - "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.28.1", - "code-block-writer": "^13.0.3" - } - }, - "node_modules/@atproto/lex-cbor": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.6.tgz", - "integrity": "sha512-lee2T00owDy3I1plRHuURT6f98NIpYZZr2wXa5pJZz5JzefZ+nv8gJ2V70C2f+jmSG+5S9NTIy4uJw94vaHf4A==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "0.0.6", - "multiformats": "^9.9.0", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-cbor/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, "node_modules/@atproto/lex-cli": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.5.tgz", @@ -515,149 +428,6 @@ "tslib": "^2.8.1" } }, - "node_modules/@atproto/lex-client": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@atproto/lex-client/-/lex-client-0.0.7.tgz", - "integrity": "sha512-ofUz3yXJ0nN/M9aqqF2ZUL/4D1wWT1P4popCfV3OEDsDrtWofMflYPFz1IWuyPa2e83paaEHRhaw3bZEhgXH1w==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "0.0.6", - "@atproto/lex-json": "0.0.6", - "@atproto/lex-schema": "0.0.7", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-data": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.6.tgz", - "integrity": "sha512-MBNB4ghRJQzuXK1zlUPljpPbQcF1LZ5dzxy274KqPt4p3uPuRw0mHjgcCoWzRUNBQC685WMQR4IN9DHtsnG57A==", - "license": "MIT", - "dependencies": { - "@atproto/syntax": "0.4.2", - "multiformats": "^9.9.0", - "tslib": "^2.8.1", - "uint8arrays": "3.0.0", - "unicode-segmenter": "^0.14.0" - } - }, - "node_modules/@atproto/lex-data/node_modules/@atproto/syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", - "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", - "license": "MIT" - }, - "node_modules/@atproto/lex-data/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/@atproto/lex-document": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@atproto/lex-document/-/lex-document-0.0.8.tgz", - "integrity": "sha512-p3l5h96Hx0vxUwbO/eas6x5h2vU0JVN1a/ktX4k3PlK9YLXfWMFsv+RdVwVZom8o0irHwlcyh1D/cY0PyUojDA==", - "license": "MIT", - "dependencies": { - "@atproto/lex-schema": "0.0.7", - "core-js": "^3", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-installer": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@atproto/lex-installer/-/lex-installer-0.0.9.tgz", - "integrity": "sha512-zEeIeSaSCb3j+zNsqqMY7+X5FO6fxy/MafaCEj42KsXQHNcobuygZsnG/0fxMj/kMvhjrNUCp/w9PyOMwx4hQg==", - "license": "MIT", - "dependencies": { - "@atproto/lex-builder": "0.0.9", - "@atproto/lex-cbor": "0.0.6", - "@atproto/lex-data": "0.0.6", - "@atproto/lex-document": "0.0.8", - "@atproto/lex-resolver": "0.0.8", - "@atproto/lex-schema": "0.0.7", - "@atproto/syntax": "0.4.2", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-installer/node_modules/@atproto/syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", - "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", - "license": "MIT" - }, - "node_modules/@atproto/lex-json": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.6.tgz", - "integrity": "sha512-EILnN5cditPvf+PCNjXt7reMuzjugxAL1fpSzmzJbEMGMUwxOf5pPWxRsaA/M3Boip4NQZ+6DVrPOGUMlnqceg==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "0.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-resolver": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@atproto/lex-resolver/-/lex-resolver-0.0.8.tgz", - "integrity": "sha512-4hXT560+k5BIttouuhXOr+UkhAuFvvkJaVdqYb8vx2Ez7eHPiZ+yWkUK6FKpyGsx2whHkJzgleEA6DNWtdDlWA==", - "license": "MIT", - "dependencies": { - "@atproto-labs/did-resolver": "0.2.5", - "@atproto/crypto": "0.4.5", - "@atproto/lex-client": "0.0.7", - "@atproto/lex-data": "0.0.6", - "@atproto/lex-document": "0.0.8", - "@atproto/lex-schema": "0.0.7", - "@atproto/repo": "0.8.12", - "@atproto/syntax": "0.4.2", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-resolver/node_modules/@atproto-labs/did-resolver": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.5.tgz", - "integrity": "sha512-he7EC6OMSifNs01a4RT9mta/yYitoKDzlK9ty2TFV5Uj/+HpB4vYMRdIDFrRW0Hcsehy90E2t/dw0t7361MEKQ==", - "license": "MIT", - "dependencies": { - "@atproto-labs/fetch": "0.2.3", - "@atproto-labs/pipe": "0.1.1", - "@atproto-labs/simple-store": "0.3.0", - "@atproto-labs/simple-store-memory": "0.1.4", - "@atproto/did": "0.2.4", - "zod": "^3.23.8" - } - }, - "node_modules/@atproto/lex-resolver/node_modules/@atproto/did": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.4.tgz", - "integrity": "sha512-nxNiCgXeo7pfjojq9fpfZxCO0X0xUipNVKW+AHNZwQKiUDt6zYL0VXEfm8HBUwQOCmKvj2pRRSM1Cur+tUWk3g==", - "license": "MIT", - "dependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@atproto/lex-resolver/node_modules/@atproto/syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", - "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", - "license": "MIT" - }, - "node_modules/@atproto/lex-schema": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@atproto/lex-schema/-/lex-schema-0.0.7.tgz", - "integrity": "sha512-/7HkTUsnP1rlzmVE6nnY0kl/hydL/W8V29V8BhFwdAvdDKpYcdRgzzsMe38LAt+ZOjHknRCZDIKGsbQMSbJErw==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "0.0.6", - "@atproto/syntax": "0.4.2", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/lex-schema/node_modules/@atproto/syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", - "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", - "license": "MIT" - }, "node_modules/@atproto/lexicon": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.1.tgz", @@ -848,135 +618,6 @@ "integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==", "license": "MIT" }, - "node_modules/@atproto/tap": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@atproto/tap/-/tap-0.1.1.tgz", - "integrity": "sha512-gW4NzLOxj74TzaDOVzzzt5kl2PdC0r75XkIpYpI5xobwCfsc/DmVtwpuSw1fW9gr4Vzk2Q90S9UE4ifAFl2gyA==", - "license": "MIT", - "dependencies": { - "@atproto/common": "^0.5.6", - "@atproto/lex": "^0.0.9", - "@atproto/syntax": "^0.4.2", - "@atproto/ws-client": "^0.0.4", - "ws": "^8.12.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18.7.0" - } - }, - "node_modules/@atproto/tap/node_modules/@atproto/common": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.10.tgz", - "integrity": "sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g==", - "license": "MIT", - "dependencies": { - "@atproto/common-web": "^0.4.15", - "@atproto/lex-cbor": "^0.0.10", - "@atproto/lex-data": "^0.0.10", - "iso-datestring-validator": "^2.2.2", - "multiformats": "^9.9.0", - "pino": "^8.21.0" - }, - "engines": { - "node": ">=18.7.0" - } - }, - "node_modules/@atproto/tap/node_modules/@atproto/lex-cbor": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz", - "integrity": "sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "^0.0.10", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/tap/node_modules/@atproto/lex-data": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.10.tgz", - "integrity": "sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.9.0", - "tslib": "^2.8.1", - "uint8arrays": "3.0.0", - "unicode-segmenter": "^0.14.0" - } - }, - "node_modules/@atproto/tap/node_modules/@atproto/syntax": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.3.tgz", - "integrity": "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/tap/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/@atproto/ws-client": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@atproto/ws-client/-/ws-client-0.0.4.tgz", - "integrity": "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg==", - "license": "MIT", - "dependencies": { - "@atproto/common": "^0.5.3", - "ws": "^8.12.0" - }, - "engines": { - "node": ">=18.7.0" - } - }, - "node_modules/@atproto/ws-client/node_modules/@atproto/common": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.10.tgz", - "integrity": "sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g==", - "license": "MIT", - "dependencies": { - "@atproto/common-web": "^0.4.15", - "@atproto/lex-cbor": "^0.0.10", - "@atproto/lex-data": "^0.0.10", - "iso-datestring-validator": "^2.2.2", - "multiformats": "^9.9.0", - "pino": "^8.21.0" - }, - "engines": { - "node": ">=18.7.0" - } - }, - "node_modules/@atproto/ws-client/node_modules/@atproto/lex-cbor": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz", - "integrity": "sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==", - "license": "MIT", - "dependencies": { - "@atproto/lex-data": "^0.0.10", - "tslib": "^2.8.1" - } - }, - "node_modules/@atproto/ws-client/node_modules/@atproto/lex-data": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.10.tgz", - "integrity": "sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.9.0", - "tslib": "^2.8.1", - "uint8arrays": "3.0.0", - "unicode-segmenter": "^0.14.0" - } - }, - "node_modules/@atproto/ws-client/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, "node_modules/@atproto/xrpc": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz", @@ -3052,27 +2693,6 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -9631,6 +9251,7 @@ "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, "license": "MIT" }, "node_modules/collapse-white-space": { @@ -9739,17 +9360,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -16142,6 +15752,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, "license": "MIT" }, "node_modules/path-exists": { @@ -16424,6 +16035,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -18520,6 +18132,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -18536,6 +18149,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -18553,6 +18167,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 5e9e2ee4..4f9c5397 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@atproto/oauth-client-node": "^0.3.8", "@atproto/sync": "^0.1.34", "@atproto/syntax": "^0.3.3", - "@atproto/tap": "^0.1.1", "@atproto/xrpc": "^0.7.5", "@atproto/xrpc-server": "^0.9.5", "@hono/node-server": "^1.14.3", diff --git a/src/replicache/mutations.ts b/src/replicache/mutations.ts index d8bf93c6..40db9f73 100644 --- a/src/replicache/mutations.ts +++ b/src/replicache/mutations.ts @@ -659,6 +659,11 @@ const updatePublicationDraft: Mutation<{ tags?: string[]; cover_image?: string | null; localPublishedAt?: string | null; + preferences?: { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null; }> = async (args, ctx) => { await ctx.runOnServer(async (serverCtx) => { console.log("updating"); @@ -667,11 +672,17 @@ const updatePublicationDraft: Mutation<{ title?: string; tags?: string[]; cover_image?: string | null; + preferences?: { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + } | null; } = {}; if (args.description !== undefined) updates.description = args.description; if (args.title !== undefined) updates.title = args.title; if (args.tags !== undefined) updates.tags = args.tags; if (args.cover_image !== undefined) updates.cover_image = args.cover_image; + if (args.preferences !== undefined) updates.preferences = args.preferences; if (Object.keys(updates).length > 0) { // First try to update leaflets_in_publications (for publications) @@ -700,6 +711,8 @@ const updatePublicationDraft: Mutation<{ await tx.set("publication_cover_image", args.cover_image); if (args.localPublishedAt !== undefined) await tx.set("publication_local_published_at", args.localPublishedAt); + if (args.preferences !== undefined) + await tx.set("post_preferences", args.preferences); }); }; diff --git a/src/utils/mergePreferences.ts b/src/utils/mergePreferences.ts new file mode 100644 index 00000000..c2e75a87 --- /dev/null +++ b/src/utils/mergePreferences.ts @@ -0,0 +1,24 @@ +type PreferencesInput = { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + showPrevNext?: boolean; +} | null; + +export function mergePreferences( + documentPrefs?: PreferencesInput, + publicationPrefs?: PreferencesInput, +): { + showComments?: boolean; + showMentions?: boolean; + showRecommends?: boolean; + showPrevNext?: boolean; +} { + return { + showComments: documentPrefs?.showComments ?? publicationPrefs?.showComments, + showMentions: documentPrefs?.showMentions ?? publicationPrefs?.showMentions, + showRecommends: + documentPrefs?.showRecommends ?? publicationPrefs?.showRecommends, + showPrevNext: publicationPrefs?.showPrevNext, + }; +} diff --git a/supabase/database.types.ts b/supabase/database.types.ts index edbfe86e..6153540b 100644 --- a/supabase/database.types.ts +++ b/supabase/database.types.ts @@ -589,6 +589,7 @@ export type Database = { description: string doc: string | null leaflet: string + preferences: Json | null publication: string tags: string[] | null title: string @@ -599,6 +600,7 @@ export type Database = { description?: string doc?: string | null leaflet: string + preferences?: Json | null publication: string tags?: string[] | null title?: string @@ -609,6 +611,7 @@ export type Database = { description?: string doc?: string | null leaflet?: string + preferences?: Json | null publication?: string tags?: string[] | null title?: string @@ -645,6 +648,7 @@ export type Database = { description: string document: string leaflet: string + preferences: Json | null tags: string[] | null title: string } @@ -655,6 +659,7 @@ export type Database = { description?: string document: string leaflet: string + preferences?: Json | null tags?: string[] | null title?: string } @@ -665,6 +670,7 @@ export type Database = { description?: string document?: string leaflet?: string + preferences?: Json | null tags?: string[] | null title?: string } diff --git a/supabase/migrations/20260208000000_add_preferences_to_drafts.sql b/supabase/migrations/20260208000000_add_preferences_to_drafts.sql new file mode 100644 index 00000000..e54b639a --- /dev/null +++ b/supabase/migrations/20260208000000_add_preferences_to_drafts.sql @@ -0,0 +1,2 @@ +ALTER TABLE leaflets_in_publications ADD COLUMN preferences jsonb; +ALTER TABLE leaflets_to_documents ADD COLUMN preferences jsonb; -- 2.51.2