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