Post own page

This commit is contained in:
LittleSheep 2024-02-06 01:10:22 +08:00
parent d3adb20b0e
commit bbdc8e6aa6
11 changed files with 206 additions and 24 deletions

View File

@ -48,6 +48,42 @@ func listPost(c *fiber.Ctx) error {
}) })
} }
func getPost(c *fiber.Ctx) error {
id, _ := c.ParamsInt("postId", 0)
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
var post models.Post
if err := services.PreloadRelatedPost(database.C.Where(&models.Post{
BaseModel: models.BaseModel{ID: uint(id)},
})).First(&post).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
tx := database.C.
Where(&models.Post{ReplyID: &post.ID}).
Where("published_at <= ? OR published_at IS NULL", time.Now()).
Order("created_at desc")
var count int64
if err := tx.
Model(&models.Post{}).
Count(&count).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
posts, err := services.ListPost(tx, take, offset)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(fiber.Map{
"data": post,
"count": count,
"related": posts,
})
}
func createPost(c *fiber.Ctx) error { func createPost(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account) user := c.Locals("principal").(models.Account)

View File

@ -69,6 +69,7 @@ func NewServer() {
api.Post("/attachments", auth, uploadAttachment) api.Post("/attachments", auth, uploadAttachment)
api.Get("/posts", listPost) api.Get("/posts", listPost)
api.Get("/posts/:postId", getPost)
api.Post("/posts", auth, createPost) api.Post("/posts", auth, createPost)
api.Post("/posts/:postId/react/:reactType", auth, reactPost) api.Post("/posts/:postId/react/:reactType", auth, reactPost)
api.Put("/posts/:postId", auth, editPost) api.Put("/posts/:postId", auth, editPost)

View File

@ -5,8 +5,10 @@ import { SolidMarkdown } from "solid-markdown";
export default function PostItem(props: { export default function PostItem(props: {
post: any, post: any,
noClick?: boolean,
noAuthor?: boolean, noAuthor?: boolean,
noControl?: boolean, noControl?: boolean,
noRelated?: boolean,
onRepost?: (post: any) => void, onRepost?: (post: any) => void,
onReply?: (post: any) => void, onReply?: (post: any) => void,
onEdit?: (post: any) => void, onEdit?: (post: any) => void,
@ -33,8 +35,8 @@ export default function PostItem(props: {
setReacting(false); setReacting(false);
} }
return ( const element = (
<div class="post-item"> <>
<Show when={!props.noAuthor}> <Show when={!props.noAuthor}>
<a href={`/accounts/${props.post.author.name}`}> <a href={`/accounts/${props.post.author.name}`}>
<div class="flex bg-base-200"> <div class="flex bg-base-200">
@ -55,7 +57,6 @@ export default function PostItem(props: {
</div> </div>
</a> </a>
</Show> </Show>
<div class="px-7"> <div class="px-7">
<h2 class="card-title">{props.post.title}</h2> <h2 class="card-title">{props.post.title}</h2>
<article class="prose"> <article class="prose">
@ -77,7 +78,7 @@ export default function PostItem(props: {
<PostAttachments attachments={props.post.attachments ?? []} /> <PostAttachments attachments={props.post.attachments ?? []} />
<Show when={props.post.repost_to}> <Show when={!props.noRelated && props.post.repost_to}>
<p class="text-xs mt-3 mb-2"> <p class="text-xs mt-3 mb-2">
<i class="fa-solid fa-retweet me-2"></i> <i class="fa-solid fa-retweet me-2"></i>
Reposted a post Reposted a post
@ -87,11 +88,10 @@ export default function PostItem(props: {
noControl noControl
post={props.post.repost_to} post={props.post.repost_to}
onError={props.onError} onError={props.onError}
onReact={props.onReact} onReact={props.onReact} />
/>
</div> </div>
</Show> </Show>
<Show when={props.post.reply_to}> <Show when={!props.noRelated && props.post.reply_to}>
<p class="text-xs mt-3 mb-2"> <p class="text-xs mt-3 mb-2">
<i class="fa-solid fa-reply me-2"></i> <i class="fa-solid fa-reply me-2"></i>
Replied a post Replied a post
@ -101,12 +101,10 @@ export default function PostItem(props: {
noControl noControl
post={props.post.reply_to} post={props.post.reply_to}
onError={props.onError} onError={props.onError}
onReact={props.onReact} onReact={props.onReact} />
/>
</div> </div>
</Show> </Show>
</div> </div>
<Show when={!props.noControl}> <Show when={!props.noControl}>
<div class="relative"> <div class="relative">
<Show when={!userinfo?.isLoggedIn}> <Show when={!userinfo?.isLoggedIn}>
@ -168,7 +166,20 @@ export default function PostItem(props: {
</div> </div>
</div> </div>
</Show> </Show>
</>
</div>
); );
if (props.noClick) {
return (
<div class="post-item">
{element}
</div>
)
} else {
return (
<a class="post-item" href={`/posts/${props.post.id}`}>
{element}
</a>
);
}
} }

View File

@ -5,6 +5,7 @@ import PostItem from "./PostItem.tsx";
import { getAtk } from "../stores/userinfo.tsx"; import { getAtk } from "../stores/userinfo.tsx";
export default function PostList(props: { export default function PostList(props: {
noRelated?: boolean,
info: { data: any[], count: number } | null, info: { data: any[], count: number } | null,
onRepost?: (post: any) => void, onRepost?: (post: any) => void,
onReply?: (post: any) => void, onReply?: (post: any) => void,
@ -58,6 +59,7 @@ export default function PostList(props: {
<For each={posts()}> <For each={posts()}>
{item => <PostItem {item => <PostItem
post={item} post={item}
noRelated={props.noRelated}
onRepost={props.onRepost} onRepost={props.onRepost}
onReply={props.onReply} onReply={props.onReply}
onEdit={props.onEdit} onEdit={props.onEdit}

View File

@ -13,6 +13,7 @@ import "@fortawesome/fontawesome-free/css/all.css";
import RootLayout from "./layouts/RootLayout.tsx"; import RootLayout from "./layouts/RootLayout.tsx";
import Feed from "./pages/feed.tsx"; import Feed from "./pages/feed.tsx";
import Global from "./pages/global.tsx"; import Global from "./pages/global.tsx";
import PostReference from "./pages/post.tsx";
import { UserinfoProvider } from "./stores/userinfo.tsx"; import { UserinfoProvider } from "./stores/userinfo.tsx";
import { WellKnownProvider } from "./stores/wellKnown.tsx"; import { WellKnownProvider } from "./stores/wellKnown.tsx";
@ -24,8 +25,9 @@ render(() => (
<Router root={RootLayout}> <Router root={RootLayout}>
<Route path="/" component={Feed}> <Route path="/" component={Feed}>
<Route path="/" component={Global} /> <Route path="/" component={Global} />
<Route path="/realms" component={lazy(() => import("./pages/realms.tsx"))} /> <Route path="/posts/:postId" component={PostReference} />
<Route path="/realms/:realmId" component={lazy(() => import("./pages/realm.tsx"))} /> <Route path="/realms" component={lazy(() => import("./pages/realms"))} />
<Route path="/realms/:realmId" component={lazy(() => import("./pages/realms/realm.tsx"))} />
<Route path="/accounts/:accountId" component={lazy(() => import("./pages/account.tsx"))} /> <Route path="/accounts/:accountId" component={lazy(() => import("./pages/account.tsx"))} />
</Route> </Route>
<Route path="/auth" component={lazy(() => import("./pages/auth/callout.tsx"))} /> <Route path="/auth" component={lazy(() => import("./pages/auth/callout.tsx"))} />

View File

@ -2,7 +2,7 @@ import Navbar from "./shared/Navbar.tsx";
import { readProfiles, useUserinfo } from "../stores/userinfo.tsx"; import { readProfiles, useUserinfo } from "../stores/userinfo.tsx";
import { createEffect, createMemo, createSignal, Show } from "solid-js"; import { createEffect, createMemo, createSignal, Show } from "solid-js";
import { readWellKnown } from "../stores/wellKnown.tsx"; import { readWellKnown } from "../stores/wellKnown.tsx";
import { BeforeLeaveEventArgs, useBeforeLeave, useLocation, useNavigate, useSearchParams } from "@solidjs/router"; import { BeforeLeaveEventArgs, useLocation, useNavigate, useSearchParams } from "@solidjs/router";
export default function RootLayout(props: any) { export default function RootLayout(props: any) {
const [ready, setReady] = createSignal(false); const [ready, setReady] = createSignal(false);
@ -17,21 +17,19 @@ export default function RootLayout(props: any) {
createEffect(() => { createEffect(() => {
if (ready()) { if (ready()) {
keepGate(location.pathname); keepGate(location.pathname + location.search);
} }
}, [ready, userinfo]); }, [ready, userinfo]);
function keepGate(path: string, e?: BeforeLeaveEventArgs) { function keepGate(path: string, e?: BeforeLeaveEventArgs) {
const whitelist = ["/auth", "/auth/callback"]; const blacklist = ["/creator"];
if (!userinfo?.isLoggedIn && !whitelist.includes(path)) { if (!userinfo?.isLoggedIn && blacklist.includes(path)) {
if (!e?.defaultPrevented) e?.preventDefault(); if (!e?.defaultPrevented) e?.preventDefault();
navigate(`/auth/login?redirect_uri=${path}`); navigate(`/auth?redirect_uri=${path}`);
} }
} }
useBeforeLeave((e: BeforeLeaveEventArgs) => keepGate(e.to.toString().split("?")[0], e));
const mainContentStyles = createMemo(() => { const mainContentStyles = createMemo(() => {
if(!searchParams["noTitle"]) { if(!searchParams["noTitle"]) {
return "h-[calc(100vh-64px)] mt-[64px]" return "h-[calc(100vh-64px)] mt-[64px]"

View File

@ -7,7 +7,7 @@ import PostPublish from "../components/PostPublish.tsx";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { closeModel, openModel } from "../scripts/modals.ts"; import { closeModel, openModel } from "../scripts/modals.ts";
export default function DashboardPage() { export default function AccountPage() {
const [error, setError] = createSignal<string | null>(null); const [error, setError] = createSignal<string | null>(null);
const [page, setPage] = createSignal(0); const [page, setPage] = createSignal(0);

132
pkg/view/src/pages/post.tsx Normal file
View File

@ -0,0 +1,132 @@
import { createSignal, Show } from "solid-js";
import { useNavigate, useParams } from "@solidjs/router";
import { createStore } from "solid-js/store";
import { closeModel, openModel } from "../scripts/modals.ts";
import PostPublish from "../components/PostPublish.tsx";
import PostList from "../components/PostList.tsx";
import PostItem from "../components/PostItem.tsx";
export default function PostPage() {
const [error, setError] = createSignal<string | null>(null);
const [page, setPage] = createSignal(0);
const [related, setRelated] = createSignal<any>(null);
const [info, setInfo] = createSignal<any>(null);
const params = useParams();
const navigate = useNavigate();
async function readPost(pn?: number) {
if (pn) setPage(pn);
const res = await fetch(`/api/posts/${params["postId"]}?` + new URLSearchParams({
take: (10).toString(),
offset: ((page() - 1) * 10).toString()
}));
if (res.status !== 200) {
setError(await res.text());
} else {
setError(null);
const data = await res.json();
setInfo(data["data"]);
setRelated({
count: data["count"],
data: data["related"]
});
}
}
readPost();
function setMeta(data: any, field: string, open = true) {
const meta: { [id: string]: any } = {
reposting: null,
replying: null,
editing: null
};
meta[field] = data;
setPublishMeta(meta);
if (open) openModel("#post-publish");
else closeModel("#post-publish");
}
const [publishMeta, setPublishMeta] = createStore<any>({
replying: null,
reposting: null,
editing: null
});
function back() {
if (window.history.length > 0) {
window.history.back();
} else {
navigate("/");
}
}
return (
<>
<div id="alerts">
<Show when={error()}>
<div role="alert" class="alert alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="capitalize">{error()}</span>
</div>
</Show>
</div>
<div class="flex pt-1">
<button class="btn btn-ghost ml-[20px] w-12 h-12" onClick={() => back()}>
<i class="fa-solid fa-angle-left"></i>
</button>
<div class="px-5 flex items-center">
<p>Post #{info()?.id}</p>
</div>
</div>
<dialog id="post-publish" class="modal">
<div class="modal-box p-0 w-[540px]">
<PostPublish
reposting={publishMeta.reposting}
replying={publishMeta.replying}
editing={publishMeta.editing}
onReset={() => setMeta(null, "none", false)}
onError={setError}
onPost={() => readPost()}
/>
</div>
</dialog>
<Show when={info()} fallback={
<div class="w-full border-b border-base-200 pt-5 pb-7 text-center">
<p class="loading loading-lg loading-infinity"></p>
<p>Creating fake news...</p>
</div>
}>
<PostItem
noClick
post={info()}
onError={setError}
onReact={readPost}
onRepost={(item) => setMeta(item, "reposting")}
onReply={(item) => setMeta(item, "replying")}
onEdit={(item) => setMeta(item, "editing")}
/>
<PostList
noRelated
info={related()}
onUpdate={readPost}
onError={setError}
onRepost={(item) => setMeta(item, "reposting")}
onReply={(item) => setMeta(item, "replying")}
onEdit={(item) => setMeta(item, "editing")}
/>
</Show>
</>
);
}

View File

@ -2,8 +2,8 @@ import { createSignal, Show } from "solid-js";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { useParams } from "@solidjs/router"; import { useParams } from "@solidjs/router";
import PostList from "../components/PostList.tsx"; import PostList from "../../components/PostList.tsx";
import PostPublish from "../components/PostPublish.tsx"; import PostPublish from "../../components/PostPublish.tsx";
import styles from "./realm.module.css"; import styles from "./realm.module.css";