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