tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
refactored the footer a bit
cozylittle.house
3 months ago
3f47948b
86b53c38
+170
-359
7 changed files
expand all
collapse all
unified
split
app
(home-pages)
discover
PubListing.tsx
lish
Subscribe.tsx
[did]
[publication]
[rkey]
Interactions
Interactions.tsx
LinearDocumentPage.tsx
PostContent.tsx
PostFooter.tsx
PostPages.tsx
+8
-7
app/(home-pages)/discover/PubListing.tsx
···
1
1
"use client";
2
2
import { AtUri } from "@atproto/syntax";
3
3
import { PublicationSubscription } from "app/(home-pages)/reader/getSubscriptions";
4
4
+
import { SubscribeWithBluesky } from "app/lish/Subscribe";
4
5
import { PubIcon } from "components/ActionBar/Publications";
5
6
import { Separator } from "components/Layout";
6
7
import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider";
···
13
14
export const PubListing = (
14
15
props: PublicationSubscription & {
15
16
resizeHeight?: boolean;
17
17
+
subscribe?: boolean;
16
18
},
17
19
) => {
18
20
let record = props.record as PubLeafletPublication.Record;
···
60
62
<div className="flex flex-row gap-2 items-center">
61
63
{props.authorProfile?.handle}
62
64
</div>
63
63
-
<p>
64
64
-
Updated{" "}
65
65
-
{timeAgo(
66
66
-
props.documents_in_publications?.[0]?.documents?.indexed_at ||
67
67
-
"",
68
68
-
)}
69
69
-
</p>
65
65
+
<SubscribeWithBluesky
66
66
+
pubName={record.name}
67
67
+
pub_uri={props.uri}
68
68
+
base_url={record.base_path ? record.base_path : ""}
69
69
+
subscribers={[]}
70
70
+
/>
70
71
</div>
71
72
</div>
72
73
</a>
+36
-206
app/lish/Subscribe.tsx
···
23
23
import { useSearchParams } from "next/navigation";
24
24
import LoginForm from "app/login/LoginForm";
25
25
import { RSSSmall } from "components/Icons/RSSSmall";
26
26
-
import { SpeedyLink } from "components/SpeedyLink";
27
27
-
28
28
-
type State =
29
29
-
| { state: "email" }
30
30
-
| { state: "code"; token: string }
31
31
-
| { state: "success" };
32
32
-
export const SubscribeButton = (props: {
33
33
-
compact?: boolean;
34
34
-
publication: string;
35
35
-
}) => {
36
36
-
let { identity, mutate } = useIdentityData();
37
37
-
let [emailInputValue, setEmailInputValue] = useState("");
38
38
-
let [codeInputValue, setCodeInputValue] = useState("");
39
39
-
let [state, setState] = useState<State>({ state: "email" });
40
40
-
41
41
-
if (state.state === "email") {
42
42
-
return (
43
43
-
<div className="flex gap-2">
44
44
-
<div className="flex relative w-full max-w-sm">
45
45
-
<Input
46
46
-
type="email"
47
47
-
className="input-with-border pr-[104px]! py-1! grow w-full"
48
48
-
placeholder={
49
49
-
props.compact ? "subscribe with email..." : "email here..."
50
50
-
}
51
51
-
disabled={!!identity?.email}
52
52
-
value={identity?.email ? identity.email : emailInputValue}
53
53
-
onChange={(e) => {
54
54
-
setEmailInputValue(e.currentTarget.value);
55
55
-
}}
56
56
-
/>
57
57
-
<ButtonPrimary
58
58
-
compact
59
59
-
className="absolute right-1 top-1 outline-0!"
60
60
-
onClick={async () => {
61
61
-
if (identity?.email) {
62
62
-
await subscribeToPublicationWithEmail(props.publication);
63
63
-
//optimistically could add!
64
64
-
await mutate();
65
65
-
return;
66
66
-
}
67
67
-
let tokenID = await requestAuthEmailToken(emailInputValue);
68
68
-
setState({ state: "code", token: tokenID });
69
69
-
}}
70
70
-
>
71
71
-
{props.compact ? (
72
72
-
<ArrowRightTiny className="w-4 h-6" />
73
73
-
) : (
74
74
-
"Subscribe"
75
75
-
)}
76
76
-
</ButtonPrimary>
77
77
-
</div>
78
78
-
{/* <ShareButton /> */}
79
79
-
</div>
80
80
-
);
81
81
-
}
82
82
-
if (state.state === "code") {
83
83
-
return (
84
84
-
<div
85
85
-
className="w-full flex flex-col justify-center place-items-center p-4 rounded-md"
86
86
-
style={{
87
87
-
background:
88
88
-
"color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)",
89
89
-
}}
90
90
-
>
91
91
-
<div className="flex flex-col leading-snug text-secondary">
92
92
-
<div>Please enter the code we sent to </div>
93
93
-
<div className="italic font-bold">{emailInputValue}</div>
94
94
-
</div>
95
95
-
96
96
-
<ConfirmCodeInput
97
97
-
publication={props.publication}
98
98
-
token={state.token}
99
99
-
codeInputValue={codeInputValue}
100
100
-
setCodeInputValue={setCodeInputValue}
101
101
-
setState={setState}
102
102
-
/>
103
103
-
104
104
-
<button
105
105
-
className="text-accent-contrast text-sm mt-1"
106
106
-
onClick={() => {
107
107
-
setState({ state: "email" });
108
108
-
}}
109
109
-
>
110
110
-
Re-enter Email
111
111
-
</button>
112
112
-
</div>
113
113
-
);
114
114
-
}
115
115
-
116
116
-
if (state.state === "success") {
117
117
-
return (
118
118
-
<div
119
119
-
className={`w-full flex flex-col gap-2 justify-center place-items-center p-4 rounded-md text-secondary ${props.compact ? "py-1 animate-bounce" : "p-4"}`}
120
120
-
style={{
121
121
-
background:
122
122
-
"color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)",
123
123
-
}}
124
124
-
>
125
125
-
<div className="flex gap-2 leading-snug font-bold italic">
126
126
-
<div>You're subscribed!</div>
127
127
-
{/* <ShareButton /> */}
128
128
-
</div>
129
129
-
</div>
130
130
-
);
131
131
-
}
132
132
-
};
133
133
-
134
134
-
export const ShareButton = () => {
135
135
-
return (
136
136
-
<button className="text-accent-contrast">
137
137
-
<ShareSmall />
138
138
-
</button>
139
139
-
);
140
140
-
};
141
141
-
142
142
-
const ConfirmCodeInput = (props: {
143
143
-
codeInputValue: string;
144
144
-
token: string;
145
145
-
setCodeInputValue: (value: string) => void;
146
146
-
setState: (state: State) => void;
147
147
-
publication: string;
148
148
-
}) => {
149
149
-
let { mutate } = useIdentityData();
150
150
-
return (
151
151
-
<div className="relative w-fit mt-2">
152
152
-
<Input
153
153
-
type="text"
154
154
-
pattern="[0-9]"
155
155
-
className="input-with-border pr-[88px]! py-1! max-w-[156px]"
156
156
-
placeholder="000000"
157
157
-
value={props.codeInputValue}
158
158
-
onChange={(e) => {
159
159
-
props.setCodeInputValue(e.currentTarget.value);
160
160
-
}}
161
161
-
/>
162
162
-
<ButtonPrimary
163
163
-
compact
164
164
-
className="absolute right-1 top-1 outline-0!"
165
165
-
onClick={async () => {
166
166
-
console.log(
167
167
-
await confirmEmailAuthToken(props.token, props.codeInputValue),
168
168
-
);
169
169
-
170
170
-
await subscribeToPublicationWithEmail(props.publication);
171
171
-
//optimistically could add!
172
172
-
await mutate();
173
173
-
props.setState({ state: "success" });
174
174
-
return;
175
175
-
}}
176
176
-
>
177
177
-
Confirm
178
178
-
</ButtonPrimary>
179
179
-
</div>
180
180
-
);
181
181
-
};
182
26
183
27
export const SubscribeWithBluesky = (props: {
184
184
-
isPost?: boolean;
185
28
pubName: string;
186
29
pub_uri: string;
187
30
base_url: string;
···
208
51
}
209
52
return (
210
53
<div className="flex flex-col gap-2 text-center justify-center">
211
211
-
{props.isPost && (
212
212
-
<div className="text-sm text-tertiary font-bold">
213
213
-
Get updates from {props.pubName}!
214
214
-
</div>
215
215
-
)}
216
54
<div className="flex flex-row gap-2 place-self-center">
217
55
<BlueskySubscribeButton
218
56
pub_uri={props.pub_uri}
···
231
69
);
232
70
};
233
71
234
234
-
const ManageSubscription = (props: {
235
235
-
isPost?: boolean;
236
236
-
pubName: string;
72
72
+
export const ManageSubscription = (props: {
237
73
pub_uri: string;
238
74
subscribers: { identity: string }[];
239
75
base_url: string;
···
248
84
});
249
85
}, null);
250
86
return (
251
251
-
<div
252
252
-
className={`flex ${props.isPost ? "flex-col " : "gap-2"} justify-center text-center`}
87
87
+
<Popover
88
88
+
trigger={
89
89
+
<div className="text-accent-contrast text-sm">Manage Subscription</div>
90
90
+
}
253
91
>
254
254
-
<div className="font-bold text-tertiary text-sm">
255
255
-
You're Subscribed{props.isPost ? ` to ` : "!"}
256
256
-
{props.isPost && (
257
257
-
<SpeedyLink href={props.base_url} className="text-accent-contrast">
258
258
-
{props.pubName}
259
259
-
</SpeedyLink>
260
260
-
)}
261
261
-
</div>
262
262
-
<Popover
263
263
-
trigger={<div className="text-accent-contrast text-sm">Manage</div>}
264
264
-
>
265
265
-
<div className="max-w-sm flex flex-col gap-1">
266
266
-
<h4>Update Options</h4>
92
92
+
<div className="max-w-sm flex flex-col gap-1">
93
93
+
<h4>Update Options</h4>
267
94
268
268
-
{!hasFeed && (
269
269
-
<a
270
270
-
href="https://bsky.app/profile/leaflet.pub/feed/subscribedPublications"
271
271
-
target="_blank"
272
272
-
className=" place-self-center"
273
273
-
>
274
274
-
<ButtonPrimary fullWidth compact className="!px-4">
275
275
-
View Bluesky Custom Feed
276
276
-
</ButtonPrimary>
277
277
-
</a>
278
278
-
)}
279
279
-
95
95
+
{!hasFeed && (
280
96
<a
281
281
-
href={`${props.base_url}/rss`}
282
282
-
className="flex"
97
97
+
href="https://bsky.app/profile/leaflet.pub/feed/subscribedPublications"
283
98
target="_blank"
284
284
-
aria-label="Subscribe to RSS"
99
99
+
className=" place-self-center"
285
100
>
286
286
-
<ButtonPrimary fullWidth compact>
287
287
-
Get RSS
101
101
+
<ButtonPrimary fullWidth compact className="!px-4">
102
102
+
View Bluesky Custom Feed
288
103
</ButtonPrimary>
289
104
</a>
105
105
+
)}
290
106
291
291
-
<hr className="border-border-light my-1" />
107
107
+
<a
108
108
+
href={`${props.base_url}/rss`}
109
109
+
className="flex"
110
110
+
target="_blank"
111
111
+
aria-label="Subscribe to RSS"
112
112
+
>
113
113
+
<ButtonPrimary fullWidth compact>
114
114
+
Get RSS
115
115
+
</ButtonPrimary>
116
116
+
</a>
292
117
293
293
-
<form action={unsubscribe}>
294
294
-
<button className="font-bold text-accent-contrast w-max place-self-center">
295
295
-
{unsubscribePending ? <DotLoader /> : "Unsubscribe"}
296
296
-
</button>
297
297
-
</form>
298
298
-
</div>{" "}
299
299
-
</Popover>
300
300
-
</div>
118
118
+
<hr className="border-border-light my-1" />
119
119
+
120
120
+
<form action={unsubscribe}>
121
121
+
<button className="font-bold text-accent-contrast w-max place-self-center">
122
122
+
{unsubscribePending ? <DotLoader /> : "Unsubscribe"}
123
123
+
</button>
124
124
+
</form>
125
125
+
</div>
126
126
+
</Popover>
301
127
);
302
128
};
303
129
···
430
256
</Dialog.Root>
431
257
);
432
258
};
259
259
+
260
260
+
export const SubscribeOnPost = () => {
261
261
+
return <div></div>;
262
262
+
};
+123
-43
app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx
···
13
13
import { Tag } from "components/Tags";
14
14
import { Popover } from "components/Popover";
15
15
import { PostPageData } from "../getPostPageData";
16
16
-
import { PubLeafletComment } from "lexicons/api";
16
16
+
import { PubLeafletComment, PubLeafletPublication } from "lexicons/api";
17
17
import { prefetchQuotesData } from "./Quotes";
18
18
+
import { useIdentityData } from "components/IdentityProvider";
19
19
+
import { ManageSubscription, SubscribeWithBluesky } from "app/lish/Subscribe";
20
20
+
import { EditTiny } from "components/Icons/EditTiny";
21
21
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
22
22
+
import { PubListing } from "app/(home-pages)/discover/PubListing";
23
23
+
import { PubIcon } from "components/ActionBar/Publications";
18
24
19
25
export type InteractionState = {
20
26
drawerOpen: undefined | boolean;
···
108
114
}) => {
109
115
const data = useContext(PostPageContext);
110
116
const document_uri = data?.uri;
117
117
+
let { identity } = useIdentityData();
111
118
if (!document_uri)
112
119
throw new Error("document_uri not available in PostPageContext");
113
120
···
121
128
122
129
const tags = (data?.data as any)?.tags as string[] | undefined;
123
130
const tagCount = tags?.length || 0;
131
131
+
124
132
return (
125
133
<div className={`flex gap-2 text-tertiary text-sm ${props.className}`}>
126
134
{tagCount > 0 && <TagPopover tags={tags} tagCount={tagCount} />}
···
165
173
pageId?: string;
166
174
}) => {
167
175
const data = useContext(PostPageContext);
176
176
+
let { identity } = useIdentityData();
177
177
+
168
178
const document_uri = data?.uri;
169
179
if (!document_uri)
170
180
throw new Error("document_uri not available in PostPageContext");
···
176
186
prefetchQuotesData(data.quotesAndMentions);
177
187
}
178
188
};
189
189
+
let publication = data?.documents_in_publications[0]?.publications;
179
190
180
191
const tags = (data?.data as any)?.tags as string[] | undefined;
181
192
const tagCount = tags?.length || 0;
193
193
+
194
194
+
let subscribed =
195
195
+
identity?.atp_did &&
196
196
+
publication?.publication_subscriptions &&
197
197
+
publication?.publication_subscriptions.find(
198
198
+
(s) => s.identity === identity.atp_did,
199
199
+
);
200
200
+
201
201
+
let isAuthor =
202
202
+
identity &&
203
203
+
identity.atp_did ===
204
204
+
data.documents_in_publications[0]?.publications?.identity_did &&
205
205
+
data.leaflets_in_publications[0];
206
206
+
182
207
return (
183
208
<div
184
184
-
className={`gap-2 text-tertiary px-3 sm:px-4 flex flex-col ${props.className}`}
209
209
+
className={`text-tertiary px-3 sm:px-4 flex flex-col ${props.className}`}
185
210
>
211
211
+
{!subscribed && !isAuthor && publication && publication.record && (
212
212
+
<div className="text-center flex flex-col accent-container rounded-md mb-3">
213
213
+
<div className="flex flex-col py-4">
214
214
+
<div className="leading-snug flex flex-col pb-2 text-sm">
215
215
+
<div className="font-bold">Subscribe to {publication.name}</div>{" "}
216
216
+
to get updates in Reader, RSS, or via Bluesky Feed
217
217
+
</div>
218
218
+
<SubscribeWithBluesky
219
219
+
pubName={publication.name}
220
220
+
pub_uri={publication.uri}
221
221
+
base_url={
222
222
+
(publication.record as PubLeafletPublication.Record)
223
223
+
.base_path || ""
224
224
+
}
225
225
+
subscribers={publication?.publication_subscriptions}
226
226
+
/>
227
227
+
</div>
228
228
+
</div>
229
229
+
)}
186
230
{tagCount > 0 && (
187
231
<>
188
188
-
<hr className="border-border-light mb-1 " />
189
189
-
<TagList tags={tags} />
190
190
-
<hr className="border-border-light mt-1 " />
232
232
+
<hr className="border-border-light mb-3" />
233
233
+
234
234
+
<TagList tags={tags} className="mb-3" />
191
235
</>
192
236
)}
193
193
-
194
194
-
{props.quotesCount > 0 && (
195
195
-
<button
196
196
-
className="flex w-fit gap-2 items-center px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
197
197
-
onClick={() => {
198
198
-
if (!drawerOpen || drawer !== "quotes")
199
199
-
openInteractionDrawer("quotes", document_uri, props.pageId);
200
200
-
else setInteractionState(document_uri, { drawerOpen: false });
201
201
-
}}
202
202
-
onMouseEnter={handleQuotePrefetch}
203
203
-
onTouchStart={handleQuotePrefetch}
204
204
-
aria-label="Post quotes"
205
205
-
>
206
206
-
<QuoteTiny aria-hidden /> {props.quotesCount}{" "}
207
207
-
<span
208
208
-
aria-hidden
209
209
-
>{`Quote${props.quotesCount === 1 ? "" : "s"}`}</span>
210
210
-
</button>
211
211
-
)}
212
212
-
{props.showComments === false ? null : (
213
213
-
<button
214
214
-
className="flex gap-2 items-center w-fit px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
215
215
-
onClick={() => {
216
216
-
if (!drawerOpen || drawer !== "comments" || pageId !== props.pageId)
217
217
-
openInteractionDrawer("comments", document_uri, props.pageId);
218
218
-
else setInteractionState(document_uri, { drawerOpen: false });
219
219
-
}}
220
220
-
aria-label="Post comments"
221
221
-
>
222
222
-
<CommentTiny aria-hidden />{" "}
223
223
-
{props.commentsCount > 0 ? (
224
224
-
<span aria-hidden>
225
225
-
{`${props.commentsCount} Comment${props.commentsCount === 1 ? "" : "s"}`}
226
226
-
</span>
227
227
-
) : (
228
228
-
"Comment"
237
237
+
<hr className="border-border-light mb-3 " />
238
238
+
<div className="flex gap-2 justify-between">
239
239
+
<div className="flex gap-2">
240
240
+
{props.quotesCount > 0 && (
241
241
+
<button
242
242
+
className="flex w-fit gap-2 items-center px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
243
243
+
onClick={() => {
244
244
+
if (!drawerOpen || drawer !== "quotes")
245
245
+
openInteractionDrawer("quotes", document_uri, props.pageId);
246
246
+
else setInteractionState(document_uri, { drawerOpen: false });
247
247
+
}}
248
248
+
onMouseEnter={handleQuotePrefetch}
249
249
+
onTouchStart={handleQuotePrefetch}
250
250
+
aria-label="Post quotes"
251
251
+
>
252
252
+
<QuoteTiny aria-hidden /> {props.quotesCount}{" "}
253
253
+
<span
254
254
+
aria-hidden
255
255
+
>{`Quote${props.quotesCount === 1 ? "" : "s"}`}</span>
256
256
+
</button>
257
257
+
)}
258
258
+
{props.showComments === false ? null : (
259
259
+
<button
260
260
+
className="flex gap-2 items-center w-fit px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"
261
261
+
onClick={() => {
262
262
+
if (
263
263
+
!drawerOpen ||
264
264
+
drawer !== "comments" ||
265
265
+
pageId !== props.pageId
266
266
+
)
267
267
+
openInteractionDrawer("comments", document_uri, props.pageId);
268
268
+
else setInteractionState(document_uri, { drawerOpen: false });
269
269
+
}}
270
270
+
aria-label="Post comments"
271
271
+
>
272
272
+
<CommentTiny aria-hidden />{" "}
273
273
+
{props.commentsCount > 0 ? (
274
274
+
<span aria-hidden>
275
275
+
{`${props.commentsCount} Comment${props.commentsCount === 1 ? "" : "s"}`}
276
276
+
</span>
277
277
+
) : (
278
278
+
"Comment"
279
279
+
)}
280
280
+
</button>
229
281
)}
230
230
-
</button>
231
231
-
)}
282
282
+
</div>
283
283
+
<EditButton document={data} />
284
284
+
{subscribed && publication && (
285
285
+
<ManageSubscription
286
286
+
base_url={getPublicationURL(publication)}
287
287
+
pub_uri={publication.uri}
288
288
+
subscribers={publication.publication_subscriptions}
289
289
+
/>
290
290
+
)}
291
291
+
</div>
232
292
</div>
233
293
);
234
294
};
···
298
358
(c) => !(c.record as PubLeafletComment.Record)?.onPage,
299
359
).length;
300
360
}
361
361
+
362
362
+
const EditButton = (props: { document: PostPageData }) => {
363
363
+
let { identity } = useIdentityData();
364
364
+
if (!props.document) return;
365
365
+
if (
366
366
+
identity &&
367
367
+
identity.atp_did ===
368
368
+
props.document.documents_in_publications[0]?.publications?.identity_did &&
369
369
+
props.document.leaflets_in_publications[0]
370
370
+
)
371
371
+
return (
372
372
+
<a
373
373
+
href={`https://leaflet.pub/${props.document.leaflets_in_publications[0]?.leaflet}`}
374
374
+
className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1"
375
375
+
>
376
376
+
<EditTiny /> Edit Post
377
377
+
</a>
378
378
+
);
379
379
+
return;
380
380
+
};
+2
-38
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx
···
48
48
fullPageScroll,
49
49
hasPageBackground,
50
50
} = props;
51
51
-
let { identity } = useIdentityData();
52
51
let drawer = useDrawerOpen(document_uri);
53
52
54
53
if (!document) return null;
···
85
84
did={did}
86
85
prerenderedCodeBlocks={prerenderedCodeBlocks}
87
86
/>
87
87
+
88
88
<ExpandedInteractions
89
89
pageId={pageId}
90
90
showComments={preferences.showComments}
91
91
commentsCount={getCommentCount(document, pageId) || 0}
92
92
quotesCount={getQuoteCount(document, pageId) || 0}
93
93
/>
94
94
-
{!isSubpage && (
95
95
-
<>
96
96
-
<div className="sm:px-4 px-3">
97
97
-
{identity &&
98
98
-
identity.atp_did ===
99
99
-
document.documents_in_publications[0]?.publications
100
100
-
?.identity_did &&
101
101
-
document.leaflets_in_publications[0] ? (
102
102
-
<a
103
103
-
href={`https://leaflet.pub/${document.leaflets_in_publications[0]?.leaflet}`}
104
104
-
className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1 mx-auto"
105
105
-
>
106
106
-
<EditTiny /> Edit Post
107
107
-
</a>
108
108
-
) : (
109
109
-
document.documents_in_publications[0]?.publications && (
110
110
-
<SubscribeWithBluesky
111
111
-
isPost
112
112
-
base_url={getPublicationURL(
113
113
-
document.documents_in_publications[0].publications,
114
114
-
)}
115
115
-
pub_uri={
116
116
-
document.documents_in_publications[0].publications.uri
117
117
-
}
118
118
-
subscribers={
119
119
-
document.documents_in_publications[0].publications
120
120
-
.publication_subscriptions
121
121
-
}
122
122
-
pubName={
123
123
-
document.documents_in_publications[0].publications.name
124
124
-
}
125
125
-
/>
126
126
-
)
127
127
-
)}
128
128
-
</div>
129
129
-
</>
130
130
-
)}
94
94
+
{!hasPageBackground && <div className={`spacer h-8 w-full`} />}
131
95
</PageWrapper>
132
96
</>
133
97
);
+1
-1
app/lish/[did]/[publication]/[rkey]/PostContent.tsx
···
59
59
return (
60
60
<div
61
61
//The postContent class is important for QuoteHandler
62
62
-
className={`postContent flex flex-col sm:px-4 px-3 sm:pt-3 pt-2 pb-1 sm:pb-6 ${className}`}
62
62
+
className={`postContent flex flex-col sm:px-4 px-3 sm:pt-3 pt-2 pb-1 sm:pb-4 ${className}`}
63
63
>
64
64
{blocks.map((b, index) => {
65
65
return (
-63
app/lish/[did]/[publication]/[rkey]/PostFooter.tsx
···
1
1
-
import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
2
2
-
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
3
3
-
import { SubscribeWithBluesky } from "app/lish/Subscribe";
4
4
-
import { EditTiny } from "components/Icons/EditTiny";
5
5
-
import { useIdentityData } from "components/IdentityProvider";
6
6
-
import { PubLeafletComment } from "lexicons/api";
7
7
-
import { PostPageData } from "./getPostPageData";
8
8
-
import { ExpandedInteractions } from "./Interactions/Interactions";
9
9
-
import { decodeQuotePosition } from "./quotePosition";
10
10
-
11
11
-
export const PostFooter = (props: {
12
12
-
data: PostPageData;
13
13
-
profile: ProfileViewDetailed;
14
14
-
preferences: { showComments?: boolean };
15
15
-
}) => {
16
16
-
let { identity } = useIdentityData();
17
17
-
if (!props.data || !props.data.documents_in_publications[0].publications)
18
18
-
return;
19
19
-
return (
20
20
-
<div className="flex flex-col px-3 sm:px-4">
21
21
-
<ExpandedInteractions
22
22
-
showComments={props.preferences.showComments}
23
23
-
quotesCount={
24
24
-
props.data.document_mentions_in_bsky.filter((q) => {
25
25
-
const url = new URL(q.link);
26
26
-
const quoteParam = url.pathname.split("/l-quote/")[1];
27
27
-
if (!quoteParam) return null;
28
28
-
const quotePosition = decodeQuotePosition(quoteParam);
29
29
-
return !quotePosition?.pageId;
30
30
-
}).length
31
31
-
}
32
32
-
commentsCount={
33
33
-
props.data.comments_on_documents.filter(
34
34
-
(c) => !(c.record as PubLeafletComment.Record)?.onPage,
35
35
-
).length
36
36
-
}
37
37
-
/>
38
38
-
{identity &&
39
39
-
identity.atp_did ===
40
40
-
props.data.documents_in_publications[0]?.publications?.identity_did ? (
41
41
-
<a
42
42
-
href={`https://leaflet.pub/${props.data.leaflets_in_publications[0]?.leaflet}`}
43
43
-
className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1 mx-auto"
44
44
-
>
45
45
-
<EditTiny /> Edit Post
46
46
-
</a>
47
47
-
) : (
48
48
-
<SubscribeWithBluesky
49
49
-
isPost
50
50
-
base_url={getPublicationURL(
51
51
-
props.data.documents_in_publications[0].publications,
52
52
-
)}
53
53
-
pub_uri={props.data.documents_in_publications[0].publications.uri}
54
54
-
subscribers={
55
55
-
props.data.documents_in_publications[0].publications
56
56
-
.publication_subscriptions
57
57
-
}
58
58
-
pubName={props.data.documents_in_publications[0].publications.name}
59
59
-
/>
60
60
-
)}
61
61
-
</div>
62
62
-
);
63
63
-
};
-1
app/lish/[did]/[publication]/[rkey]/PostPages.tsx
···
21
21
import { scrollIntoView } from "src/utils/scrollIntoView";
22
22
import { useParams } from "next/navigation";
23
23
import { decodeQuotePosition } from "./quotePosition";
24
24
-
import { PostFooter } from "./PostFooter";
25
24
import { PollData } from "./fetchPollData";
26
25
import { LinearDocumentPage } from "./LinearDocumentPage";
27
26
import { CanvasPage } from "./CanvasPage";