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