tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
a tool for shared writing and social publishing
284
fork
atom
overview
issues
28
pulls
pipelines
add per post interactions
awarm.space
5 days ago
dbf7cf24
8ae93e16
+356
-491
26 changed files
expand all
collapse all
unified
split
actions
publishToPublication.ts
app
[leaflet_id]
Sidebar.tsx
actions
PublishButton.tsx
publish
PublishPost.tsx
api
rpc
[command]
pull.ts
lish
[did]
[publication]
[rkey]
DocumentPageRenderer.tsx
PostPages.tsx
components
Canvas.tsx
Pages
PublicationMetadata.tsx
PostListing.tsx
PostSettings.tsx
lexicons
api
lexicons.ts
types
pub
leaflet
document.ts
publication.ts
site
standard
document.ts
publication.ts
pub
leaflet
document.json
site
standard
document.json
src
document.ts
normalize.ts
package-lock.json
package.json
src
replicache
mutations.ts
utils
mergePreferences.ts
supabase
database.types.ts
migrations
20260208000000_add_preferences_to_drafts.sql
+21
actions/publishToPublication.ts
···
78
cover_image,
79
entitiesToDelete,
80
publishedAt,
0
81
}: {
82
root_entity: string;
83
publication_uri?: string;
···
88
cover_image?: string | null;
89
entitiesToDelete?: string[];
90
publishedAt?: string;
0
0
0
0
0
91
}): Promise<PublishResult> {
92
let identity = await getIdentityData();
93
if (!identity || !identity.atp_did) {
···
175
};
176
}
177
0
0
0
178
// Extract theme for standalone documents (not for publications)
179
let theme: PubLeafletPublication.Theme | undefined;
180
if (!publication_uri) {
···
245
...(coverImageBlob && { coverImage: coverImageBlob }),
246
// Include theme for standalone documents (not for publication documents)
247
...(!publication_uri && theme && { theme }),
0
0
0
0
0
0
248
content: {
249
$type: "pub.leaflet.content" as const,
250
pages: pagesArray,
···
257
author: credentialSession.did!,
258
...(publication_uri && { publication: publication_uri }),
259
...(theme && { theme }),
0
0
0
0
0
0
260
title: title || "Untitled",
261
description: description || "",
262
...(tags !== undefined && { tags }),
···
78
cover_image,
79
entitiesToDelete,
80
publishedAt,
81
+
postPreferences,
82
}: {
83
root_entity: string;
84
publication_uri?: string;
···
89
cover_image?: string | null;
90
entitiesToDelete?: string[];
91
publishedAt?: string;
92
+
postPreferences?: {
93
+
showComments?: boolean;
94
+
showMentions?: boolean;
95
+
showRecommends?: boolean;
96
+
} | null;
97
}): Promise<PublishResult> {
98
let identity = await getIdentityData();
99
if (!identity || !identity.atp_did) {
···
181
};
182
}
183
184
+
// Resolve preferences: explicit param > draft DB value
185
+
const preferences = postPreferences ?? draft?.preferences;
186
+
187
// Extract theme for standalone documents (not for publications)
188
let theme: PubLeafletPublication.Theme | undefined;
189
if (!publication_uri) {
···
254
...(coverImageBlob && { coverImage: coverImageBlob }),
255
// Include theme for standalone documents (not for publication documents)
256
...(!publication_uri && theme && { theme }),
257
+
...(preferences && {
258
+
preferences: {
259
+
$type: "pub.leaflet.publication#preferences" as const,
260
+
...preferences,
261
+
},
262
+
}),
263
content: {
264
$type: "pub.leaflet.content" as const,
265
pages: pagesArray,
···
272
author: credentialSession.did!,
273
...(publication_uri && { publication: publication_uri }),
274
...(theme && { theme }),
275
+
...(preferences && {
276
+
preferences: {
277
+
$type: "pub.leaflet.publication#preferences" as const,
278
+
...preferences,
279
+
},
280
+
}),
281
title: title || "Untitled",
282
description: description || "",
283
...(tags !== undefined && { tags }),
+2
app/[leaflet_id]/Sidebar.tsx
···
8
import { ShareOptions } from "app/[leaflet_id]/actions/ShareOptions";
9
import { ThemePopover } from "components/ThemeManager/ThemeSetter";
10
import { PublishButton } from "./actions/PublishButton";
0
11
import { Watermark } from "components/Watermark";
12
import { BackToPubButton } from "./actions/BackToPubButton";
13
import { useIdentityData } from "components/IdentityProvider";
···
30
<Sidebar>
31
<PublishButton entityID={rootEntity} />
32
<ShareOptions />
0
33
<ThemePopover entityID={rootEntity} />
34
<HelpButton />
35
<hr className="text-border" />
···
8
import { ShareOptions } from "app/[leaflet_id]/actions/ShareOptions";
9
import { ThemePopover } from "components/ThemeManager/ThemeSetter";
10
import { PublishButton } from "./actions/PublishButton";
11
+
import { PostSettings } from "components/PostSettings";
12
import { Watermark } from "components/Watermark";
13
import { BackToPubButton } from "./actions/BackToPubButton";
14
import { useIdentityData } from "components/IdentityProvider";
···
31
<Sidebar>
32
<PublishButton entityID={rootEntity} />
33
<ShareOptions />
34
+
<PostSettings />
35
<ThemePopover entityID={rootEntity} />
36
<HelpButton />
37
<hr className="text-border" />
+10
app/[leaflet_id]/actions/PublishButton.tsx
···
96
tx.get<string | null>("publication_cover_image"),
97
);
98
0
0
0
0
0
0
0
0
0
99
// Get local published at from Replicache (session-only state, not persisted to DB)
100
let publishedAt = useLocalPublishedAt((s) =>
101
pub?.doc ? s[pub?.doc] : undefined,
···
118
tags: currentTags,
119
cover_image: coverImage,
120
publishedAt: publishedAt?.toISOString(),
0
121
});
122
setIsLoading(false);
123
mutate();
···
96
tx.get<string | null>("publication_cover_image"),
97
);
98
99
+
// Get post preferences from Replicache state
100
+
let postPreferences = useSubscribe(rep, (tx) =>
101
+
tx.get<{
102
+
showComments?: boolean;
103
+
showMentions?: boolean;
104
+
showRecommends?: boolean;
105
+
} | null>("post_preferences"),
106
+
);
107
+
108
// Get local published at from Replicache (session-only state, not persisted to DB)
109
let publishedAt = useLocalPublishedAt((s) =>
110
pub?.doc ? s[pub?.doc] : undefined,
···
127
tags: currentTags,
128
cover_image: coverImage,
129
publishedAt: publishedAt?.toISOString(),
130
+
postPreferences,
131
});
132
setIsLoading(false);
133
mutate();
+10
app/[leaflet_id]/publish/PublishPost.tsx
···
91
tx.get<string | null>("publication_cover_image"),
92
);
93
0
0
0
0
0
0
0
0
0
94
// Use Replicache tags only when we have a draft
95
const currentTags = props.hasDraft
96
? Array.isArray(replicacheTags)
···
124
cover_image: replicacheCoverImage,
125
entitiesToDelete: props.entitiesToDelete,
126
publishedAt: localPublishedAt?.toISOString() || new Date().toISOString(),
0
127
});
128
129
if (!result.success) {
···
91
tx.get<string | null>("publication_cover_image"),
92
);
93
94
+
// Get post preferences from Replicache state
95
+
let postPreferences = useSubscribe(rep, (tx) =>
96
+
tx.get<{
97
+
showComments?: boolean;
98
+
showMentions?: boolean;
99
+
showRecommends?: boolean;
100
+
} | null>("post_preferences"),
101
+
);
102
+
103
// Use Replicache tags only when we have a draft
104
const currentTags = props.hasDraft
105
? Array.isArray(replicacheTags)
···
133
cover_image: replicacheCoverImage,
134
entitiesToDelete: props.entitiesToDelete,
135
publishedAt: localPublishedAt?.toISOString() || new Date().toISOString(),
136
+
postPreferences,
137
});
138
139
if (!result.success) {
+7
app/api/rpc/[command]/pull.ts
···
9
import type { Attribute } from "src/replicache/attributes";
10
import { makeRoute } from "../lib";
11
import type { Env } from "./route";
0
12
13
// First define the sub-types for V0 and V1 requests
14
const pullRequestV0 = z.object({
···
75
title: string;
76
tags: string[];
77
cover_image: string | null;
0
78
}[];
79
let pub_patch = publication_data?.[0]
80
? [
···
97
op: "put",
98
key: "publication_cover_image",
99
value: publication_data[0].cover_image || null,
0
0
0
0
0
100
},
101
]
102
: [];
···
9
import type { Attribute } from "src/replicache/attributes";
10
import { makeRoute } from "../lib";
11
import type { Env } from "./route";
12
+
import type { Json } from "supabase/database.types";
13
14
// First define the sub-types for V0 and V1 requests
15
const pullRequestV0 = z.object({
···
76
title: string;
77
tags: string[];
78
cover_image: string | null;
79
+
preferences: Json | null;
80
}[];
81
let pub_patch = publication_data?.[0]
82
? [
···
99
op: "put",
100
key: "publication_cover_image",
101
value: publication_data[0].cover_image || null,
102
+
},
103
+
{
104
+
op: "put",
105
+
key: "post_preferences",
106
+
value: publication_data[0].preferences || null,
107
},
108
]
109
: [];
+2
-1
app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx
···
21
} from "src/utils/normalizeRecords";
22
import { DocumentProvider } from "contexts/DocumentContext";
23
import { LeafletContentProvider } from "contexts/LeafletContentContext";
0
24
25
export async function DocumentPageRenderer({
26
did,
···
133
<LeafletLayout>
134
<PostPages
135
document_uri={document.uri}
136
-
preferences={pubRecord?.preferences || {}}
137
pubRecord={pubRecord}
138
profile={JSON.parse(JSON.stringify(profile.data))}
139
document={document}
···
21
} from "src/utils/normalizeRecords";
22
import { DocumentProvider } from "contexts/DocumentContext";
23
import { LeafletContentProvider } from "contexts/LeafletContentContext";
24
+
import { mergePreferences } from "src/utils/mergePreferences";
25
26
export async function DocumentPageRenderer({
27
did,
···
134
<LeafletLayout>
135
<PostPages
136
document_uri={document.uri}
137
+
preferences={mergePreferences(record?.preferences, pubRecord?.preferences)}
138
pubRecord={pubRecord}
139
profile={JSON.parse(JSON.stringify(profile.data))}
140
document={document}
+4
-4
app/lish/[did]/[publication]/[rkey]/PostPages.tsx
···
295
showPageBackground={pubRecord?.theme?.showPageBackground}
296
document_uri={document.uri}
297
comments={
298
-
pubRecord?.preferences?.showComments === false
299
? []
300
: document.comments_on_documents
301
}
302
quotesAndMentions={
303
-
pubRecord?.preferences?.showMentions === false
304
? []
305
: quotesAndMentions
306
}
···
387
pageId={page.id}
388
document_uri={document.uri}
389
comments={
390
-
pubRecord?.preferences?.showComments === false
391
? []
392
: document.comments_on_documents
393
}
394
quotesAndMentions={
395
-
pubRecord?.preferences?.showMentions === false
396
? []
397
: quotesAndMentions
398
}
···
295
showPageBackground={pubRecord?.theme?.showPageBackground}
296
document_uri={document.uri}
297
comments={
298
+
preferences.showComments === false
299
? []
300
: document.comments_on_documents
301
}
302
quotesAndMentions={
303
+
preferences.showMentions === false
304
? []
305
: quotesAndMentions
306
}
···
387
pageId={page.id}
388
document_uri={document.uri}
389
comments={
390
+
preferences.showComments === false
391
? []
392
: document.comments_on_documents
393
}
394
quotesAndMentions={
395
+
preferences.showMentions === false
396
? []
397
: quotesAndMentions
398
}
+17
-4
components/Canvas.tsx
···
24
import { useHandleCanvasDrop } from "./Blocks/useHandleCanvasDrop";
25
import { useBlockMouseHandlers } from "./Blocks/useBlockMouseHandlers";
26
import { RecommendTinyEmpty } from "./Icons/RecommendTiny";
0
0
27
28
export function Canvas(props: {
29
entityID: string;
···
164
165
const CanvasMetadata = (props: { isSubpage: boolean | undefined }) => {
166
let { data: pub, normalizedPublication } = useLeafletPublicationData();
0
0
0
0
0
0
0
0
167
if (!pub || !pub.publications) return null;
168
169
if (!normalizedPublication) return null;
170
-
let showComments = normalizedPublication.preferences?.showComments !== false;
171
-
let showMentions = normalizedPublication.preferences?.showMentions !== false;
172
-
let showRecommends =
173
-
normalizedPublication.preferences?.showRecommends !== false;
0
0
0
174
175
return (
176
<div className="flex flex-row gap-3 items-center absolute top-6 right-3 sm:top-4 sm:right-4 bg-bg-page border-border-light rounded-md px-2 py-1 h-fit z-20">
···
24
import { useHandleCanvasDrop } from "./Blocks/useHandleCanvasDrop";
25
import { useBlockMouseHandlers } from "./Blocks/useBlockMouseHandlers";
26
import { RecommendTinyEmpty } from "./Icons/RecommendTiny";
27
+
import { useSubscribe } from "src/replicache/useSubscribe";
28
+
import { mergePreferences } from "src/utils/mergePreferences";
29
30
export function Canvas(props: {
31
entityID: string;
···
166
167
const CanvasMetadata = (props: { isSubpage: boolean | undefined }) => {
168
let { data: pub, normalizedPublication } = useLeafletPublicationData();
169
+
let { rep } = useReplicache();
170
+
let postPreferences = useSubscribe(rep, (tx) =>
171
+
tx.get<{
172
+
showComments?: boolean;
173
+
showMentions?: boolean;
174
+
showRecommends?: boolean;
175
+
} | null>("post_preferences"),
176
+
);
177
if (!pub || !pub.publications) return null;
178
179
if (!normalizedPublication) return null;
180
+
let merged = mergePreferences(
181
+
postPreferences || undefined,
182
+
normalizedPublication.preferences,
183
+
);
184
+
let showComments = merged.showComments !== false;
185
+
let showMentions = merged.showMentions !== false;
186
+
let showRecommends = merged.showRecommends !== false;
187
188
return (
189
<div className="flex flex-row gap-3 items-center absolute top-6 right-3 sm:top-4 sm:right-4 bg-bg-page border-border-light rounded-md px-2 py-1 h-fit z-20">
+18
-7
components/Pages/PublicationMetadata.tsx
···
21
import { PostHeaderLayout } from "app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader";
22
import { Backdater } from "./Backdater";
23
import { RecommendTinyEmpty } from "components/Icons/RecommendTiny";
0
24
25
export const PublicationMetadata = (props: { noInteractions?: boolean }) => {
26
let { rep } = useReplicache();
···
33
let title = useSubscribe(rep, (tx) => tx.get<string>("publication_title"));
34
let description = useSubscribe(rep, (tx) =>
35
tx.get<string>("publication_description"),
0
0
0
0
0
0
0
0
0
0
0
36
);
37
let publishedAt = normalizedDocument?.publishedAt;
38
···
121
)}
122
{!props.noInteractions && (
123
<div className="flex gap-2 text-border items-center">
124
-
{normalizedPublication?.preferences?.showRecommends !== false && (
125
<div className="flex gap-1 items-center">
126
<RecommendTinyEmpty />—
127
</div>
128
)}
129
130
-
{normalizedPublication?.preferences?.showMentions !== false && (
131
<div className="flex gap-1 items-center">
132
<QuoteTiny />—
133
</div>
134
)}
135
-
{normalizedPublication?.preferences?.showComments !== false && (
136
<div className="flex gap-1 items-center">
137
<CommentTiny />—
138
</div>
139
)}
140
{tags && (
141
<>
142
-
{normalizedPublication?.preferences?.showRecommends !==
143
-
false ||
144
-
normalizedPublication?.preferences?.showMentions !== false ||
145
-
normalizedPublication?.preferences?.showComments !== false ? (
146
<Separator classname="h-4!" />
147
) : null}
148
<AddTags />
···
21
import { PostHeaderLayout } from "app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader";
22
import { Backdater } from "./Backdater";
23
import { RecommendTinyEmpty } from "components/Icons/RecommendTiny";
24
+
import { mergePreferences } from "src/utils/mergePreferences";
25
26
export const PublicationMetadata = (props: { noInteractions?: boolean }) => {
27
let { rep } = useReplicache();
···
34
let title = useSubscribe(rep, (tx) => tx.get<string>("publication_title"));
35
let description = useSubscribe(rep, (tx) =>
36
tx.get<string>("publication_description"),
37
+
);
38
+
let postPreferences = useSubscribe(rep, (tx) =>
39
+
tx.get<{
40
+
showComments?: boolean;
41
+
showMentions?: boolean;
42
+
showRecommends?: boolean;
43
+
} | null>("post_preferences"),
44
+
);
45
+
let merged = mergePreferences(
46
+
postPreferences || undefined,
47
+
normalizedPublication?.preferences,
48
);
49
let publishedAt = normalizedDocument?.publishedAt;
50
···
133
)}
134
{!props.noInteractions && (
135
<div className="flex gap-2 text-border items-center">
136
+
{merged.showRecommends !== false && (
137
<div className="flex gap-1 items-center">
138
<RecommendTinyEmpty />—
139
</div>
140
)}
141
142
+
{merged.showMentions !== false && (
143
<div className="flex gap-1 items-center">
144
<QuoteTiny />—
145
</div>
146
)}
147
+
{merged.showComments !== false && (
148
<div className="flex gap-1 items-center">
149
<CommentTiny />—
150
</div>
151
)}
152
{tags && (
153
<>
154
+
{merged.showRecommends !== false ||
155
+
merged.showMentions !== false ||
156
+
merged.showComments !== false ? (
0
157
<Separator classname="h-4!" />
158
) : null}
159
<AddTags />
+7
-6
components/PostListing.tsx
···
17
import Link from "next/link";
18
import { InteractionPreview } from "./InteractionsPreview";
19
import { useLocalizedDate } from "src/hooks/useLocalizedDate";
0
20
21
export const PostListing = (props: Post) => {
22
let pubRecord = props.publication?.pubRecord as
···
48
? pubRecord?.theme?.showPageBackground
49
: postRecord.theme?.showPageBackground ?? true;
50
0
0
51
let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0;
52
let comments =
53
-
pubRecord?.preferences?.showComments === false
54
? 0
55
: props.documents.comments_on_documents?.[0]?.count || 0;
56
let recommends = props.documents.recommends_on_documents?.[0]?.count || 0;
···
109
recommendsCount={recommends}
110
documentUri={props.documents.uri}
111
tags={tags}
112
-
showComments={pubRecord?.preferences?.showComments !== false}
113
-
showMentions={pubRecord?.preferences?.showMentions !== false}
114
-
showRecommends={
115
-
pubRecord?.preferences?.showRecommends !== false
116
-
}
117
share
118
/>
119
</div>
···
17
import Link from "next/link";
18
import { InteractionPreview } from "./InteractionsPreview";
19
import { useLocalizedDate } from "src/hooks/useLocalizedDate";
20
+
import { mergePreferences } from "src/utils/mergePreferences";
21
22
export const PostListing = (props: Post) => {
23
let pubRecord = props.publication?.pubRecord as
···
49
? pubRecord?.theme?.showPageBackground
50
: postRecord.theme?.showPageBackground ?? true;
51
52
+
let mergedPrefs = mergePreferences(postRecord?.preferences, pubRecord?.preferences);
53
+
54
let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0;
55
let comments =
56
+
mergedPrefs.showComments === false
57
? 0
58
: props.documents.comments_on_documents?.[0]?.count || 0;
59
let recommends = props.documents.recommends_on_documents?.[0]?.count || 0;
···
112
recommendsCount={recommends}
113
documentUri={props.documents.uri}
114
tags={tags}
115
+
showComments={mergedPrefs.showComments !== false}
116
+
showMentions={mergedPrefs.showMentions !== false}
117
+
showRecommends={mergedPrefs.showRecommends !== false}
0
0
118
share
119
/>
120
</div>
+96
components/PostSettings.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
"use client";
2
+
3
+
import { ActionButton } from "components/ActionBar/ActionButton";
4
+
import { SettingsSmall } from "components/Icons/SettingsSmall";
5
+
import { Toggle } from "components/Toggle";
6
+
import { Popover } from "components/Popover";
7
+
import { useLeafletPublicationData } from "components/PageSWRDataProvider";
8
+
import { useReplicache } from "src/replicache";
9
+
import { useSubscribe } from "src/replicache/useSubscribe";
10
+
11
+
type PostPreferences = {
12
+
showComments?: boolean;
13
+
showMentions?: boolean;
14
+
showRecommends?: boolean;
15
+
};
16
+
17
+
export function PostSettings() {
18
+
let { data: pub, normalizedPublication } = useLeafletPublicationData();
19
+
let { rep } = useReplicache();
20
+
21
+
let postPreferences = useSubscribe(rep, (tx) =>
22
+
tx.get<PostPreferences | null>("post_preferences"),
23
+
);
24
+
25
+
if (!pub || !pub.publications) return null;
26
+
27
+
let pubPrefs = normalizedPublication?.preferences;
28
+
29
+
let showComments =
30
+
postPreferences?.showComments ?? pubPrefs?.showComments ?? true;
31
+
let showMentions =
32
+
postPreferences?.showMentions ?? pubPrefs?.showMentions ?? true;
33
+
let showRecommends =
34
+
postPreferences?.showRecommends ?? pubPrefs?.showRecommends ?? true;
35
+
36
+
const updatePreference = (
37
+
field: keyof PostPreferences,
38
+
value: boolean,
39
+
) => {
40
+
let current: PostPreferences = postPreferences || {};
41
+
rep?.mutate.updatePublicationDraft({
42
+
preferences: { ...current, [field]: value },
43
+
});
44
+
};
45
+
46
+
return (
47
+
<Popover
48
+
asChild
49
+
side="right"
50
+
align="start"
51
+
className="max-w-xs w-[1000px]"
52
+
trigger={
53
+
<ActionButton
54
+
icon={<SettingsSmall />}
55
+
label="Settings"
56
+
/>
57
+
}
58
+
>
59
+
<div className="text-primary flex flex-col">
60
+
<div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-2 mb-1">
61
+
This Post Settings
62
+
</div>
63
+
<div className="flex flex-col gap-2">
64
+
<Toggle
65
+
toggle={showComments}
66
+
onToggle={() => updatePreference("showComments", !showComments)}
67
+
>
68
+
<div className="font-bold">Show Comments</div>
69
+
</Toggle>
70
+
<Toggle
71
+
toggle={showMentions}
72
+
onToggle={() => updatePreference("showMentions", !showMentions)}
73
+
>
74
+
<div className="flex flex-col justify-start">
75
+
<div className="font-bold">Show Mentions</div>
76
+
<div className="text-tertiary text-sm leading-tight">
77
+
Display a list Bluesky mentions about your post
78
+
</div>
79
+
</div>
80
+
</Toggle>
81
+
<Toggle
82
+
toggle={showRecommends}
83
+
onToggle={() => updatePreference("showRecommends", !showRecommends)}
84
+
>
85
+
<div className="flex flex-col justify-start">
86
+
<div className="font-bold">Show Recommends</div>
87
+
<div className="text-tertiary text-sm leading-tight">
88
+
Allow readers to recommend/like your post
89
+
</div>
90
+
</div>
91
+
</Toggle>
92
+
</div>
93
+
</div>
94
+
</Popover>
95
+
);
96
+
}
+17
lexicons/api/lexicons.ts
···
1469
type: 'ref',
1470
ref: 'lex:pub.leaflet.publication#theme',
1471
},
0
0
0
0
1472
tags: {
1473
type: 'array',
1474
items: {
···
1868
type: 'boolean',
1869
default: true,
1870
},
0
0
0
0
1871
},
1872
},
1873
theme: {
···
2194
maxLength: 5000,
2195
type: 'string',
2196
},
0
0
0
0
0
2197
updatedAt: {
2198
format: 'datetime',
2199
type: 'string',
···
2288
},
2289
showPrevNext: {
2290
default: false,
0
0
0
0
2291
type: 'boolean',
2292
},
2293
},
···
1469
type: 'ref',
1470
ref: 'lex:pub.leaflet.publication#theme',
1471
},
1472
+
preferences: {
1473
+
type: 'ref',
1474
+
ref: 'lex:pub.leaflet.publication#preferences',
1475
+
},
1476
tags: {
1477
type: 'array',
1478
items: {
···
1872
type: 'boolean',
1873
default: true,
1874
},
1875
+
showRecommends: {
1876
+
type: 'boolean',
1877
+
default: true,
1878
+
},
1879
},
1880
},
1881
theme: {
···
2202
maxLength: 5000,
2203
type: 'string',
2204
},
2205
+
preferences: {
2206
+
type: 'union',
2207
+
refs: ['lex:pub.leaflet.publication#preferences'],
2208
+
closed: false,
2209
+
},
2210
updatedAt: {
2211
format: 'datetime',
2212
type: 'string',
···
2301
},
2302
showPrevNext: {
2303
default: false,
2304
+
type: 'boolean',
2305
+
},
2306
+
showRecommends: {
2307
+
default: true,
2308
type: 'boolean',
2309
},
2310
},
+1
lexicons/api/types/pub/leaflet/document.ts
···
23
publication?: string
24
author: string
25
theme?: PubLeafletPublication.Theme
0
26
tags?: string[]
27
coverImage?: BlobRef
28
pages: (
···
23
publication?: string
24
author: string
25
theme?: PubLeafletPublication.Theme
26
+
preferences?: PubLeafletPublication.Preferences
27
tags?: string[]
28
coverImage?: BlobRef
29
pages: (
+40
-44
lexicons/api/types/pub/leaflet/publication.ts
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
-
import { type ValidationResult, BlobRef } from "@atproto/lexicon";
5
-
import { CID } from "multiformats/cid";
6
-
import { validate as _validate } from "../../../lexicons";
7
-
import {
8
-
type $Typed,
9
-
is$typed as _is$typed,
10
-
type OmitKey,
11
-
} from "../../../util";
12
-
import type * as PubLeafletThemeColor from "./theme/color";
13
-
import type * as PubLeafletThemeBackgroundImage from "./theme/backgroundImage";
14
15
const is$typed = _is$typed,
16
-
validate = _validate;
17
-
const id = "pub.leaflet.publication";
18
19
export interface Record {
20
-
$type: "pub.leaflet.publication";
21
-
name: string;
22
-
base_path?: string;
23
-
description?: string;
24
-
icon?: BlobRef;
25
-
theme?: Theme;
26
-
preferences?: Preferences;
27
-
[k: string]: unknown;
28
}
29
30
-
const hashRecord = "main";
31
32
export function isRecord<V>(v: V) {
33
-
return is$typed(v, id, hashRecord);
34
}
35
36
export function validateRecord<V>(v: V) {
37
-
return validate<Record & V>(v, id, hashRecord, true);
38
}
39
40
export interface Preferences {
41
-
$type?: "pub.leaflet.publication#preferences";
42
-
showInDiscover: boolean;
43
-
showComments: boolean;
44
-
showMentions: boolean;
45
-
showPrevNext: boolean;
46
-
showRecommends: boolean;
47
}
48
49
-
const hashPreferences = "preferences";
50
51
export function isPreferences<V>(v: V) {
52
-
return is$typed(v, id, hashPreferences);
53
}
54
55
export function validatePreferences<V>(v: V) {
56
-
return validate<Preferences & V>(v, id, hashPreferences);
57
}
58
59
export interface Theme {
60
-
$type?: "pub.leaflet.publication#theme";
61
backgroundColor?:
62
| $Typed<PubLeafletThemeColor.Rgba>
63
| $Typed<PubLeafletThemeColor.Rgb>
64
-
| { $type: string };
65
-
backgroundImage?: PubLeafletThemeBackgroundImage.Main;
66
-
pageWidth?: number;
67
primary?:
68
| $Typed<PubLeafletThemeColor.Rgba>
69
| $Typed<PubLeafletThemeColor.Rgb>
70
-
| { $type: string };
71
pageBackground?:
72
| $Typed<PubLeafletThemeColor.Rgba>
73
| $Typed<PubLeafletThemeColor.Rgb>
74
-
| { $type: string };
75
-
showPageBackground: boolean;
76
accentBackground?:
77
| $Typed<PubLeafletThemeColor.Rgba>
78
| $Typed<PubLeafletThemeColor.Rgb>
79
-
| { $type: string };
80
accentText?:
81
| $Typed<PubLeafletThemeColor.Rgba>
82
| $Typed<PubLeafletThemeColor.Rgb>
83
-
| { $type: string };
84
}
85
86
-
const hashTheme = "theme";
87
88
export function isTheme<V>(v: V) {
89
-
return is$typed(v, id, hashTheme);
90
}
91
92
export function validateTheme<V>(v: V) {
93
-
return validate<Theme & V>(v, id, hashTheme);
94
}
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { CID } from 'multiformats/cid'
6
+
import { validate as _validate } from '../../../lexicons'
7
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
import type * as PubLeafletThemeColor from './theme/color'
9
+
import type * as PubLeafletThemeBackgroundImage from './theme/backgroundImage'
0
0
0
0
10
11
const is$typed = _is$typed,
12
+
validate = _validate
13
+
const id = 'pub.leaflet.publication'
14
15
export interface Record {
16
+
$type: 'pub.leaflet.publication'
17
+
name: string
18
+
base_path?: string
19
+
description?: string
20
+
icon?: BlobRef
21
+
theme?: Theme
22
+
preferences?: Preferences
23
+
[k: string]: unknown
24
}
25
26
+
const hashRecord = 'main'
27
28
export function isRecord<V>(v: V) {
29
+
return is$typed(v, id, hashRecord)
30
}
31
32
export function validateRecord<V>(v: V) {
33
+
return validate<Record & V>(v, id, hashRecord, true)
34
}
35
36
export interface Preferences {
37
+
$type?: 'pub.leaflet.publication#preferences'
38
+
showInDiscover: boolean
39
+
showComments: boolean
40
+
showMentions: boolean
41
+
showPrevNext: boolean
42
+
showRecommends: boolean
43
}
44
45
+
const hashPreferences = 'preferences'
46
47
export function isPreferences<V>(v: V) {
48
+
return is$typed(v, id, hashPreferences)
49
}
50
51
export function validatePreferences<V>(v: V) {
52
+
return validate<Preferences & V>(v, id, hashPreferences)
53
}
54
55
export interface Theme {
56
+
$type?: 'pub.leaflet.publication#theme'
57
backgroundColor?:
58
| $Typed<PubLeafletThemeColor.Rgba>
59
| $Typed<PubLeafletThemeColor.Rgb>
60
+
| { $type: string }
61
+
backgroundImage?: PubLeafletThemeBackgroundImage.Main
62
+
pageWidth?: number
63
primary?:
64
| $Typed<PubLeafletThemeColor.Rgba>
65
| $Typed<PubLeafletThemeColor.Rgb>
66
+
| { $type: string }
67
pageBackground?:
68
| $Typed<PubLeafletThemeColor.Rgba>
69
| $Typed<PubLeafletThemeColor.Rgb>
70
+
| { $type: string }
71
+
showPageBackground: boolean
72
accentBackground?:
73
| $Typed<PubLeafletThemeColor.Rgba>
74
| $Typed<PubLeafletThemeColor.Rgb>
75
+
| { $type: string }
76
accentText?:
77
| $Typed<PubLeafletThemeColor.Rgba>
78
| $Typed<PubLeafletThemeColor.Rgb>
79
+
| { $type: string }
80
}
81
82
+
const hashTheme = 'theme'
83
84
export function isTheme<V>(v: V) {
85
+
return is$typed(v, id, hashTheme)
86
}
87
88
export function validateTheme<V>(v: V) {
89
+
return validate<Theme & V>(v, id, hashTheme)
90
}
+1
lexicons/api/types/site/standard/document.ts
···
28
textContent?: string
29
theme?: PubLeafletPublication.Theme
30
title: string
0
31
updatedAt?: string
32
[k: string]: unknown
33
}
···
28
textContent?: string
29
theme?: PubLeafletPublication.Theme
30
title: string
31
+
preferences?: $Typed<PubLeafletPublication.Preferences> | { $type: string }
32
updatedAt?: string
33
[k: string]: unknown
34
}
+29
-33
lexicons/api/types/site/standard/publication.ts
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
-
import { type ValidationResult, BlobRef } from "@atproto/lexicon";
5
-
import { CID } from "multiformats/cid";
6
-
import { validate as _validate } from "../../../lexicons";
7
-
import {
8
-
type $Typed,
9
-
is$typed as _is$typed,
10
-
type OmitKey,
11
-
} from "../../../util";
12
-
import type * as SiteStandardThemeBasic from "./theme/basic";
13
-
import type * as PubLeafletPublication from "../../pub/leaflet/publication";
14
15
const is$typed = _is$typed,
16
-
validate = _validate;
17
-
const id = "site.standard.publication";
18
19
export interface Record {
20
-
$type: "site.standard.publication";
21
-
basicTheme?: SiteStandardThemeBasic.Main;
22
-
theme?: $Typed<PubLeafletPublication.Theme> | { $type: string };
23
-
description?: string;
24
-
icon?: BlobRef;
25
-
name: string;
26
-
preferences?: Preferences;
27
-
url: string;
28
-
[k: string]: unknown;
29
}
30
31
-
const hashRecord = "main";
32
33
export function isRecord<V>(v: V) {
34
-
return is$typed(v, id, hashRecord);
35
}
36
37
export function validateRecord<V>(v: V) {
38
-
return validate<Record & V>(v, id, hashRecord, true);
39
}
40
41
export interface Preferences {
42
-
$type?: "site.standard.publication#preferences";
43
-
showInDiscover: boolean;
44
-
showComments: boolean;
45
-
showMentions: boolean;
46
-
showPrevNext: boolean;
47
-
showRecommends: boolean;
48
}
49
50
-
const hashPreferences = "preferences";
51
52
export function isPreferences<V>(v: V) {
53
-
return is$typed(v, id, hashPreferences);
54
}
55
56
export function validatePreferences<V>(v: V) {
57
-
return validate<Preferences & V>(v, id, hashPreferences);
58
}
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { CID } from 'multiformats/cid'
6
+
import { validate as _validate } from '../../../lexicons'
7
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
import type * as SiteStandardThemeBasic from './theme/basic'
9
+
import type * as PubLeafletPublication from '../../pub/leaflet/publication'
0
0
0
0
10
11
const is$typed = _is$typed,
12
+
validate = _validate
13
+
const id = 'site.standard.publication'
14
15
export interface Record {
16
+
$type: 'site.standard.publication'
17
+
basicTheme?: SiteStandardThemeBasic.Main
18
+
theme?: $Typed<PubLeafletPublication.Theme> | { $type: string }
19
+
description?: string
20
+
icon?: BlobRef
21
+
name: string
22
+
preferences?: Preferences
23
+
url: string
24
+
[k: string]: unknown
25
}
26
27
+
const hashRecord = 'main'
28
29
export function isRecord<V>(v: V) {
30
+
return is$typed(v, id, hashRecord)
31
}
32
33
export function validateRecord<V>(v: V) {
34
+
return validate<Record & V>(v, id, hashRecord, true)
35
}
36
37
export interface Preferences {
38
+
$type?: 'site.standard.publication#preferences'
39
+
showInDiscover: boolean
40
+
showComments: boolean
41
+
showMentions: boolean
42
+
showPrevNext: boolean
43
+
showRecommends: boolean
44
}
45
46
+
const hashPreferences = 'preferences'
47
48
export function isPreferences<V>(v: V) {
49
+
return is$typed(v, id, hashPreferences)
50
}
51
52
export function validatePreferences<V>(v: V) {
53
+
return validate<Preferences & V>(v, id, hashPreferences)
54
}
+4
lexicons/pub/leaflet/document.json
···
46
"type": "ref",
47
"ref": "pub.leaflet.publication#theme"
48
},
0
0
0
0
49
"tags": {
50
"type": "array",
51
"items": {
···
46
"type": "ref",
47
"ref": "pub.leaflet.publication#theme"
48
},
49
+
"preferences": {
50
+
"type": "ref",
51
+
"ref": "pub.leaflet.publication#preferences"
52
+
},
53
"tags": {
54
"type": "array",
55
"items": {
+5
lexicons/site/standard/document.json
···
57
"maxLength": 5000,
58
"type": "string"
59
},
0
0
0
0
0
60
"updatedAt": {
61
"format": "datetime",
62
"type": "string"
···
57
"maxLength": 5000,
58
"type": "string"
59
},
60
+
"preferences": {
61
+
"type": "union",
62
+
"refs": ["pub.leaflet.publication#preferences"],
63
+
"closed": false
64
+
},
65
"updatedAt": {
66
"format": "datetime",
67
"type": "string"
+4
lexicons/src/document.ts
···
23
publication: { type: "string", format: "at-uri" },
24
author: { type: "string", format: "at-identifier" },
25
theme: { type: "ref", ref: "pub.leaflet.publication#theme" },
0
0
0
0
26
tags: { type: "array", items: { type: "string", maxLength: 50 } },
27
coverImage: {
28
type: "blob",
···
23
publication: { type: "string", format: "at-uri" },
24
author: { type: "string", format: "at-identifier" },
25
theme: { type: "ref", ref: "pub.leaflet.publication#theme" },
26
+
preferences: {
27
+
type: "ref",
28
+
ref: "pub.leaflet.publication#preferences",
29
+
},
30
tags: { type: "array", items: { type: "string", maxLength: 50 } },
31
coverImage: {
32
type: "blob",
+10
lexicons/src/normalize.ts
···
28
export type NormalizedDocument = SiteStandardDocument.Record & {
29
// Keep the original theme for components that need leaflet-specific styling
30
theme?: PubLeafletPublication.Theme;
0
31
};
32
33
// Normalized publication type - uses the generated site.standard.publication type
···
169
170
// Pass through site.standard records directly (theme is already in correct format if present)
171
if (isStandardDocument(record)) {
0
0
0
172
return {
173
...record,
174
theme: record.theme,
0
175
} as NormalizedDocument;
176
}
177
···
198
}
199
: undefined;
200
0
0
0
0
201
return {
202
$type: "site.standard.document",
203
title: record.title,
···
210
bskyPostRef: record.postRef,
211
content,
212
theme: record.theme,
0
213
};
214
}
215
···
28
export type NormalizedDocument = SiteStandardDocument.Record & {
29
// Keep the original theme for components that need leaflet-specific styling
30
theme?: PubLeafletPublication.Theme;
31
+
preferences?: SiteStandardPublication.Preferences;
32
};
33
34
// Normalized publication type - uses the generated site.standard.publication type
···
170
171
// Pass through site.standard records directly (theme is already in correct format if present)
172
if (isStandardDocument(record)) {
173
+
const preferences = record.preferences as
174
+
| SiteStandardPublication.Preferences
175
+
| undefined;
176
return {
177
...record,
178
theme: record.theme,
179
+
preferences,
180
} as NormalizedDocument;
181
}
182
···
203
}
204
: undefined;
205
206
+
// Extract preferences if present (available after lexicon rebuild)
207
+
const leafletPrefs = (record as Record<string, unknown>)
208
+
.preferences as SiteStandardPublication.Preferences | undefined;
209
+
210
return {
211
$type: "site.standard.document",
212
title: record.title,
···
219
bskyPostRef: record.postRef,
220
content,
221
theme: record.theme,
222
+
preferences: leafletPrefs,
223
};
224
}
225
+6
-391
package-lock.json
···
16
"@atproto/oauth-client-node": "^0.3.8",
17
"@atproto/sync": "^0.1.34",
18
"@atproto/syntax": "^0.3.3",
19
-
"@atproto/tap": "^0.1.1",
20
"@atproto/xrpc": "^0.7.5",
21
"@atproto/xrpc-server": "^0.9.5",
22
"@hono/node-server": "^1.14.3",
···
396
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
397
"license": "(Apache-2.0 AND MIT)"
398
},
399
-
"node_modules/@atproto/lex": {
400
-
"version": "0.0.9",
401
-
"resolved": "https://registry.npmjs.org/@atproto/lex/-/lex-0.0.9.tgz",
402
-
"integrity": "sha512-o6gauf1lz0iyzJR0rqSj4VHOrO+Nt8+/iPb0KPojw1ieXk13zOSTSxotAoDzO/dP6y8Ey5jxwuCQGuzab/4XnQ==",
403
-
"license": "MIT",
404
-
"dependencies": {
405
-
"@atproto/lex-builder": "0.0.9",
406
-
"@atproto/lex-client": "0.0.7",
407
-
"@atproto/lex-data": "0.0.6",
408
-
"@atproto/lex-installer": "0.0.9",
409
-
"@atproto/lex-json": "0.0.6",
410
-
"@atproto/lex-schema": "0.0.7",
411
-
"tslib": "^2.8.1",
412
-
"yargs": "^17.0.0"
413
-
},
414
-
"bin": {
415
-
"lex": "bin/lex",
416
-
"ts-lex": "bin/lex"
417
-
}
418
-
},
419
-
"node_modules/@atproto/lex-builder": {
420
-
"version": "0.0.9",
421
-
"resolved": "https://registry.npmjs.org/@atproto/lex-builder/-/lex-builder-0.0.9.tgz",
422
-
"integrity": "sha512-buOFk1JpuW3twI7To7f/67zQQ1NulLHf/oasH/kTOPUAd0dNyeAa13t9eRSVGbwi0BcZYxRxBm0QzPmdLKyuyw==",
423
-
"license": "MIT",
424
-
"dependencies": {
425
-
"@atproto/lex-document": "0.0.8",
426
-
"@atproto/lex-schema": "0.0.7",
427
-
"prettier": "^3.2.5",
428
-
"ts-morph": "^27.0.0",
429
-
"tslib": "^2.8.1"
430
-
}
431
-
},
432
-
"node_modules/@atproto/lex-builder/node_modules/@ts-morph/common": {
433
-
"version": "0.28.1",
434
-
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz",
435
-
"integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==",
436
-
"license": "MIT",
437
-
"dependencies": {
438
-
"minimatch": "^10.0.1",
439
-
"path-browserify": "^1.0.1",
440
-
"tinyglobby": "^0.2.14"
441
-
}
442
-
},
443
-
"node_modules/@atproto/lex-builder/node_modules/minimatch": {
444
-
"version": "10.1.1",
445
-
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
446
-
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
447
-
"license": "BlueOak-1.0.0",
448
-
"dependencies": {
449
-
"@isaacs/brace-expansion": "^5.0.0"
450
-
},
451
-
"engines": {
452
-
"node": "20 || >=22"
453
-
},
454
-
"funding": {
455
-
"url": "https://github.com/sponsors/isaacs"
456
-
}
457
-
},
458
-
"node_modules/@atproto/lex-builder/node_modules/ts-morph": {
459
-
"version": "27.0.2",
460
-
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz",
461
-
"integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==",
462
-
"license": "MIT",
463
-
"dependencies": {
464
-
"@ts-morph/common": "~0.28.1",
465
-
"code-block-writer": "^13.0.3"
466
-
}
467
-
},
468
-
"node_modules/@atproto/lex-cbor": {
469
-
"version": "0.0.6",
470
-
"resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.6.tgz",
471
-
"integrity": "sha512-lee2T00owDy3I1plRHuURT6f98NIpYZZr2wXa5pJZz5JzefZ+nv8gJ2V70C2f+jmSG+5S9NTIy4uJw94vaHf4A==",
472
-
"license": "MIT",
473
-
"dependencies": {
474
-
"@atproto/lex-data": "0.0.6",
475
-
"multiformats": "^9.9.0",
476
-
"tslib": "^2.8.1"
477
-
}
478
-
},
479
-
"node_modules/@atproto/lex-cbor/node_modules/multiformats": {
480
-
"version": "9.9.0",
481
-
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
482
-
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
483
-
"license": "(Apache-2.0 AND MIT)"
484
-
},
485
"node_modules/@atproto/lex-cli": {
486
"version": "0.9.5",
487
"resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.5.tgz",
···
515
"tslib": "^2.8.1"
516
}
517
},
518
-
"node_modules/@atproto/lex-client": {
519
-
"version": "0.0.7",
520
-
"resolved": "https://registry.npmjs.org/@atproto/lex-client/-/lex-client-0.0.7.tgz",
521
-
"integrity": "sha512-ofUz3yXJ0nN/M9aqqF2ZUL/4D1wWT1P4popCfV3OEDsDrtWofMflYPFz1IWuyPa2e83paaEHRhaw3bZEhgXH1w==",
522
-
"license": "MIT",
523
-
"dependencies": {
524
-
"@atproto/lex-data": "0.0.6",
525
-
"@atproto/lex-json": "0.0.6",
526
-
"@atproto/lex-schema": "0.0.7",
527
-
"tslib": "^2.8.1"
528
-
}
529
-
},
530
-
"node_modules/@atproto/lex-data": {
531
-
"version": "0.0.6",
532
-
"resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.6.tgz",
533
-
"integrity": "sha512-MBNB4ghRJQzuXK1zlUPljpPbQcF1LZ5dzxy274KqPt4p3uPuRw0mHjgcCoWzRUNBQC685WMQR4IN9DHtsnG57A==",
534
-
"license": "MIT",
535
-
"dependencies": {
536
-
"@atproto/syntax": "0.4.2",
537
-
"multiformats": "^9.9.0",
538
-
"tslib": "^2.8.1",
539
-
"uint8arrays": "3.0.0",
540
-
"unicode-segmenter": "^0.14.0"
541
-
}
542
-
},
543
-
"node_modules/@atproto/lex-data/node_modules/@atproto/syntax": {
544
-
"version": "0.4.2",
545
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
546
-
"integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
547
-
"license": "MIT"
548
-
},
549
-
"node_modules/@atproto/lex-data/node_modules/multiformats": {
550
-
"version": "9.9.0",
551
-
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
552
-
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
553
-
"license": "(Apache-2.0 AND MIT)"
554
-
},
555
-
"node_modules/@atproto/lex-document": {
556
-
"version": "0.0.8",
557
-
"resolved": "https://registry.npmjs.org/@atproto/lex-document/-/lex-document-0.0.8.tgz",
558
-
"integrity": "sha512-p3l5h96Hx0vxUwbO/eas6x5h2vU0JVN1a/ktX4k3PlK9YLXfWMFsv+RdVwVZom8o0irHwlcyh1D/cY0PyUojDA==",
559
-
"license": "MIT",
560
-
"dependencies": {
561
-
"@atproto/lex-schema": "0.0.7",
562
-
"core-js": "^3",
563
-
"tslib": "^2.8.1"
564
-
}
565
-
},
566
-
"node_modules/@atproto/lex-installer": {
567
-
"version": "0.0.9",
568
-
"resolved": "https://registry.npmjs.org/@atproto/lex-installer/-/lex-installer-0.0.9.tgz",
569
-
"integrity": "sha512-zEeIeSaSCb3j+zNsqqMY7+X5FO6fxy/MafaCEj42KsXQHNcobuygZsnG/0fxMj/kMvhjrNUCp/w9PyOMwx4hQg==",
570
-
"license": "MIT",
571
-
"dependencies": {
572
-
"@atproto/lex-builder": "0.0.9",
573
-
"@atproto/lex-cbor": "0.0.6",
574
-
"@atproto/lex-data": "0.0.6",
575
-
"@atproto/lex-document": "0.0.8",
576
-
"@atproto/lex-resolver": "0.0.8",
577
-
"@atproto/lex-schema": "0.0.7",
578
-
"@atproto/syntax": "0.4.2",
579
-
"tslib": "^2.8.1"
580
-
}
581
-
},
582
-
"node_modules/@atproto/lex-installer/node_modules/@atproto/syntax": {
583
-
"version": "0.4.2",
584
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
585
-
"integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
586
-
"license": "MIT"
587
-
},
588
-
"node_modules/@atproto/lex-json": {
589
-
"version": "0.0.6",
590
-
"resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.6.tgz",
591
-
"integrity": "sha512-EILnN5cditPvf+PCNjXt7reMuzjugxAL1fpSzmzJbEMGMUwxOf5pPWxRsaA/M3Boip4NQZ+6DVrPOGUMlnqceg==",
592
-
"license": "MIT",
593
-
"dependencies": {
594
-
"@atproto/lex-data": "0.0.6",
595
-
"tslib": "^2.8.1"
596
-
}
597
-
},
598
-
"node_modules/@atproto/lex-resolver": {
599
-
"version": "0.0.8",
600
-
"resolved": "https://registry.npmjs.org/@atproto/lex-resolver/-/lex-resolver-0.0.8.tgz",
601
-
"integrity": "sha512-4hXT560+k5BIttouuhXOr+UkhAuFvvkJaVdqYb8vx2Ez7eHPiZ+yWkUK6FKpyGsx2whHkJzgleEA6DNWtdDlWA==",
602
-
"license": "MIT",
603
-
"dependencies": {
604
-
"@atproto-labs/did-resolver": "0.2.5",
605
-
"@atproto/crypto": "0.4.5",
606
-
"@atproto/lex-client": "0.0.7",
607
-
"@atproto/lex-data": "0.0.6",
608
-
"@atproto/lex-document": "0.0.8",
609
-
"@atproto/lex-schema": "0.0.7",
610
-
"@atproto/repo": "0.8.12",
611
-
"@atproto/syntax": "0.4.2",
612
-
"tslib": "^2.8.1"
613
-
}
614
-
},
615
-
"node_modules/@atproto/lex-resolver/node_modules/@atproto-labs/did-resolver": {
616
-
"version": "0.2.5",
617
-
"resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.5.tgz",
618
-
"integrity": "sha512-he7EC6OMSifNs01a4RT9mta/yYitoKDzlK9ty2TFV5Uj/+HpB4vYMRdIDFrRW0Hcsehy90E2t/dw0t7361MEKQ==",
619
-
"license": "MIT",
620
-
"dependencies": {
621
-
"@atproto-labs/fetch": "0.2.3",
622
-
"@atproto-labs/pipe": "0.1.1",
623
-
"@atproto-labs/simple-store": "0.3.0",
624
-
"@atproto-labs/simple-store-memory": "0.1.4",
625
-
"@atproto/did": "0.2.4",
626
-
"zod": "^3.23.8"
627
-
}
628
-
},
629
-
"node_modules/@atproto/lex-resolver/node_modules/@atproto/did": {
630
-
"version": "0.2.4",
631
-
"resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.4.tgz",
632
-
"integrity": "sha512-nxNiCgXeo7pfjojq9fpfZxCO0X0xUipNVKW+AHNZwQKiUDt6zYL0VXEfm8HBUwQOCmKvj2pRRSM1Cur+tUWk3g==",
633
-
"license": "MIT",
634
-
"dependencies": {
635
-
"zod": "^3.23.8"
636
-
}
637
-
},
638
-
"node_modules/@atproto/lex-resolver/node_modules/@atproto/syntax": {
639
-
"version": "0.4.2",
640
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
641
-
"integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
642
-
"license": "MIT"
643
-
},
644
-
"node_modules/@atproto/lex-schema": {
645
-
"version": "0.0.7",
646
-
"resolved": "https://registry.npmjs.org/@atproto/lex-schema/-/lex-schema-0.0.7.tgz",
647
-
"integrity": "sha512-/7HkTUsnP1rlzmVE6nnY0kl/hydL/W8V29V8BhFwdAvdDKpYcdRgzzsMe38LAt+ZOjHknRCZDIKGsbQMSbJErw==",
648
-
"license": "MIT",
649
-
"dependencies": {
650
-
"@atproto/lex-data": "0.0.6",
651
-
"@atproto/syntax": "0.4.2",
652
-
"tslib": "^2.8.1"
653
-
}
654
-
},
655
-
"node_modules/@atproto/lex-schema/node_modules/@atproto/syntax": {
656
-
"version": "0.4.2",
657
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
658
-
"integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
659
-
"license": "MIT"
660
-
},
661
"node_modules/@atproto/lexicon": {
662
"version": "0.6.1",
663
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.1.tgz",
···
848
"integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==",
849
"license": "MIT"
850
},
851
-
"node_modules/@atproto/tap": {
852
-
"version": "0.1.1",
853
-
"resolved": "https://registry.npmjs.org/@atproto/tap/-/tap-0.1.1.tgz",
854
-
"integrity": "sha512-gW4NzLOxj74TzaDOVzzzt5kl2PdC0r75XkIpYpI5xobwCfsc/DmVtwpuSw1fW9gr4Vzk2Q90S9UE4ifAFl2gyA==",
855
-
"license": "MIT",
856
-
"dependencies": {
857
-
"@atproto/common": "^0.5.6",
858
-
"@atproto/lex": "^0.0.9",
859
-
"@atproto/syntax": "^0.4.2",
860
-
"@atproto/ws-client": "^0.0.4",
861
-
"ws": "^8.12.0",
862
-
"zod": "^3.23.8"
863
-
},
864
-
"engines": {
865
-
"node": ">=18.7.0"
866
-
}
867
-
},
868
-
"node_modules/@atproto/tap/node_modules/@atproto/common": {
869
-
"version": "0.5.10",
870
-
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.10.tgz",
871
-
"integrity": "sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g==",
872
-
"license": "MIT",
873
-
"dependencies": {
874
-
"@atproto/common-web": "^0.4.15",
875
-
"@atproto/lex-cbor": "^0.0.10",
876
-
"@atproto/lex-data": "^0.0.10",
877
-
"iso-datestring-validator": "^2.2.2",
878
-
"multiformats": "^9.9.0",
879
-
"pino": "^8.21.0"
880
-
},
881
-
"engines": {
882
-
"node": ">=18.7.0"
883
-
}
884
-
},
885
-
"node_modules/@atproto/tap/node_modules/@atproto/lex-cbor": {
886
-
"version": "0.0.10",
887
-
"resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz",
888
-
"integrity": "sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==",
889
-
"license": "MIT",
890
-
"dependencies": {
891
-
"@atproto/lex-data": "^0.0.10",
892
-
"tslib": "^2.8.1"
893
-
}
894
-
},
895
-
"node_modules/@atproto/tap/node_modules/@atproto/lex-data": {
896
-
"version": "0.0.10",
897
-
"resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.10.tgz",
898
-
"integrity": "sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ==",
899
-
"license": "MIT",
900
-
"dependencies": {
901
-
"multiformats": "^9.9.0",
902
-
"tslib": "^2.8.1",
903
-
"uint8arrays": "3.0.0",
904
-
"unicode-segmenter": "^0.14.0"
905
-
}
906
-
},
907
-
"node_modules/@atproto/tap/node_modules/@atproto/syntax": {
908
-
"version": "0.4.3",
909
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.3.tgz",
910
-
"integrity": "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==",
911
-
"license": "MIT",
912
-
"dependencies": {
913
-
"tslib": "^2.8.1"
914
-
}
915
-
},
916
-
"node_modules/@atproto/tap/node_modules/multiformats": {
917
-
"version": "9.9.0",
918
-
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
919
-
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
920
-
"license": "(Apache-2.0 AND MIT)"
921
-
},
922
-
"node_modules/@atproto/ws-client": {
923
-
"version": "0.0.4",
924
-
"resolved": "https://registry.npmjs.org/@atproto/ws-client/-/ws-client-0.0.4.tgz",
925
-
"integrity": "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg==",
926
-
"license": "MIT",
927
-
"dependencies": {
928
-
"@atproto/common": "^0.5.3",
929
-
"ws": "^8.12.0"
930
-
},
931
-
"engines": {
932
-
"node": ">=18.7.0"
933
-
}
934
-
},
935
-
"node_modules/@atproto/ws-client/node_modules/@atproto/common": {
936
-
"version": "0.5.10",
937
-
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.10.tgz",
938
-
"integrity": "sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g==",
939
-
"license": "MIT",
940
-
"dependencies": {
941
-
"@atproto/common-web": "^0.4.15",
942
-
"@atproto/lex-cbor": "^0.0.10",
943
-
"@atproto/lex-data": "^0.0.10",
944
-
"iso-datestring-validator": "^2.2.2",
945
-
"multiformats": "^9.9.0",
946
-
"pino": "^8.21.0"
947
-
},
948
-
"engines": {
949
-
"node": ">=18.7.0"
950
-
}
951
-
},
952
-
"node_modules/@atproto/ws-client/node_modules/@atproto/lex-cbor": {
953
-
"version": "0.0.10",
954
-
"resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz",
955
-
"integrity": "sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==",
956
-
"license": "MIT",
957
-
"dependencies": {
958
-
"@atproto/lex-data": "^0.0.10",
959
-
"tslib": "^2.8.1"
960
-
}
961
-
},
962
-
"node_modules/@atproto/ws-client/node_modules/@atproto/lex-data": {
963
-
"version": "0.0.10",
964
-
"resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.10.tgz",
965
-
"integrity": "sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ==",
966
-
"license": "MIT",
967
-
"dependencies": {
968
-
"multiformats": "^9.9.0",
969
-
"tslib": "^2.8.1",
970
-
"uint8arrays": "3.0.0",
971
-
"unicode-segmenter": "^0.14.0"
972
-
}
973
-
},
974
-
"node_modules/@atproto/ws-client/node_modules/multiformats": {
975
-
"version": "9.9.0",
976
-
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
977
-
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
978
-
"license": "(Apache-2.0 AND MIT)"
979
-
},
980
"node_modules/@atproto/xrpc": {
981
"version": "0.7.5",
982
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz",
···
3052
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
3053
"license": "(Apache-2.0 AND MIT)"
3054
},
3055
-
"node_modules/@isaacs/balanced-match": {
3056
-
"version": "4.0.1",
3057
-
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
3058
-
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
3059
-
"license": "MIT",
3060
-
"engines": {
3061
-
"node": "20 || >=22"
3062
-
}
3063
-
},
3064
-
"node_modules/@isaacs/brace-expansion": {
3065
-
"version": "5.0.0",
3066
-
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
3067
-
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
3068
-
"license": "MIT",
3069
-
"dependencies": {
3070
-
"@isaacs/balanced-match": "^4.0.1"
3071
-
},
3072
-
"engines": {
3073
-
"node": "20 || >=22"
3074
-
}
3075
-
},
3076
"node_modules/@isaacs/fs-minipass": {
3077
"version": "4.0.1",
3078
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
···
9631
"version": "13.0.3",
9632
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
9633
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==",
0
9634
"license": "MIT"
9635
},
9636
"node_modules/collapse-white-space": {
···
9739
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
9740
"license": "MIT"
9741
},
9742
-
"node_modules/core-js": {
9743
-
"version": "3.47.0",
9744
-
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
9745
-
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
9746
-
"hasInstallScript": true,
9747
-
"license": "MIT",
9748
-
"funding": {
9749
-
"type": "opencollective",
9750
-
"url": "https://opencollective.com/core-js"
9751
-
}
9752
-
},
9753
"node_modules/crelt": {
9754
"version": "1.0.6",
9755
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
···
16142
"version": "1.0.1",
16143
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
16144
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
0
16145
"license": "MIT"
16146
},
16147
"node_modules/path-exists": {
···
16424
"version": "3.2.5",
16425
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
16426
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
0
16427
"bin": {
16428
"prettier": "bin/prettier.cjs"
16429
},
···
18520
"version": "0.2.15",
18521
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
18522
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
0
18523
"license": "MIT",
18524
"dependencies": {
18525
"fdir": "^6.5.0",
···
18536
"version": "6.5.0",
18537
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
18538
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
0
18539
"license": "MIT",
18540
"engines": {
18541
"node": ">=12.0.0"
···
18553
"version": "4.0.3",
18554
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
18555
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
0
18556
"license": "MIT",
18557
"engines": {
18558
"node": ">=12"
···
16
"@atproto/oauth-client-node": "^0.3.8",
17
"@atproto/sync": "^0.1.34",
18
"@atproto/syntax": "^0.3.3",
0
19
"@atproto/xrpc": "^0.7.5",
20
"@atproto/xrpc-server": "^0.9.5",
21
"@hono/node-server": "^1.14.3",
···
395
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
396
"license": "(Apache-2.0 AND MIT)"
397
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
398
"node_modules/@atproto/lex-cli": {
399
"version": "0.9.5",
400
"resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.5.tgz",
···
428
"tslib": "^2.8.1"
429
}
430
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
431
"node_modules/@atproto/lexicon": {
432
"version": "0.6.1",
433
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.1.tgz",
···
618
"integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==",
619
"license": "MIT"
620
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
621
"node_modules/@atproto/xrpc": {
622
"version": "0.7.5",
623
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz",
···
2693
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
2694
"license": "(Apache-2.0 AND MIT)"
2695
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
2696
"node_modules/@isaacs/fs-minipass": {
2697
"version": "4.0.1",
2698
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
···
9251
"version": "13.0.3",
9252
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
9253
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==",
9254
+
"dev": true,
9255
"license": "MIT"
9256
},
9257
"node_modules/collapse-white-space": {
···
9360
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
9361
"license": "MIT"
9362
},
0
0
0
0
0
0
0
0
0
0
0
9363
"node_modules/crelt": {
9364
"version": "1.0.6",
9365
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
···
15752
"version": "1.0.1",
15753
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
15754
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
15755
+
"dev": true,
15756
"license": "MIT"
15757
},
15758
"node_modules/path-exists": {
···
16035
"version": "3.2.5",
16036
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
16037
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
16038
+
"dev": true,
16039
"bin": {
16040
"prettier": "bin/prettier.cjs"
16041
},
···
18132
"version": "0.2.15",
18133
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
18134
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
18135
+
"dev": true,
18136
"license": "MIT",
18137
"dependencies": {
18138
"fdir": "^6.5.0",
···
18149
"version": "6.5.0",
18150
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
18151
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
18152
+
"dev": true,
18153
"license": "MIT",
18154
"engines": {
18155
"node": ">=12.0.0"
···
18167
"version": "4.0.3",
18168
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
18169
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
18170
+
"dev": true,
18171
"license": "MIT",
18172
"engines": {
18173
"node": ">=12"
-1
package.json
···
27
"@atproto/oauth-client-node": "^0.3.8",
28
"@atproto/sync": "^0.1.34",
29
"@atproto/syntax": "^0.3.3",
30
-
"@atproto/tap": "^0.1.1",
31
"@atproto/xrpc": "^0.7.5",
32
"@atproto/xrpc-server": "^0.9.5",
33
"@hono/node-server": "^1.14.3",
···
27
"@atproto/oauth-client-node": "^0.3.8",
28
"@atproto/sync": "^0.1.34",
29
"@atproto/syntax": "^0.3.3",
0
30
"@atproto/xrpc": "^0.7.5",
31
"@atproto/xrpc-server": "^0.9.5",
32
"@hono/node-server": "^1.14.3",
+13
src/replicache/mutations.ts
···
659
tags?: string[];
660
cover_image?: string | null;
661
localPublishedAt?: string | null;
0
0
0
0
0
662
}> = async (args, ctx) => {
663
await ctx.runOnServer(async (serverCtx) => {
664
console.log("updating");
···
667
title?: string;
668
tags?: string[];
669
cover_image?: string | null;
0
0
0
0
0
670
} = {};
671
if (args.description !== undefined) updates.description = args.description;
672
if (args.title !== undefined) updates.title = args.title;
673
if (args.tags !== undefined) updates.tags = args.tags;
674
if (args.cover_image !== undefined) updates.cover_image = args.cover_image;
0
675
676
if (Object.keys(updates).length > 0) {
677
// First try to update leaflets_in_publications (for publications)
···
700
await tx.set("publication_cover_image", args.cover_image);
701
if (args.localPublishedAt !== undefined)
702
await tx.set("publication_local_published_at", args.localPublishedAt);
0
0
703
});
704
};
705
···
659
tags?: string[];
660
cover_image?: string | null;
661
localPublishedAt?: string | null;
662
+
preferences?: {
663
+
showComments?: boolean;
664
+
showMentions?: boolean;
665
+
showRecommends?: boolean;
666
+
} | null;
667
}> = async (args, ctx) => {
668
await ctx.runOnServer(async (serverCtx) => {
669
console.log("updating");
···
672
title?: string;
673
tags?: string[];
674
cover_image?: string | null;
675
+
preferences?: {
676
+
showComments?: boolean;
677
+
showMentions?: boolean;
678
+
showRecommends?: boolean;
679
+
} | null;
680
} = {};
681
if (args.description !== undefined) updates.description = args.description;
682
if (args.title !== undefined) updates.title = args.title;
683
if (args.tags !== undefined) updates.tags = args.tags;
684
if (args.cover_image !== undefined) updates.cover_image = args.cover_image;
685
+
if (args.preferences !== undefined) updates.preferences = args.preferences;
686
687
if (Object.keys(updates).length > 0) {
688
// First try to update leaflets_in_publications (for publications)
···
711
await tx.set("publication_cover_image", args.cover_image);
712
if (args.localPublishedAt !== undefined)
713
await tx.set("publication_local_published_at", args.localPublishedAt);
714
+
if (args.preferences !== undefined)
715
+
await tx.set("post_preferences", args.preferences);
716
});
717
};
718
+24
src/utils/mergePreferences.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
type PreferencesInput = {
2
+
showComments?: boolean;
3
+
showMentions?: boolean;
4
+
showRecommends?: boolean;
5
+
showPrevNext?: boolean;
6
+
} | null;
7
+
8
+
export function mergePreferences(
9
+
documentPrefs?: PreferencesInput,
10
+
publicationPrefs?: PreferencesInput,
11
+
): {
12
+
showComments?: boolean;
13
+
showMentions?: boolean;
14
+
showRecommends?: boolean;
15
+
showPrevNext?: boolean;
16
+
} {
17
+
return {
18
+
showComments: documentPrefs?.showComments ?? publicationPrefs?.showComments,
19
+
showMentions: documentPrefs?.showMentions ?? publicationPrefs?.showMentions,
20
+
showRecommends:
21
+
documentPrefs?.showRecommends ?? publicationPrefs?.showRecommends,
22
+
showPrevNext: publicationPrefs?.showPrevNext,
23
+
};
24
+
}
+6
supabase/database.types.ts
···
589
description: string
590
doc: string | null
591
leaflet: string
0
592
publication: string
593
tags: string[] | null
594
title: string
···
599
description?: string
600
doc?: string | null
601
leaflet: string
0
602
publication: string
603
tags?: string[] | null
604
title?: string
···
609
description?: string
610
doc?: string | null
611
leaflet?: string
0
612
publication?: string
613
tags?: string[] | null
614
title?: string
···
645
description: string
646
document: string
647
leaflet: string
0
648
tags: string[] | null
649
title: string
650
}
···
655
description?: string
656
document: string
657
leaflet: string
0
658
tags?: string[] | null
659
title?: string
660
}
···
665
description?: string
666
document?: string
667
leaflet?: string
0
668
tags?: string[] | null
669
title?: string
670
}
···
589
description: string
590
doc: string | null
591
leaflet: string
592
+
preferences: Json | null
593
publication: string
594
tags: string[] | null
595
title: string
···
600
description?: string
601
doc?: string | null
602
leaflet: string
603
+
preferences?: Json | null
604
publication: string
605
tags?: string[] | null
606
title?: string
···
611
description?: string
612
doc?: string | null
613
leaflet?: string
614
+
preferences?: Json | null
615
publication?: string
616
tags?: string[] | null
617
title?: string
···
648
description: string
649
document: string
650
leaflet: string
651
+
preferences: Json | null
652
tags: string[] | null
653
title: string
654
}
···
659
description?: string
660
document: string
661
leaflet: string
662
+
preferences?: Json | null
663
tags?: string[] | null
664
title?: string
665
}
···
670
description?: string
671
document?: string
672
leaflet?: string
673
+
preferences?: Json | null
674
tags?: string[] | null
675
title?: string
676
}
+2
supabase/migrations/20260208000000_add_preferences_to_drafts.sql
···
0
0
···
1
+
ALTER TABLE leaflets_in_publications ADD COLUMN preferences jsonb;
2
+
ALTER TABLE leaflets_to_documents ADD COLUMN preferences jsonb;