Skip to content

Commit

Permalink
Add post pinning.
Browse files Browse the repository at this point in the history
  • Loading branch information
SupertigerDev committed Dec 12, 2024
1 parent 5c139fb commit d967296
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 66 deletions.
17 changes: 17 additions & 0 deletions src/chat-api/services/PostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ export const getPostsLiked = async (userId: string) => {
return data;
};

export const pinPost = async (postId: string) => {
const data = await request<RawPost>({
method: "POST",
url: env.SERVER_URL + "/api" + ServiceEndpoints.post(postId) + "/pin",
useToken: true,
});
return data;
};

export const unpinPost = async (postId: string) => {
const data = await request<RawPost>({
method: "DELETE",
url: env.SERVER_URL + "/api" + ServiceEndpoints.post(postId) + "/pin",
useToken: true,
});
return data;
};
export const getPost = async (postId: string) => {
const data = await request<RawPost>({
method: "GET",
Expand Down
7 changes: 6 additions & 1 deletion src/chat-api/services/UserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export interface UserDetails {
mutualFriendIds: string[];
mutualServerIds: string[];
latestPost: RawPost;
pinnedPosts: RawPost[];
profile?: UserProfile;
hideFollowers?: boolean;
hideFollowing?: boolean;
Expand All @@ -186,10 +187,14 @@ export interface UserProfile {
primaryColor?: string;
}

export async function getUserDetailsRequest(userId?: string) {
export async function getUserDetailsRequest(
userId?: string,
includePinnedPosts?: boolean
) {
return request<UserDetails>({
url: env.SERVER_URL + "/api" + ServiceEndpoints.user(userId || ""),
method: "GET",
params: { ...(includePinnedPosts ? { includePinnedPosts } : {}) },
useToken: true,
});
}
Expand Down
31 changes: 31 additions & 0 deletions src/components/PostsArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { formatTimestamp, timeSince } from "@/common/date";
import RouterEndpoints from "@/common/RouterEndpoints";
import { A, useSearchParams } from "solid-navigator";
import {
batch,
createEffect,
createMemo,
createSignal,
For,
Index,
Expand Down Expand Up @@ -482,6 +484,7 @@ const PostsContainer = styled(FlexColumn)`

export function PostsArea(props: {
showLiked?: boolean;
pinnedPosts?: Post[];
showFeed?: boolean;
showDiscover?: boolean;
showReplies?: boolean;
Expand All @@ -500,6 +503,24 @@ export function PostsArea(props: {
const [lastFetchCount, setLastFetchCount] = createSignal(0);
let postsContainerRef: HTMLDivElement | undefined;

createEffect(() => {
if (props.pinnedPosts?.length) {
batch(() => {
for (let i = 0; i < props.pinnedPosts!.length; i++) {
const post = props.pinnedPosts![i]!;
posts.pushPost(post);
}
});
}
});

const pinnedPosts = createMemo(() => {
if (!props.pinnedPosts?.length) return [];
return props.pinnedPosts
.map((post) => posts.cachedPost(post.id))
.filter((post) => post) as Post[];
});

const cachedReplies = () => {
if (props.showDiscover) return posts.cachedDiscover();
if (props.showFeed) return posts.cachedFeed();
Expand Down Expand Up @@ -646,6 +667,16 @@ export function PostsArea(props: {
/>
</Show>
<FlexColumn ref={postsContainerRef}>
<For each={pinnedPosts()}>
{(post) => (
<PostItem
bgColor={props.bgColor}
post={post}
pinned
primaryColor={props.primaryColor}
/>
)}
</For>
<For each={cachedReplies()}>
{(post, i) => (
<PostItem
Expand Down
5 changes: 5 additions & 0 deletions src/components/post-area/PostItem.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.pinnedContainer {
display: flex;
align-items: center;
gap: 4px;
}

.embedContainer {
display: flex;
Expand Down
128 changes: 94 additions & 34 deletions src/components/post-area/PostItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { RawPostChoice, RawPostPoll, RawUser } from "@/chat-api/RawData";
import { RadioBoxItem } from "../ui/RadioBox";
import { DeletePostModal, EditPostModal } from "../PostsArea";
import { css } from "solid-styled-components";
import ContextMenu from "../ui/context-menu/ContextMenu";
import { pinPost, unpinPost } from "@/chat-api/services/PostService";

const viewsEnabledAt = new Date();
viewsEnabledAt.setUTCFullYear(2024);
Expand All @@ -39,11 +41,14 @@ export function PostItem(props: {
class?: string;
onClick?: (id: Post) => void;
post: Post;
pinned?: boolean;
}) {
const { posts } = useStore();
const [search, setSearchParams] = useSearchParams<{ postId: string }>();
const [hovered, setHovered] = createSignal(false);

const [pinned, setPinned] = createSignal(props.pinned);

const replyingTo = createMemo(() => {
if (!props.post.commentToId) return;
return posts.cachedPost(props.post.commentToId);
Expand Down Expand Up @@ -115,6 +120,9 @@ export function PostItem(props: {
<Show when={replyingTo()}>
<ReplyTo user={replyingTo()!.createdBy} />
</Show>
<Show when={pinned()}>
<Pinned />
</Show>
<div class={style.postInnerContainer}>
<A
onClick={(e) => e.stopPropagation()}
Expand All @@ -136,9 +144,11 @@ export function PostItem(props: {
<Content post={props.post} hovered={hovered()} />

<Actions
onTogglePinned={() => setPinned(!pinned())}
primaryColor={props.primaryColor}
hideDelete={props.hideDelete}
post={props.post}
pinned={pinned()}
/>
</div>
</div>
Expand Down Expand Up @@ -194,6 +204,8 @@ const Actions = (props: {
primaryColor?: string;
post: Post;
hideDelete?: boolean;
pinned?: boolean;
onTogglePinned?: () => void;
}) => {
const navigate = useNavigate();
const { account } = useStore();
Expand Down Expand Up @@ -223,6 +235,19 @@ const Actions = (props: {
<DeletePostModal close={close} post={props.post} />
));

const [pinRequestSent, setPinRequestSent] = createSignal(false);

const togglePin = async () => {
if (pinRequestSent()) return;
setPinRequestSent(true);
if (props.pinned) {
await unpinPost(props.post.id).finally(() => setPinRequestSent(false));
} else {
await pinPost(props.post.id).finally(() => setPinRequestSent(false));
}
props.onTogglePinned?.();
};

const onEditClicked = () =>
createPortal?.((close) => (
<EditPostModal close={close} post={props.post} />
Expand All @@ -232,6 +257,58 @@ const Actions = (props: {
return props.post.createdAt > timestampViewsEnabledAt;
};

const showDeleteAndEdit = () =>
props.post.createdBy?.id === account.user()?.id && !props.hideDelete;

const showContextMenu = (event: MouseEvent) => {
if (event.target instanceof HTMLElement) {
const rect = event.target?.getBoundingClientRect()!;
createPortal(
(close) => (
<ContextMenu
items={[
...(showDeleteAndEdit()
? [
{
label: props.pinned ? "Unpin" : "Pin",
onClick: togglePin,
alert: props.pinned,
icon: "push_pin",
},
{ label: "Edit", onClick: onEditClicked, icon: "edit" },
{ separator: true },
{
label: "Delete",
onClick: onDeleteClick,
alert: true,
icon: "delete",
},
]
: []),
...(account.hasModeratorPerm()
? [
{
label: "Moderation Pane",
onClick: () =>
navigate(
"/app/moderation?search-post-id=" + props.post.id
),
icon: "security",
},
]
: []),
]}
position={rect}
onClose={close}
triggerClassName="post-more-button"
/>
),
"post-context-menu",
true
);
}
};

return (
<div class={style.postActionsContainer}>
<Button
Expand Down Expand Up @@ -267,40 +344,13 @@ const Actions = (props: {
</Show>

<div class={style.rightActions}>
<Show when={account.hasModeratorPerm()}>
<Button
onClick={() =>
navigate("/app/moderation?search-post-id=" + props.post.id)
}
margin={0}
iconClass={style.icon}
color={props.primaryColor}
class={style.postButtonStyle}
iconName="security"
/>
</Show>
<Show
when={
props.post.createdBy?.id === account.user()?.id && !props.hideDelete
}
>
<Button
onClick={onEditClicked}
margin={0}
class={style.postButtonStyle}
color={props.primaryColor}
iconClass={style.icon}
iconName="edit"
/>
<Button
onClick={onDeleteClick}
margin={0}
class={style.postButtonStyle}
color="var(--alert-color)"
iconClass={style.icon}
iconName="delete"
/>
</Show>
<Button
onclick={showContextMenu}
margin={0}
class={cn(style.postButtonStyle, "post-more-button")}
iconClass={style.icon}
iconName="more_vert"
/>
</div>
</div>
);
Expand Down Expand Up @@ -466,3 +516,13 @@ const ReplyTo = (props: { user: RawUser }) => {
</div>
);
};
const Pinned = () => {
return (
<div class={style.pinnedContainer}>
<Icon name="push_pin" color="var(--primary-color)" size={16} />
<Text size={14} style={{ "margin-right": "5px" }}>
Pinned
</Text>
</div>
);
};
3 changes: 2 additions & 1 deletion src/components/profile-pane/ProfilePane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export default function ProfilePane() {

const fetchUserDetails = async (userId: string) => {
setAnimateAvatar(false);
const userDetails = await getUserDetailsRequest(userId);
const userDetails = await getUserDetailsRequest(userId, true);
setUserDetails(userDetails);
setTimeout(() => {
setAnimateAvatar(true);
Expand Down Expand Up @@ -1125,6 +1125,7 @@ function PostsContainer(props: { user: UserDetails; bgColor: string }) {
</FlexRow>
<Show when={props.user && currentPage() <= 2}>
<PostsArea
pinnedPosts={currentPage() <= 1 ? props.user.pinnedPosts : []}
primaryColor={primaryColor()}
showLiked={currentPage() === 2}
showReplies={currentPage() === 1}
Expand Down
Loading

0 comments on commit d967296

Please sign in to comment.