Skip to content

Commit b332807

Browse files
committed
Merge branch 'preview'
2 parents daf992b + 3c7df6c commit b332807

File tree

13 files changed

+299
-38
lines changed

13 files changed

+299
-38
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ouranos",
3-
"version": "0.6.0",
3+
"version": "0.7.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

src/components/actions/composePrompt/ComposePrompt.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ interface Props {
1212

1313
export default function ComposePrompt(props: Props) {
1414
const { avatar, post, rounded } = props;
15+
const canReply = !post.viewer?.replyDisabled ?? false;
1516
const { openComposer } = useComposerControls();
1617

18+
if (!canReply) return null;
19+
1720
return (
1821
<button
1922
onClick={(e) => {
@@ -33,7 +36,7 @@ export default function ComposePrompt(props: Props) {
3336
},
3437
});
3538
}}
36-
className={`flex items-center w-full gap-3 px-3 py-2 border-x-0 md:border-x hover:bg-neutral-50 ${
39+
className={`flex w-full items-center gap-3 border-x-0 px-3 py-2 hover:bg-neutral-50 md:border-x ${
3740
rounded ? "border md:rounded-b-2xl" : "border-t"
3841
}`}
3942
>

src/components/contentDisplay/threadPost/ThreadPost.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ export default function ThreadPost(props: Props) {
3131
adultContentFilters.find((f) => f.values.includes(label || embedLabel))
3232
?.message ?? "Marked content";
3333
const visibility = adultContentFilters.find((f) =>
34-
f.values.includes(label || embedLabel)
34+
f.values.includes(label || embedLabel),
3535
)?.visibility;
3636

3737
const [hidden, setHidden] = useState(
3838
isAdultContentHidden
3939
? true
4040
: visibility === "hide" || visibility === "warn"
41-
? true
42-
: false
41+
? true
42+
: false,
4343
);
4444

4545
const router = useRouter();
@@ -55,7 +55,7 @@ export default function ThreadPost(props: Props) {
5555
return (
5656
<article
5757
ref={threadPostRef}
58-
className="p-3 md:border-x border-t last:border-b md:last:rounded-b-2xl"
58+
className="border-t p-3 last:border-b md:border-x md:last:rounded-b-2xl"
5959
>
6060
<div className="relative flex items-start gap-3">
6161
<button
@@ -67,7 +67,7 @@ export default function ThreadPost(props: Props) {
6767
>
6868
<Avatar src={author.avatar} size="md" />
6969
</button>
70-
<div className="flex flex-col grow">
70+
<div className="flex grow flex-col">
7171
<div className="flex flex-col">
7272
<Link
7373
href={`/dashboard/user/${author.handle}`}
@@ -76,11 +76,11 @@ export default function ThreadPost(props: Props) {
7676
}}
7777
className="flex gap-1"
7878
>
79-
<span className="font-semibold break-all max-w-[90%] shrink-0 line-clamp-1 overflow-ellipsis text-neutral-700 hover:text-neutral-500">
79+
<span className="line-clamp-1 max-w-[90%] shrink-0 overflow-ellipsis break-all font-semibold text-neutral-700 hover:text-neutral-500">
8080
{author.displayName ?? author.handle}{" "}
8181
</span>
8282
</Link>
83-
<span className="text-neutral-400 font-medium line-clamp-1 break-all shrink min-w-[10%]">
83+
<span className="line-clamp-1 min-w-[10%] shrink break-all font-medium text-neutral-400">
8484
@{author.handle}
8585
</span>
8686
</div>
@@ -103,7 +103,7 @@ export default function ThreadPost(props: Props) {
103103
{!hidden && (
104104
<>{post.embed && <PostEmbed content={post.embed} depth={0} />}</>
105105
)}
106-
<div className="mt-3 text-neutral-400 font-medium">
106+
<div className="mt-3 font-medium text-neutral-400">
107107
{getFormattedDate(post.indexedAt)}
108108
</div>
109109
</div>

src/components/dataDisplay/postActions/PostActions.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ export default function PostActions(props: Props) {
6565
if (mode === "thread") {
6666
return (
6767
<div>
68-
<div className="flex flex-wrap items-center gap-3 mt-3 border-y p-2">
68+
<div className="mt-3 flex flex-wrap items-center gap-3 border-y p-2">
6969
<Link
7070
href={`/dashboard/user/${post.author.handle}/post/${getPostId(
71-
post.uri
71+
post.uri,
7272
)}/reposted-by`}
7373
className="flex gap-1 font-semibold text-neutral-700 hover:brightness-110"
7474
>
@@ -79,7 +79,7 @@ export default function PostActions(props: Props) {
7979
</Link>
8080
<Link
8181
href={`/dashboard/user/${post.author.handle}/post/${getPostId(
82-
post.uri
82+
post.uri,
8383
)}/liked-by`}
8484
className="flex gap-1 font-semibold text-neutral-700 hover:brightness-110"
8585
>
@@ -89,8 +89,9 @@ export default function PostActions(props: Props) {
8989
</span>
9090
</Link>
9191
</div>
92-
<div className="flex gap-x-8 mt-3">
92+
<div className="mt-3 flex gap-x-8">
9393
<Button
94+
disabled={post.viewer?.replyDisabled}
9495
onClick={(e) => {
9596
e.stopPropagation();
9697
const text =
@@ -109,7 +110,7 @@ export default function PostActions(props: Props) {
109110
},
110111
});
111112
}}
112-
className="text-neutral-500 hover:text-primary"
113+
className="hover:text-primary text-neutral-500"
113114
>
114115
<BiMessageRounded className="text-lg" />
115116
</Button>
@@ -229,6 +230,7 @@ export default function PostActions(props: Props) {
229230
return (
230231
<div className="flex gap-x-8">
231232
<Button
233+
disabled={post.viewer?.replyDisabled}
232234
onClick={(e) => {
233235
e.stopPropagation();
234236

@@ -248,7 +250,7 @@ export default function PostActions(props: Props) {
248250
},
249251
});
250252
}}
251-
className="text-sm font-medium text-neutral-500 hover:text-primary"
253+
className="hover:text-primary text-sm font-medium text-neutral-500"
252254
>
253255
<BiMessageRounded className="text-lg" />
254256
{post.replyCount}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { AppBskyFeedDefs, AppBskyFeedThreadgate } from "@atproto/api";
2+
import Link from "next/link";
3+
import { ReactElement } from "react";
4+
import { BiMessageRoundedEdit } from "react-icons/bi";
5+
6+
interface Props {
7+
post: AppBskyFeedDefs.PostView;
8+
}
9+
10+
export default function WhoCanReply(props: Props) {
11+
const { post } = props;
12+
const canReply = !post.viewer?.replyDisabled ?? false;
13+
const rounded = !canReply && post.replyCount === 0;
14+
const record =
15+
post.threadgate &&
16+
AppBskyFeedThreadgate.isRecord(post.threadgate.record) &&
17+
AppBskyFeedThreadgate.validateRecord(post.threadgate.record).success
18+
? post.threadgate.record
19+
: null;
20+
21+
if (!record) return null;
22+
23+
const getLabel = () => {
24+
if (!record.allow || record.allow.length === 0) {
25+
return (
26+
<p className="text-sm text-neutral-800">
27+
Replies to this thread are disabled
28+
</p>
29+
);
30+
}
31+
32+
let label: ReactElement = <></>;
33+
const rules = record.allow;
34+
let mention = false;
35+
let follow = false;
36+
let lists;
37+
38+
for (const rule of rules) {
39+
if (
40+
AppBskyFeedThreadgate.isMentionRule(rule) &&
41+
AppBskyFeedThreadgate.isFollowingRule(rule)
42+
) {
43+
mention = true;
44+
follow = true;
45+
} else if (AppBskyFeedThreadgate.isFollowingRule(rule)) {
46+
follow = true;
47+
} else if (AppBskyFeedThreadgate.isMentionRule(rule)) {
48+
mention = true;
49+
} else if (AppBskyFeedThreadgate.isListRule(rule)) {
50+
lists = post.threadgate?.lists;
51+
}
52+
}
53+
54+
return (
55+
<p className="text-sm text-neutral-800">
56+
{follow && mention && (
57+
<>
58+
Users followed and mentioned by{" "}
59+
<Link
60+
href={`/dashboard/user/${post.author.handle}`}
61+
className="text-primary hover:text-primary-dark font-medium"
62+
>
63+
{post.author.handle}
64+
</Link>{" "}
65+
</>
66+
)}
67+
{follow && !mention && (
68+
<>
69+
Users followed by{" "}
70+
<Link
71+
href={`/dashboard/user/${post.author.handle}`}
72+
className="text-primary hover:text-primary-dark font-medium"
73+
>
74+
{post.author.handle}
75+
</Link>{" "}
76+
</>
77+
)}
78+
{mention && !follow && <>Mentioned users</>}
79+
{lists && lists.length > 0 && (
80+
<>
81+
{follow || mention ? " and members " : "Members "}
82+
in the following {lists.length > 1 ? "lists" : "list"} can reply:{" "}
83+
{lists.map((list, i) => (
84+
<Link
85+
key={list.uri}
86+
href={{
87+
pathname: `/dashboard/user/${post.author.handle}/lists/${encodeURIComponent(
88+
list.uri.split(":")[3].split("/")[2],
89+
)}`,
90+
query: { uri: list.uri },
91+
}}
92+
className="text-primary hover:text-primary-dark font-medium"
93+
>
94+
{list.name}
95+
{i < lists?.length - 1 && ", "}
96+
</Link>
97+
))}
98+
</>
99+
)}
100+
{!lists && <> can reply</>}
101+
</p>
102+
);
103+
};
104+
105+
return (
106+
<section
107+
className={`bg-primary/20 flex w-full items-center gap-3 border-x-0 border-t px-3 py-2 md:border-x ${rounded && "md:rounded-b-2xl"}`}
108+
>
109+
<div className="bg-primary rounded-2xl p-1.5">
110+
<BiMessageRoundedEdit className=" text-xl text-white" />
111+
</div>
112+
{getLabel()}
113+
</section>
114+
);
115+
}

src/components/inputs/editor/BottomEditorBar.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { BiUpload } from "react-icons/bi";
1010
import UploadPreview from "./UploadPreview";
1111
import { RichText } from "@atproto/api";
1212
import { jsonToText } from "@/lib/utils/text";
13+
import ThreadGatePicker from "./ThreadGatePicker";
14+
import { ThreadgateSetting } from "../../../../types/feed";
1315

1416
interface Props {
1517
editor: Editor;
@@ -20,6 +22,8 @@ interface Props {
2022
onUpdateImages: React.Dispatch<
2123
React.SetStateAction<UploadImage[] | undefined>
2224
>;
25+
threadGate: ThreadgateSetting[];
26+
onUpdateThreadGate: React.Dispatch<React.SetStateAction<ThreadgateSetting[]>>;
2327
onSelectLabel: React.Dispatch<React.SetStateAction<string>>;
2428
onSelectLanguages: React.Dispatch<React.SetStateAction<Language[]>>;
2529
}
@@ -31,7 +35,9 @@ export default function BottomEditorBar(props: Props) {
3135
label,
3236
languages,
3337
images,
38+
threadGate,
3439
onUpdateImages,
40+
onUpdateThreadGate,
3541
onSelectLabel,
3642
onSelectLanguages,
3743
} = props;
@@ -53,7 +59,7 @@ export default function BottomEditorBar(props: Props) {
5359
...files.slice(0, 4).map((file) =>
5460
Object.assign(file, {
5561
url: URL.createObjectURL(file),
56-
})
62+
}),
5763
),
5864
];
5965
onUpdateImages(updatedImages);
@@ -70,10 +76,14 @@ export default function BottomEditorBar(props: Props) {
7076
selectedLabel={label}
7177
disabled={!images || images.length === 0}
7278
/>
79+
<ThreadGatePicker
80+
onUpdate={onUpdateThreadGate}
81+
selected={threadGate}
82+
/>
7383
<ImagePicker onShow={setShowDropzone} />
7484
{/* <LinkPicker editor={editor} /> */}
7585
</div>
76-
<div className="flex flex-wrap just gap-x-5 gap-y-2">
86+
<div className="just flex flex-wrap gap-x-5 gap-y-2">
7787
<LanguagePicker
7888
languages={languages}
7989
onSelectLanguages={onSelectLanguages}
@@ -88,10 +98,10 @@ export default function BottomEditorBar(props: Props) {
8898
<div
8999
{...getRootProps()}
90100
className={`
91-
cursor-pointer p-6 rounded-2xl border text-center hover:bg-neutral-50 animate-fade animate-duration-200
101+
animate-fade animate-duration-200 cursor-pointer rounded-2xl border p-6 text-center hover:bg-neutral-50
92102
${
93103
isDragActive &&
94-
"bg-neutral-50 border-neutral-200 ring-2 ring-primary"
104+
"ring-primary border-neutral-200 bg-neutral-50 ring-2"
95105
}
96106
`}
97107
>

src/components/inputs/editor/Editor.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { detectLinksInEditor } from "@/lib/utils/link";
1919
import LinkCardPrompt from "./LinkCardPrompt";
2020
import LinkCard from "./LinkCard";
2121
import usePublishPost from "@/lib/hooks/bsky/feed/usePublishPost";
22+
import { ThreadgateSetting } from "../../../../types/feed";
2223

2324
interface Props {
2425
onCancel: () => void;
@@ -28,12 +29,13 @@ interface Props {
2829

2930
export default function Editor(props: Props) {
3031
const { onCancel, options, author } = props;
31-
const { replyTo, onPost, quote, mention } = options ?? {};
32+
const { replyTo, quote, mention } = options ?? {};
3233
const [label, setLabel] = useState("");
34+
const [threadGate, setThreadGate] = useState<ThreadgateSetting[]>([]);
3335
const [languages, setLanguages] = useState<Language[]>([]);
3436
const [images, setImages] = useState<UploadImage[]>();
3537
const [embedSuggestions, setEmbedSuggestions] = useState<Set<string>>(
36-
new Set("")
38+
new Set(""),
3739
);
3840
const [linkEmbed, setLinkEmbed] = useState("");
3941
const [linkCard, setLinkCard] = useState<LinkMeta | null>(null);
@@ -42,7 +44,7 @@ export default function Editor(props: Props) {
4244
const quoteAuthor = quote?.author.displayName ?? quote?.author.handle;
4345
const placeholderText = getComposerPlaceholder(
4446
replyTo ? "reply" : quote ? "quote" : "post",
45-
replyAuthor ?? quoteAuthor
47+
replyAuthor ?? quoteAuthor,
4648
);
4749

4850
const editor = useEditor({
@@ -112,12 +114,13 @@ export default function Editor(props: Props) {
112114
languages: languages.map((lang) => lang.code),
113115
images,
114116
label,
117+
threadGate,
115118
});
116119

117120
if (!editor) return null;
118121

119122
return (
120-
<section className="bg-white p-3 bottom-0 z-50 fixed w-full h-full md:max-h-[80svh] md:h-fit md:border-t shadow-2xl rounded-t-3xl overflow-auto animate-fade-up animate-duration-200">
123+
<section className="animate-fade-up animate-duration-200 fixed bottom-0 z-50 h-full w-full overflow-auto rounded-t-3xl bg-white p-3 shadow-2xl md:h-fit md:max-h-[80svh] md:border-t">
121124
<div className="mx-auto max-w-2xl">
122125
<TopEditorBar
123126
onClose={onCancel}
@@ -135,7 +138,7 @@ export default function Editor(props: Props) {
135138
<div className="mb-3">
136139
{quote && <QuoteToPreview post={quote} />}
137140
{embedSuggestions.size > 0 && (
138-
<div className="flex flex-col gap-y-3 mb-3">
141+
<div className="mb-3 flex flex-col gap-y-3">
139142
{Array.from(embedSuggestions).map((link) => (
140143
<LinkCardPrompt
141144
key={link}
@@ -166,6 +169,8 @@ export default function Editor(props: Props) {
166169
text={editor.getJSON()}
167170
languages={languages}
168171
onSelectLanguages={setLanguages}
172+
threadGate={threadGate}
173+
onUpdateThreadGate={setThreadGate}
169174
images={images}
170175
onUpdateImages={setImages}
171176
/>

0 commit comments

Comments
 (0)