✨ Post own page
This commit is contained in:
		| @@ -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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
| @@ -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} | ||||||
|   | |||||||
| @@ -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"))} /> | ||||||
|   | |||||||
| @@ -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]" | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										132
									
								
								pkg/view/src/pages/post.tsx
									
									
									
									
									
										Normal 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> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -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"; | ||||||
| 
 | 
 | ||||||
		Reference in New Issue
	
	Block a user