tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
a tool for shared writing and social publishing
275
fork
atom
overview
issues
24
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
update/wider-page
update/thread-viewer
update/looseleafs
update/delete-leaflets
update/delete-blocks
test/unknown-marks
refactor/standard.site
refactor/shared-home-layout
main
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/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/looseleafs
update/delete-leaflets
update/delete-blocks
test/unknown-marks
refactor/standard.site
refactor/shared-home-layout
main
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/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
+357
-134
31 changed files
expand all
collapse all
unified
split
actions
publishToPublication.ts
app
[leaflet_id]
actions
PublishButton.tsx
lish
[did]
[publication]
[rkey]
CanvasPage.tsx
Interactions
Comments
index.tsx
InteractionDrawer.tsx
Interactions.tsx
LinearDocumentPage.tsx
PostHeader
PostHeader.tsx
PostPages.tsx
PostPrevNextButtons.tsx
QuoteHandler.tsx
getPostPageData.ts
dashboard
PublishedPostsLists.tsx
settings
PostOptions.tsx
PublicationSettings.tsx
page.tsx
createPub
CreatePubForm.tsx
UpdatePubForm.tsx
updatePublication.ts
components
Canvas.tsx
InteractionsPreview.tsx
Pages
PublicationMetadata.tsx
PostListing.tsx
ThemeManager
PublicationThemeProvider.tsx
ThemeProvider.tsx
ThemeSetter.tsx
themeUtils.ts
lexicons
api
lexicons.ts
types
pub
leaflet
publication.ts
pub
leaflet
publication.json
src
publication.ts
+2
actions/publishToPublication.ts
···
784
784
root_entity,
785
785
"theme/background-image-repeat",
786
786
)?.[0];
787
787
+
let pageWidth = scan.eav(root_entity, "theme/page-width")?.[0];
787
788
788
789
let theme: PubLeafletPublication.Theme = {
789
790
showPageBackground: showPageBackground ?? true,
790
791
};
791
792
793
793
+
if (pageWidth) theme.pageWidth = pageWidth.data.value;
792
794
if (pageBackground)
793
795
theme.backgroundColor = ColorToRGBA(parseColor(`hsba(${pageBackground})`));
794
796
if (cardBackground)
+2
-2
app/[leaflet_id]/actions/PublishButton.tsx
···
136
136
content: (
137
137
<div>
138
138
{pub.doc ? "Updated! " : "Published! "}
139
139
-
<SpeedyLink className="underline font-bold" href={docUrl}>
140
140
-
See Post
139
139
+
<SpeedyLink className="underline" href={docUrl}>
140
140
+
See Published Post
141
141
</SpeedyLink>
142
142
</div>
143
143
),
+6
-1
app/lish/[did]/[publication]/[rkey]/CanvasPage.tsx
···
202
202
isSubpage: boolean | undefined;
203
203
data: PostPageData;
204
204
profile: ProfileViewDetailed;
205
205
-
preferences: { showComments?: boolean };
205
205
+
preferences: {
206
206
+
showComments?: boolean;
207
207
+
showMentions?: boolean;
208
208
+
showPrevNext?: boolean;
209
209
+
};
206
210
quotesCount: number | undefined;
207
211
commentsCount: number | undefined;
208
212
}) => {
···
213
217
quotesCount={props.quotesCount || 0}
214
218
commentsCount={props.commentsCount || 0}
215
219
showComments={props.preferences.showComments}
220
220
+
showMentions={props.preferences.showMentions}
216
221
pageId={props.pageId}
217
222
/>
218
223
{!props.isSubpage && (
+4
-1
app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx
···
51
51
}, []);
52
52
53
53
return (
54
54
-
<div id={"commentsDrawer"} className="flex flex-col gap-2 relative">
54
54
+
<div
55
55
+
id={"commentsDrawer"}
56
56
+
className="flex flex-col gap-2 relative text-sm text-secondary"
57
57
+
>
55
58
<div className="w-full flex justify-between text-secondary font-bold">
56
59
Comments
57
60
<button
+2
-1
app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx
···
9
9
import { decodeQuotePosition } from "../quotePosition";
10
10
11
11
export const InteractionDrawer = (props: {
12
12
+
showPageBackground: boolean | undefined;
12
13
document_uri: string;
13
14
quotesAndMentions: { uri: string; link?: string }[];
14
15
comments: Comment[];
···
38
39
<div className="snap-center h-full flex z-10 shrink-0 w-[calc(var(--page-width-units)-6px)] sm:w-[calc(var(--page-width-units))]">
39
40
<div
40
41
id="interaction-drawer"
41
41
-
className="opaque-container rounded-l-none! rounded-r-lg! h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll -ml-[1px] "
42
42
+
className={`opaque-container h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll -ml-[1px] ${props.showPageBackground ? "rounded-l-none! rounded-r-lg!" : "rounded-lg! sm:mx-2"}`}
42
43
>
43
44
{drawer.drawer === "quotes" ? (
44
45
<Quotes {...props} quotesAndMentions={filteredQuotesAndMentions} />
+68
-44
app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx
···
108
108
commentsCount: number;
109
109
className?: string;
110
110
showComments?: boolean;
111
111
+
showMentions?: boolean;
111
112
pageId?: string;
112
113
}) => {
113
114
const data = useContext(PostPageContext);
···
131
132
<div className={`flex gap-2 text-tertiary text-sm ${props.className}`}>
132
133
{tagCount > 0 && <TagPopover tags={tags} tagCount={tagCount} />}
133
134
134
134
-
{props.quotesCount > 0 && (
135
135
+
{props.quotesCount === 0 || props.showMentions === false ? null : (
135
136
<button
136
137
className="flex w-fit gap-2 items-center"
137
138
onClick={() => {
···
168
169
commentsCount: number;
169
170
className?: string;
170
171
showComments?: boolean;
172
172
+
showMentions?: boolean;
171
173
pageId?: string;
172
174
}) => {
173
175
const data = useContext(PostPageContext);
···
189
191
const tags = (data?.data as any)?.tags as string[] | undefined;
190
192
const tagCount = tags?.length || 0;
191
193
194
194
+
let noInteractions = !props.showComments && !props.showMentions;
195
195
+
192
196
let subscribed =
193
197
identity?.atp_did &&
194
198
publication?.publication_subscriptions &&
···
229
233
<TagList tags={tags} className="mb-3" />
230
234
</>
231
235
)}
236
236
+
232
237
<hr className="border-border-light mb-3 " />
238
238
+
233
239
<div className="flex gap-2 justify-between">
234
234
-
<div className="flex gap-2">
235
235
-
{props.quotesCount > 0 && (
236
236
-
<button
237
237
-
className="flex w-fit gap-2 items-center px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
238
238
-
onClick={() => {
239
239
-
if (!drawerOpen || drawer !== "quotes")
240
240
-
openInteractionDrawer("quotes", document_uri, props.pageId);
241
241
-
else setInteractionState(document_uri, { drawerOpen: false });
242
242
-
}}
243
243
-
onMouseEnter={handleQuotePrefetch}
244
244
-
onTouchStart={handleQuotePrefetch}
245
245
-
aria-label="Post quotes"
246
246
-
>
247
247
-
<QuoteTiny aria-hidden /> {props.quotesCount}{" "}
248
248
-
<span
249
249
-
aria-hidden
250
250
-
>{`Mention${props.quotesCount === 1 ? "" : "s"}`}</span>
251
251
-
</button>
252
252
-
)}
253
253
-
{props.showComments === false ? null : (
254
254
-
<button
255
255
-
className="flex gap-2 items-center w-fit px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
256
256
-
onClick={() => {
257
257
-
if (
258
258
-
!drawerOpen ||
259
259
-
drawer !== "comments" ||
260
260
-
pageId !== props.pageId
261
261
-
)
262
262
-
openInteractionDrawer("comments", document_uri, props.pageId);
263
263
-
else setInteractionState(document_uri, { drawerOpen: false });
264
264
-
}}
265
265
-
aria-label="Post comments"
266
266
-
>
267
267
-
<CommentTiny aria-hidden />{" "}
268
268
-
{props.commentsCount > 0 ? (
269
269
-
<span aria-hidden>
270
270
-
{`${props.commentsCount} Comment${props.commentsCount === 1 ? "" : "s"}`}
271
271
-
</span>
272
272
-
) : (
273
273
-
"Comment"
240
240
+
{noInteractions ? (
241
241
+
<div />
242
242
+
) : (
243
243
+
<>
244
244
+
<div className="flex gap-2">
245
245
+
{props.quotesCount === 0 ||
246
246
+
props.showMentions === false ? null : (
247
247
+
<button
248
248
+
className="flex w-fit gap-2 items-center px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
249
249
+
onClick={() => {
250
250
+
if (!drawerOpen || drawer !== "quotes")
251
251
+
openInteractionDrawer(
252
252
+
"quotes",
253
253
+
document_uri,
254
254
+
props.pageId,
255
255
+
);
256
256
+
else
257
257
+
setInteractionState(document_uri, { drawerOpen: false });
258
258
+
}}
259
259
+
onMouseEnter={handleQuotePrefetch}
260
260
+
onTouchStart={handleQuotePrefetch}
261
261
+
aria-label="Post quotes"
262
262
+
>
263
263
+
<QuoteTiny aria-hidden /> {props.quotesCount}{" "}
264
264
+
<span
265
265
+
aria-hidden
266
266
+
>{`Mention${props.quotesCount === 1 ? "" : "s"}`}</span>
267
267
+
</button>
274
268
)}
275
275
-
</button>
276
276
-
)}
277
277
-
</div>
269
269
+
{props.showComments === false ? null : (
270
270
+
<button
271
271
+
className="flex gap-2 items-center w-fit px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
272
272
+
onClick={() => {
273
273
+
if (
274
274
+
!drawerOpen ||
275
275
+
drawer !== "comments" ||
276
276
+
pageId !== props.pageId
277
277
+
)
278
278
+
openInteractionDrawer(
279
279
+
"comments",
280
280
+
document_uri,
281
281
+
props.pageId,
282
282
+
);
283
283
+
else
284
284
+
setInteractionState(document_uri, { drawerOpen: false });
285
285
+
}}
286
286
+
aria-label="Post comments"
287
287
+
>
288
288
+
<CommentTiny aria-hidden />{" "}
289
289
+
{props.commentsCount > 0 ? (
290
290
+
<span aria-hidden>
291
291
+
{`${props.commentsCount} Comment${props.commentsCount === 1 ? "" : "s"}`}
292
292
+
</span>
293
293
+
) : (
294
294
+
"Comment"
295
295
+
)}
296
296
+
</button>
297
297
+
)}
298
298
+
</div>
299
299
+
</>
300
300
+
)}
301
301
+
278
302
<EditButton document={data} />
279
303
{subscribed && publication && (
280
304
<ManageSubscription
+7
-2
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx
···
14
14
ExpandedInteractions,
15
15
getCommentCount,
16
16
getQuoteCount,
17
17
-
Interactions,
18
17
} from "./Interactions/Interactions";
19
18
import { PostContent } from "./PostContent";
20
19
import { PostHeader } from "./PostHeader/PostHeader";
···
25
24
import { decodeQuotePosition } from "./quotePosition";
26
25
import { PollData } from "./fetchPollData";
27
26
import { SharedPageProps } from "./PostPages";
27
27
+
import { PostPrevNextButtons } from "./PostPrevNextButtons";
28
28
29
29
export function LinearDocumentPage({
30
30
blocks,
···
56
56
57
57
const isSubpage = !!pageId;
58
58
59
59
+
console.log("prev/next?: " + preferences.showPrevNext);
60
60
+
59
61
return (
60
62
<>
61
63
<PageWrapper
···
83
85
did={did}
84
86
prerenderedCodeBlocks={prerenderedCodeBlocks}
85
87
/>
86
86
-
88
88
+
<PostPrevNextButtons
89
89
+
showPrevNext={preferences.showPrevNext && !isSubpage}
90
90
+
/>
87
91
<ExpandedInteractions
88
92
pageId={pageId}
89
93
showComments={preferences.showComments}
94
94
+
showMentions={preferences.showMentions}
90
95
commentsCount={getCommentCount(document, pageId) || 0}
91
96
quotesCount={getQuoteCount(document, pageId) || 0}
92
97
/>
+2
-1
app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
···
23
23
export function PostHeader(props: {
24
24
data: PostPageData;
25
25
profile: ProfileViewDetailed;
26
26
-
preferences: { showComments?: boolean };
26
26
+
preferences: { showComments?: boolean; showMentions?: boolean };
27
27
}) {
28
28
let { identity } = useIdentityData();
29
29
let document = props.data;
···
91
91
</div>
92
92
<Interactions
93
93
showComments={props.preferences.showComments}
94
94
+
showMentions={props.preferences.showMentions}
94
95
quotesCount={getQuoteCount(document) || 0}
95
96
commentsCount={getCommentCount(document) || 0}
96
97
/>
+22
-4
app/lish/[did]/[publication]/[rkey]/PostPages.tsx
···
147
147
document: PostPageData;
148
148
did: string;
149
149
profile: ProfileViewDetailed;
150
150
-
preferences: { showComments?: boolean };
150
150
+
preferences: {
151
151
+
showComments?: boolean;
152
152
+
showMentions?: boolean;
153
153
+
showPrevNext?: boolean;
154
154
+
};
151
155
pubRecord?: PubLeafletPublication.Record;
152
156
theme?: PubLeafletPublication.Theme | null;
153
157
prerenderedCodeBlocks?: Map<string, string>;
···
206
210
did: string;
207
211
prerenderedCodeBlocks?: Map<string, string>;
208
212
bskyPostData: AppBskyFeedDefs.PostView[];
209
209
-
preferences: { showComments?: boolean };
213
213
+
preferences: {
214
214
+
showComments?: boolean;
215
215
+
showMentions?: boolean;
216
216
+
showPrevNext?: boolean;
217
217
+
};
210
218
pollData: PollData[];
211
219
}) {
212
220
let drawer = useDrawerOpen(document_uri);
···
261
269
262
270
{drawer && !drawer.pageId && (
263
271
<InteractionDrawer
272
272
+
showPageBackground={pubRecord?.theme?.showPageBackground}
264
273
document_uri={document.uri}
265
274
comments={
266
275
pubRecord?.preferences?.showComments === false
267
276
? []
268
277
: document.comments_on_documents
269
278
}
270
270
-
quotesAndMentions={quotesAndMentions}
279
279
+
quotesAndMentions={
280
280
+
pubRecord?.preferences?.showMentions === false
281
281
+
? []
282
282
+
: quotesAndMentions
283
283
+
}
271
284
did={did}
272
285
/>
273
286
)}
···
347
360
/>
348
361
{drawer && drawer.pageId === page.id && (
349
362
<InteractionDrawer
363
363
+
showPageBackground={pubRecord?.theme?.showPageBackground}
350
364
pageId={page.id}
351
365
document_uri={document.uri}
352
366
comments={
···
354
368
? []
355
369
: document.comments_on_documents
356
370
}
357
357
-
quotesAndMentions={quotesAndMentions}
371
371
+
quotesAndMentions={
372
372
+
pubRecord?.preferences?.showMentions === false
373
373
+
? []
374
374
+
: quotesAndMentions
375
375
+
}
358
376
did={did}
359
377
/>
360
378
)}
+58
app/lish/[did]/[publication]/[rkey]/PostPrevNextButtons.tsx
···
1
1
+
"use client";
2
2
+
import { PubLeafletDocument } from "lexicons/api";
3
3
+
import { usePublicationData } from "../dashboard/PublicationSWRProvider";
4
4
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
5
5
+
import { AtUri } from "@atproto/api";
6
6
+
import { useParams } from "next/navigation";
7
7
+
import { getPostPageData } from "./getPostPageData";
8
8
+
import { PostPageContext } from "./PostPageContext";
9
9
+
import { useContext } from "react";
10
10
+
import { SpeedyLink } from "components/SpeedyLink";
11
11
+
import { ArrowRightTiny } from "components/Icons/ArrowRightTiny";
12
12
+
13
13
+
export const PostPrevNextButtons = (props: {
14
14
+
showPrevNext: boolean | undefined;
15
15
+
}) => {
16
16
+
let postData = useContext(PostPageContext);
17
17
+
let pub = postData?.documents_in_publications[0]?.publications;
18
18
+
19
19
+
if (!props.showPrevNext || !pub || !postData) return;
20
20
+
21
21
+
function getPostLink(uri: string) {
22
22
+
return pub && uri
23
23
+
? `${getPublicationURL(pub)}/${new AtUri(uri).rkey}`
24
24
+
: "leaflet.pub/not-found";
25
25
+
}
26
26
+
let prevPost = postData?.prevNext?.prev;
27
27
+
let nextPost = postData?.prevNext?.next;
28
28
+
29
29
+
return (
30
30
+
<div className="flex flex-col gap-1 w-full px-3 sm:px-4 pb-2 pt-2">
31
31
+
{/*<hr className="border-border-light" />*/}
32
32
+
<div className="flex justify-between w-full gap-8 ">
33
33
+
{nextPost ? (
34
34
+
<SpeedyLink
35
35
+
href={getPostLink(nextPost.uri)}
36
36
+
className="flex gap-1 items-center truncate min-w-0 basis-1/2"
37
37
+
>
38
38
+
<ArrowRightTiny className="rotate-180 shrink-0" />
39
39
+
<div className="min-w-0 truncate">{nextPost.title}</div>
40
40
+
</SpeedyLink>
41
41
+
) : (
42
42
+
<div />
43
43
+
)}
44
44
+
{prevPost ? (
45
45
+
<SpeedyLink
46
46
+
href={getPostLink(prevPost.uri)}
47
47
+
className="flex gap-1 items-center truncate min-w-0 basis-1/2 justify-end"
48
48
+
>
49
49
+
<div className="min-w-0 truncate">{prevPost.title}</div>
50
50
+
<ArrowRightTiny className="shrink-0" />
51
51
+
</SpeedyLink>
52
52
+
) : (
53
53
+
<div />
54
54
+
)}
55
55
+
</div>
56
56
+
</div>
57
57
+
);
58
58
+
};
+3
-2
app/lish/[did]/[publication]/[rkey]/QuoteHandler.tsx
···
186
186
<BlueskyLinkTiny className="shrink-0" />
187
187
Bluesky
188
188
</a>
189
189
-
<Separator classname="h-4" />
189
189
+
<Separator classname="h-4!" />
190
190
<button
191
191
id="copy-quote-link"
192
192
className="flex gap-1 items-center hover:font-bold px-1"
···
211
211
</button>
212
212
{pubRecord?.preferences?.showComments !== false && identity?.atp_did && (
213
213
<>
214
214
-
<Separator classname="h-4" />
214
214
+
<Separator classname="h-4! " />
215
215
+
215
216
<button
216
217
className="flex gap-1 items-center hover:font-bold px-1"
217
218
onClick={() => {
+58
-1
app/lish/[did]/[publication]/[rkey]/getPostPageData.ts
···
10
10
data,
11
11
uri,
12
12
comments_on_documents(*, bsky_profiles(*)),
13
13
-
documents_in_publications(publications(*, publication_subscriptions(*))),
13
13
+
documents_in_publications(publications(*,
14
14
+
documents_in_publications(documents(uri, data)),
15
15
+
publication_subscriptions(*))
16
16
+
),
14
17
document_mentions_in_bsky(*),
15
18
leaflets_in_publications(*)
16
19
`,
···
51
54
?.record as PubLeafletPublication.Record
52
55
)?.theme || (document?.data as PubLeafletDocument.Record)?.theme;
53
56
57
57
+
// Calculate prev/next documents from the fetched publication documents
58
58
+
let prevNext:
59
59
+
| {
60
60
+
prev?: { uri: string; title: string };
61
61
+
next?: { uri: string; title: string };
62
62
+
}
63
63
+
| undefined;
64
64
+
65
65
+
const currentPublishedAt = (document.data as PubLeafletDocument.Record)
66
66
+
?.publishedAt;
67
67
+
const allDocs =
68
68
+
document.documents_in_publications[0]?.publications
69
69
+
?.documents_in_publications;
70
70
+
71
71
+
if (currentPublishedAt && allDocs) {
72
72
+
// Filter and sort documents by publishedAt
73
73
+
const sortedDocs = allDocs
74
74
+
.map((dip) => ({
75
75
+
uri: dip?.documents?.uri,
76
76
+
title: (dip?.documents?.data as PubLeafletDocument.Record).title,
77
77
+
publishedAt: (dip?.documents?.data as PubLeafletDocument.Record)
78
78
+
.publishedAt,
79
79
+
}))
80
80
+
.filter((doc) => doc.publishedAt) // Only include docs with publishedAt
81
81
+
.sort(
82
82
+
(a, b) =>
83
83
+
new Date(a.publishedAt!).getTime() -
84
84
+
new Date(b.publishedAt!).getTime(),
85
85
+
);
86
86
+
87
87
+
// Find current document index
88
88
+
const currentIndex = sortedDocs.findIndex((doc) => doc.uri === uri);
89
89
+
90
90
+
if (currentIndex !== -1) {
91
91
+
prevNext = {
92
92
+
prev:
93
93
+
currentIndex > 0
94
94
+
? {
95
95
+
uri: sortedDocs[currentIndex - 1].uri || "",
96
96
+
title: sortedDocs[currentIndex - 1].title,
97
97
+
}
98
98
+
: undefined,
99
99
+
next:
100
100
+
currentIndex < sortedDocs.length - 1
101
101
+
? {
102
102
+
uri: sortedDocs[currentIndex + 1].uri || "",
103
103
+
title: sortedDocs[currentIndex + 1].title,
104
104
+
}
105
105
+
: undefined,
106
106
+
};
107
107
+
}
108
108
+
}
109
109
+
54
110
return {
55
111
...document,
56
112
quotesAndMentions,
57
113
theme,
114
114
+
prevNext,
58
115
};
59
116
}
60
117
+1
app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
···
140
140
commentsCount={comments}
141
141
tags={tags}
142
142
showComments={pubRecord?.preferences?.showComments}
143
143
+
showMentions={pubRecord?.preferences?.showMentions}
143
144
postUrl={`${getPublicationURL(publication)}/${uri.rkey}`}
144
145
/>
145
146
</div>
+31
-25
app/lish/[did]/[publication]/dashboard/settings/PostOptions.tsx
···
22
22
? true
23
23
: record.preferences.showComments,
24
24
);
25
25
-
let [showMentions, setShowMentions] = useState(true);
26
26
-
let [showPrevNext, setShowPrevNext] = useState(true);
25
25
+
let [showMentions, setShowMentions] = useState(
26
26
+
record?.preferences?.showMentions === undefined
27
27
+
? true
28
28
+
: record.preferences.showMentions,
29
29
+
);
30
30
+
let [showPrevNext, setShowPrevNext] = useState(
31
31
+
record?.preferences?.showPrevNext === undefined
32
32
+
? true
33
33
+
: record.preferences.showPrevNext,
34
34
+
);
27
35
28
36
let toast = useToaster();
29
37
return (
30
38
<form
31
39
onSubmit={async (e) => {
32
32
-
// if (!pubData) return;
33
33
-
// e.preventDefault();
34
34
-
// props.setLoading(true);
35
35
-
// let data = await updatePublication({
36
36
-
// uri: pubData.uri,
37
37
-
// name: nameValue,
38
38
-
// description: descriptionValue,
39
39
-
// iconFile: iconFile,
40
40
-
// preferences: {
41
41
-
// showInDiscover: showInDiscover,
42
42
-
// showComments: showComments,
43
43
-
// },
44
44
-
// });
45
45
-
// toast({ type: "success", content: "Posts Updated!" });
46
46
-
// props.setLoading(false);
47
47
-
// mutate("publication-data");
40
40
+
if (!pubData) return;
41
41
+
e.preventDefault();
42
42
+
props.setLoading(true);
43
43
+
let data = await updatePublication({
44
44
+
name: record.name,
45
45
+
uri: pubData.uri,
46
46
+
preferences: {
47
47
+
showInDiscover:
48
48
+
record?.preferences?.showInDiscover === undefined
49
49
+
? true
50
50
+
: record.preferences.showInDiscover,
51
51
+
showComments: showComments,
52
52
+
showMentions: showMentions,
53
53
+
showPrevNext: showPrevNext,
54
54
+
},
55
55
+
});
56
56
+
toast({ type: "success", content: <strong>Posts Updated!</strong> });
57
57
+
console.log(record.preferences?.showPrevNext);
58
58
+
props.setLoading(false);
59
59
+
mutate("publication-data");
48
60
}}
49
61
className="text-primary flex flex-col"
50
62
>
···
57
69
Post Options
58
70
</PubSettingsHeader>
59
71
<h4 className="mb-1">Layout</h4>
60
60
-
{/*<div>Max Post Width</div>*/}
61
72
<Toggle
62
73
toggle={showPrevNext}
63
74
onToggle={() => {
64
75
setShowPrevNext(!showPrevNext);
65
76
}}
66
77
>
67
67
-
<div className="flex flex-col justify-start">
68
68
-
<div className="font-bold">Show Prev/Next Buttons</div>
69
69
-
<div className="text-tertiary text-sm leading-tight">
70
70
-
Show buttons that navigate to the previous and next posts
71
71
-
</div>
72
72
-
</div>
78
78
+
<div className="font-bold">Show Prev/Next Buttons</div>
73
79
</Toggle>
74
80
<hr className="my-2 border-border-light" />
75
81
<h4 className="mb-1">Interactions</h4>
+2
-2
app/lish/[did]/[publication]/dashboard/settings/PublicationSettings.tsx
···
103
103
Theme and Layout
104
104
<ArrowRightTiny />
105
105
</button>
106
106
-
{/*<button
106
106
+
<button
107
107
className={menuItemClassName}
108
108
type="button"
109
109
onClick={() => props.setState("post-options")}
110
110
>
111
111
Post Options
112
112
<ArrowRightTiny />
113
113
-
</button>*/}
113
113
+
</button>
114
114
</div>
115
115
);
116
116
};
+1
app/lish/[did]/[publication]/page.tsx
···
172
172
tags={tags}
173
173
postUrl={`${getPublicationURL(publication)}/${uri.rkey}`}
174
174
showComments={record?.preferences?.showComments}
175
175
+
showMentions={record?.preferences?.showMentions}
175
176
/>
176
177
</div>
177
178
</div>
+9
-2
app/lish/createPub/CreatePubForm.tsx
···
53
53
description: descriptionValue,
54
54
iconFile: logoFile,
55
55
subdomain: domainValue,
56
56
-
preferences: { showInDiscover, showComments: true },
56
56
+
preferences: {
57
57
+
showInDiscover,
58
58
+
showComments: true,
59
59
+
showMentions: true,
60
60
+
showPrevNext: false,
61
61
+
},
57
62
});
58
63
59
64
if (!result.success) {
···
68
73
setTimeout(() => {
69
74
setFormState("normal");
70
75
if (result.publication)
71
71
-
router.push(`${getBasePublicationURL(result.publication)}/dashboard`);
76
76
+
router.push(
77
77
+
`${getBasePublicationURL(result.publication)}/dashboard`,
78
78
+
);
72
79
}, 500);
73
80
}}
74
81
>
+19
-14
app/lish/createPub/UpdatePubForm.tsx
···
21
21
import { Checkbox } from "components/Checkbox";
22
22
import type { GetDomainConfigResponseBody } from "@vercel/sdk/esm/models/getdomainconfigop";
23
23
import { PubSettingsHeader } from "../[did]/[publication]/dashboard/settings/PublicationSettings";
24
24
+
import { Toggle } from "components/Toggle";
24
25
25
26
export const EditPubForm = (props: {
26
27
backToMenuAction: () => void;
···
43
44
? true
44
45
: record.preferences.showComments,
45
46
);
47
47
+
let showMentions =
48
48
+
record?.preferences?.showMentions === undefined
49
49
+
? true
50
50
+
: record.preferences.showMentions;
51
51
+
let showPrevNext =
52
52
+
record?.preferences?.showPrevNext === undefined
53
53
+
? true
54
54
+
: record.preferences.showPrevNext;
55
55
+
46
56
let [descriptionValue, setDescriptionValue] = useState(
47
57
record?.description || "",
48
58
);
···
74
84
preferences: {
75
85
showInDiscover: showInDiscover,
76
86
showComments: showComments,
87
87
+
showMentions: showMentions,
88
88
+
showPrevNext: showPrevNext,
77
89
},
78
90
});
79
91
toast({ type: "success", content: "Updated!" });
···
90
102
General Settings
91
103
</PubSettingsHeader>
92
104
<div className="flex flex-col gap-3 w-[1000px] max-w-full pb-2">
93
93
-
<div className="flex items-center justify-between gap-2 ">
105
105
+
<div className="flex items-center justify-between gap-2 mt-2 ">
94
106
<p className="pl-0.5 pb-0.5 text-tertiary italic text-sm font-bold">
95
107
Logo <span className="font-normal">(optional)</span>
96
108
</p>
···
160
172
<CustomDomainForm />
161
173
<hr className="border-border-light" />
162
174
163
163
-
<Checkbox
164
164
-
checked={showInDiscover}
165
165
-
onChange={(e) => setShowInDiscover(e.target.checked)}
175
175
+
<Toggle
176
176
+
toggle={showInDiscover}
177
177
+
onToggle={() => setShowInDiscover(!showInDiscover)}
166
178
>
167
167
-
<div className=" pt-0.5 flex flex-col text-sm italic text-tertiary ">
179
179
+
<div className=" pt-0.5 flex flex-col text-sm text-tertiary ">
168
180
<p className="font-bold">
169
181
Show In{" "}
170
182
<a href="/discover" target="_blank">
···
179
191
page. You can change this at any time!
180
192
</p>
181
193
</div>
182
182
-
</Checkbox>
194
194
+
</Toggle>
183
195
184
184
-
<Checkbox
185
185
-
checked={showComments}
186
186
-
onChange={(e) => setShowComments(e.target.checked)}
187
187
-
>
188
188
-
<div className=" pt-0.5 flex flex-col text-sm italic text-tertiary ">
189
189
-
<p className="font-bold">Show comments on posts</p>
190
190
-
</div>
191
191
-
</Checkbox>
196
196
+
192
197
</div>
193
198
</form>
194
199
);
+2
-2
app/lish/createPub/updatePublication.ts
···
25
25
}: {
26
26
uri: string;
27
27
name: string;
28
28
-
description: string;
29
29
-
iconFile: File | null;
28
28
+
description?: string;
29
29
+
iconFile?: File | null;
30
30
preferences?: Omit<PubLeafletPublication.Preferences, "$type">;
31
31
}): Promise<UpdatePublicationResult> {
32
32
let identity = await getIdentityData();
+6
-3
components/Canvas.tsx
···
170
170
171
171
let pubRecord = pub.publications.record as PubLeafletPublication.Record;
172
172
let showComments = pubRecord.preferences?.showComments;
173
173
+
let showMentions = pubRecord.preferences?.showMentions;
173
174
174
175
return (
175
176
<div className="flex flex-row gap-3 items-center absolute top-6 right-3 sm:top-4 sm:right-4 bg-bg-page border-border-light rounded-md px-2 py-1 h-fit z-20">
···
178
179
<CommentTiny className="text-border" /> โ
179
180
</div>
180
181
)}
181
181
-
<div className="flex gap-1 text-tertiary items-center">
182
182
-
<QuoteTiny className="text-border" /> โ
183
183
-
</div>
182
182
+
{showComments && (
183
183
+
<div className="flex gap-1 text-tertiary items-center">
184
184
+
<QuoteTiny className="text-border" /> โ
185
185
+
</div>
186
186
+
)}
184
187
185
188
{!props.isSubpage && (
186
189
<>
+4
-2
components/InteractionsPreview.tsx
···
14
14
tags?: string[];
15
15
postUrl: string;
16
16
showComments: boolean | undefined;
17
17
+
showMentions: boolean | undefined;
18
18
+
17
19
share?: boolean;
18
20
}) => {
19
21
let smoker = useSmoker();
20
22
let interactionsAvailable =
21
21
-
props.quotesCount > 0 ||
23
23
+
(props.quotesCount > 0 && props.showMentions !== false) ||
22
24
(props.showComments !== false && props.commentsCount > 0);
23
25
24
26
const tagsCount = props.tags?.length || 0;
···
36
38
</>
37
39
)}
38
40
39
39
-
{props.quotesCount === 0 ? null : (
41
41
+
{props.showMentions === false || props.quotesCount === 0 ? null : (
40
42
<SpeedyLink
41
43
aria-label="Post quotes"
42
44
href={`${props.postUrl}?interactionDrawer=quotes`}
+5
-3
components/Pages/PublicationMetadata.tsx
···
121
121
<Separator classname="h-4!" />
122
122
</>
123
123
)}
124
124
-
<div className="flex gap-1 items-center">
125
125
-
<QuoteTiny />โ
126
126
-
</div>
124
124
+
{pubRecord?.preferences?.showMentions && (
125
125
+
<div className="flex gap-1 items-center">
126
126
+
<QuoteTiny />โ
127
127
+
</div>
128
128
+
)}
127
129
{pubRecord?.preferences?.showComments && (
128
130
<div className="flex gap-1 items-center">
129
131
<CommentTiny />โ
+1
components/PostListing.tsx
···
97
97
commentsCount={comments}
98
98
tags={tags}
99
99
showComments={pubRecord?.preferences?.showComments}
100
100
+
showMentions={pubRecord?.preferences?.showMentions}
100
101
share
101
102
/>
102
103
</div>
+7
-7
components/ThemeManager/PublicationThemeProvider.tsx
···
2
2
import { useMemo, useState } from "react";
3
3
import { parseColor } from "react-aria-components";
4
4
import { useEntity } from "src/replicache";
5
5
-
import { getColorContrast } from "./themeUtils";
5
5
+
import { getColorDifference } from "./themeUtils";
6
6
import { useColorAttribute, colorToString } from "./useColorAttribute";
7
7
import { BaseThemeProvider, CardBorderHiddenContext } from "./ThemeProvider";
8
8
import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
···
174
174
let newAccentContrast;
175
175
let sortedAccents = [newTheme.accent1, newTheme.accent2].sort((a, b) => {
176
176
return (
177
177
-
getColorContrast(
177
177
+
getColorDifference(
178
178
colorToString(b, "rgb"),
179
179
colorToString(
180
180
showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet,
181
181
"rgb",
182
182
),
183
183
) -
184
184
-
getColorContrast(
184
184
+
getColorDifference(
185
185
colorToString(a, "rgb"),
186
186
colorToString(
187
187
showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet,
···
191
191
);
192
192
});
193
193
if (
194
194
-
getColorContrast(
194
194
+
getColorDifference(
195
195
colorToString(sortedAccents[0], "rgb"),
196
196
colorToString(newTheme.primary, "rgb"),
197
197
-
) < 30 &&
198
198
-
getColorContrast(
197
197
+
) < 0.15 &&
198
198
+
getColorDifference(
199
199
colorToString(sortedAccents[1], "rgb"),
200
200
colorToString(
201
201
showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet,
202
202
"rgb",
203
203
),
204
204
-
) > 12
204
204
+
) > 0.08
205
205
) {
206
206
newAccentContrast = sortedAccents[1];
207
207
} else newAccentContrast = sortedAccents[0];
+9
-9
components/ThemeManager/ThemeProvider.tsx
···
22
22
PublicationThemeProvider,
23
23
} from "./PublicationThemeProvider";
24
24
import { PubLeafletPublication } from "lexicons/api";
25
25
-
import { getColorContrast } from "./themeUtils";
25
25
+
import { getColorDifference } from "./themeUtils";
26
26
27
27
// define a function to set an Aria Color to a CSS Variable in RGB
28
28
function setCSSVariableToColor(
···
140
140
//sorting the accents by contrast on background
141
141
let sortedAccents = [accent1, accent2].sort((a, b) => {
142
142
return (
143
143
-
getColorContrast(
143
143
+
getColorDifference(
144
144
colorToString(b, "rgb"),
145
145
colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"),
146
146
) -
147
147
-
getColorContrast(
147
147
+
getColorDifference(
148
148
colorToString(a, "rgb"),
149
149
colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"),
150
150
)
···
156
156
// then use the not contrasty option
157
157
158
158
if (
159
159
-
getColorContrast(
159
159
+
getColorDifference(
160
160
colorToString(sortedAccents[0], "rgb"),
161
161
colorToString(primary, "rgb"),
162
162
-
) < 30 &&
163
163
-
getColorContrast(
162
162
+
) < 0.15 &&
163
163
+
getColorDifference(
164
164
colorToString(sortedAccents[1], "rgb"),
165
165
colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"),
166
166
-
) > 12
166
166
+
) > 0.08
167
167
) {
168
168
accentContrast = sortedAccents[1];
169
169
} else accentContrast = sortedAccents[0];
···
286
286
bgPage && accent1 && accent2
287
287
? [accent1, accent2].sort((a, b) => {
288
288
return (
289
289
-
getColorContrast(
289
289
+
getColorDifference(
290
290
colorToString(b, "rgb"),
291
291
colorToString(bgPage, "rgb"),
292
292
) -
293
293
-
getColorContrast(
293
293
+
getColorDifference(
294
294
colorToString(a, "rgb"),
295
295
colorToString(bgPage, "rgb"),
296
296
)
+2
-3
components/ThemeManager/ThemeSetter.tsx
···
1
1
"use client";
2
2
import { Popover } from "components/Popover";
3
3
-
import { theme } from "../../tailwind.config";
4
3
5
4
import { Color } from "react-aria-components";
6
5
···
166
165
setOpenPicker={(pickers) => setOpenPicker(pickers)}
167
166
/>
168
167
<SectionArrow
169
169
-
fill={theme.colors["accent-2"]}
170
170
-
stroke={theme.colors["accent-1"]}
168
168
+
fill="rgb(var(--accent-2))"
169
169
+
stroke="rgb(var(--accent-1))"
171
170
className="ml-2"
172
171
/>
173
172
</div>
+4
-3
components/ThemeManager/themeUtils.ts
···
1
1
-
import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
1
1
+
import { parse, ColorSpace, sRGB, distance, OKLab } from "colorjs.io/fn";
2
2
3
3
// define the color defaults for everything
4
4
export const ThemeDefaults = {
···
17
17
};
18
18
19
19
// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast
20
20
-
export function getColorContrast(color1: string, color2: string) {
20
20
+
export function getColorDifference(color1: string, color2: string) {
21
21
ColorSpace.register(sRGB);
22
22
+
ColorSpace.register(OKLab);
22
23
23
24
let parsedColor1 = parse(`rgb(${color1})`);
24
25
let parsedColor2 = parse(`rgb(${color2})`);
25
26
26
26
-
return contrastLstar(parsedColor1, parsedColor2);
27
27
+
return distance(parsedColor1, parsedColor2, "oklab");
27
28
}
+8
lexicons/api/lexicons.ts
···
1810
1810
type: 'boolean',
1811
1811
default: true,
1812
1812
},
1813
1813
+
showMentions: {
1814
1814
+
type: 'boolean',
1815
1815
+
default: true,
1816
1816
+
},
1817
1817
+
showPrevNext: {
1818
1818
+
type: 'boolean',
1819
1819
+
default: false,
1820
1820
+
},
1813
1821
},
1814
1822
},
1815
1823
theme: {
+2
lexicons/api/types/pub/leaflet/publication.ts
···
37
37
$type?: 'pub.leaflet.publication#preferences'
38
38
showInDiscover: boolean
39
39
showComments: boolean
40
40
+
showMentions: boolean
41
41
+
showPrevNext: boolean
40
42
}
41
43
42
44
const hashPreferences = 'preferences'
+8
lexicons/pub/leaflet/publication.json
···
51
51
"showComments": {
52
52
"type": "boolean",
53
53
"default": true
54
54
+
},
55
55
+
"showMentions": {
56
56
+
"type": "boolean",
57
57
+
"default": true
58
58
+
},
59
59
+
"showPrevNext": {
60
60
+
"type": "boolean",
61
61
+
"default": false
54
62
}
55
63
}
56
64
},
+2
lexicons/src/publication.ts
···
27
27
properties: {
28
28
showInDiscover: { type: "boolean", default: true },
29
29
showComments: { type: "boolean", default: true },
30
30
+
showMentions: { type: "boolean", default: true },
31
31
+
showPrevNext: { type: "boolean", default: false },
30
32
},
31
33
},
32
34
theme: {