tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
a tool for shared writing and social publishing
284
fork
atom
overview
issues
26
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
update/wider-page
update/thread-viewer
update/reader
update/looseleafs
update/delete-leaflets
update/delete-blocks
test/unknown-marks
refactor/standard.site
refactor/shared-home-layout
main
fix/tag-case-insensitive
fix/profile-popover
fix/bottom-scroll-margin-on-docs
fix/accent-contrast-on-page
feature/thread-viewer
feature/tags
feature/small-text
feature/set-page-width
feature/recommend
feature/reader
feature/publish-leaflets
feature/pub-contributors
feature/profiles
feature/post-options
feature/page-blocks
feature/notifications
feature/hard_breaks
feature/fonts
feature/follow-via-email
feature/bsky-follows-feed
feature/backdate
feature/atp-polls
feature/atp-canvas-blocks
feature/at-mentions
debug/datetime
no tags found
compare:
update/wider-page
update/thread-viewer
update/reader
update/looseleafs
update/delete-leaflets
update/delete-blocks
test/unknown-marks
refactor/standard.site
refactor/shared-home-layout
main
fix/tag-case-insensitive
fix/profile-popover
fix/bottom-scroll-margin-on-docs
fix/accent-contrast-on-page
feature/thread-viewer
feature/tags
feature/small-text
feature/set-page-width
feature/recommend
feature/reader
feature/publish-leaflets
feature/pub-contributors
feature/profiles
feature/post-options
feature/page-blocks
feature/notifications
feature/hard_breaks
feature/fonts
feature/follow-via-email
feature/bsky-follows-feed
feature/backdate
feature/atp-polls
feature/atp-canvas-blocks
feature/at-mentions
debug/datetime
no tags found
go
+398
-504
31 changed files
expand all
collapse all
unified
split
actions
publishToPublication.ts
app
[leaflet_id]
Footer.tsx
Sidebar.tsx
actions
PublishButton.tsx
publish
PublishPost.tsx
api
oauth
[route]
oauth-metadata.ts
route.ts
rpc
[command]
pull.ts
lish
[did]
[publication]
[rkey]
DocumentPageRenderer.tsx
PostPages.tsx
components
Canvas.tsx
OAuthError.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
atproto-oauth.ts
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]/Footer.tsx
···
14
14
import { useIdentityData } from "components/IdentityProvider";
15
15
import { useEntity } from "src/replicache";
16
16
import { block } from "sharp";
17
17
+
import { PostSettings } from "components/PostSettings";
17
18
18
19
export function hasBlockToolbar(blockType: string | null | undefined) {
19
20
return (
···
64
65
65
66
<PublishButton entityID={props.entityID} />
66
67
<ShareOptions />
68
68
+
<PostSettings />
67
69
<ThemePopover entityID={props.entityID} />
68
70
</ActionFooter>
69
71
) : (
+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) {
+2
-1
app/api/oauth/[route]/oauth-metadata.ts
···
7
7
? "http://localhost:3000"
8
8
: "https://leaflet.pub";
9
9
10
10
-
const scope = "atproto transition:generic transition:email";
10
10
+
const scope =
11
11
+
"atproto transition:generic transition:email include:pub.leaflet.authFullPermissions include:site.standard.authFull include:app.bsky.authCreatePosts blob:*/*";
11
12
const localconfig: OAuthClientMetadataInput = {
12
13
client_id: `http://localhost/?redirect_uri=${encodeURI(`http://127.0.0.1:3000/api/oauth/callback`)}&scope=${encodeURIComponent(scope)}`,
13
14
client_name: `Leaflet`,
+2
-1
app/api/oauth/[route]/route.ts
···
42
42
const ac = new AbortController();
43
43
44
44
const url = await client.authorize(handle || "https://bsky.social", {
45
45
-
scope: "atproto transition:generic transition:email",
45
45
+
scope:
46
46
+
"atproto transition:email include:pub.leaflet.authFullPermissions include:site.standard.authFull include:app.bsky.authCreatePosts blob:*/*",
46
47
signal: ac.signal,
47
48
state: JSON.stringify(state),
48
49
});
+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">
+7
-2
components/OAuthError.tsx
···
13
13
14
14
return (
15
15
<div className={className}>
16
16
-
<span>Your session has expired or is invalid. </span>
16
16
+
<span>
17
17
+
{error.type === "missing_scope"
18
18
+
? "Your session is missing required permissions. "
19
19
+
: "Your session has expired or is invalid. "}
20
20
+
</span>
17
21
<a href={signInUrl} className="underline font-bold whitespace-nowrap">
18
22
Sign in again
19
23
</a>
···
28
32
typeof error === "object" &&
29
33
error !== null &&
30
34
"type" in error &&
31
31
-
(error as OAuthSessionError).type === "oauth_session_expired"
35
35
+
((error as OAuthSessionError).type === "oauth_session_expired" ||
36
36
+
(error as OAuthSessionError).type === "missing_scope")
32
37
);
33
38
}
+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",
+12
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
223
223
+
? { ...leafletPrefs, $type: "site.standard.publication#preferences" as const }
224
224
+
: undefined,
213
225
};
214
226
}
215
227
+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",
+27
-9
src/atproto-oauth.ts
···
93
93
},
94
94
};
95
95
96
96
-
export type OAuthSessionError = {
97
97
-
type: "oauth_session_expired";
98
98
-
message: string;
99
99
-
did: string;
100
100
-
};
96
96
+
export type OAuthSessionError =
97
97
+
| {
98
98
+
type: "oauth_session_expired";
99
99
+
message: string;
100
100
+
did: string;
101
101
+
}
102
102
+
| {
103
103
+
type: "missing_scope";
104
104
+
message: string;
105
105
+
did: string;
106
106
+
scope: string;
107
107
+
};
101
108
102
109
export async function restoreOAuthSession(
103
110
did: string
···
107
114
const session = await oauthClient.restore(did);
108
115
return Ok(session);
109
116
} catch (error) {
117
117
+
const message =
118
118
+
error instanceof Error
119
119
+
? error.message
120
120
+
: "OAuth session expired or invalid";
121
121
+
if (message.includes("Missing required scope")) {
122
122
+
const scope = message.replace("Missing required scope: ", "").trim();
123
123
+
console.error(`OAuth missing scope for ${did}: ${scope}`);
124
124
+
return Err({
125
125
+
type: "missing_scope",
126
126
+
message,
127
127
+
did,
128
128
+
scope,
129
129
+
});
130
130
+
}
110
131
return Err({
111
132
type: "oauth_session_expired",
112
112
-
message:
113
113
-
error instanceof Error
114
114
-
? error.message
115
115
-
: "OAuth session expired or invalid",
133
133
+
message,
116
134
did,
117
135
});
118
136
}
+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;