From 7a581ce6e746aa8917baee2b2457004057629bc7 Mon Sep 17 00:00:00 2001 From: sneedmaster Date: Mon, 13 Apr 2026 12:14:35 +0200 Subject: [PATCH] =?UTF-8?q?Mod=20uj=C3=AD,=20mod=20u=20u=20=C3=BAj?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++ config/locale/permissions.json | 2 +- config/roles.json | 6 +- src/lib/permissions.ts | 2 +- src/schema/validation/post.ts | 4 +- src/web/components/content/catalog_tile.tsx | 10 +- src/web/components/content/post.tsx | 10 +- .../components/content/post_attributes.tsx | 1 + src/web/components/content/thumbnail.tsx | 9 +- .../components/forms/post_actions_form.tsx | 46 +------ .../actions/action_pages/delete_and_ban.tsx | 122 ++++++++++++++++++ .../action_pages/manage_attributes.tsx | 114 ++++++++++++++++ .../actions/action_pages/manage_files.tsx | 122 ++++++++++++++++++ src/web/routes/actions/create_post.ts | 4 +- src/web/routes/actions/post_actions.ts | 38 +++++- src/web/routes/mod/actions/create_banner.ts | 2 +- src/web/routes/mod/boards.tsx | 2 +- .../routes/mod/{ => edit}/board_config.tsx | 20 +-- src/web/routes/mod/edit/news_content.tsx | 70 ++++++++++ .../mod/{ => edit}/user_permissions.tsx | 18 +-- src/web/routes/mod/index.tsx | 2 +- src/web/routes/mod/news.tsx | 2 +- src/web/routes/mod/news_content.tsx | 73 ----------- src/web/routes/mod/users.tsx | 2 +- src/web/routing/register_mod.ts | 41 +++--- web/sass/base.scss | 5 +- web/sass/yotsuba-base.scss | 4 +- 27 files changed, 573 insertions(+), 183 deletions(-) create mode 100644 src/web/components/content/post_attributes.tsx create mode 100644 src/web/routes/actions/action_pages/delete_and_ban.tsx create mode 100644 src/web/routes/actions/action_pages/manage_attributes.tsx create mode 100644 src/web/routes/actions/action_pages/manage_files.tsx rename src/web/routes/mod/{ => edit}/board_config.tsx (65%) create mode 100644 src/web/routes/mod/edit/news_content.tsx rename src/web/routes/mod/{ => edit}/user_permissions.tsx (78%) delete mode 100644 src/web/routes/mod/news_content.tsx diff --git a/README.md b/README.md index 4f0d73b..a823a1c 100755 --- a/README.md +++ b/README.md @@ -3,3 +3,28 @@ Český anonymní imageboard. Migrace databází se budou opakovaně rozbíjet před vydáním první verze do produkce. Prosím, počítejte s tím. + +## Průběh + +- [x] Základní funkce +- [x] Formátování textu +- [x] Standardní + bezpečný tripcode +- [x] Katalog +- [x] Bany, limity, blokace, filtry +- [x] Správa účtú, nástěnek, role +- [x] Podpora de facto všech multimediálních formátů +- [x] Cache optimalizace, indexy v paměti +- [x] Uživatelské funkce, mazání heslem +- [-] Moderátorské funkce +- [-] Domovská strana +- [-] JavaScript na frontendu + +### Plánováno + +- [-] Lepší logger +- [-] Nastavení stránky a správa assetů z UI +- [-] Přesouvání příspěvků +- [-] phash +- [-] Memeflagy, rizz +- [-] Fortune +- [-] Pořádná dokumentace diff --git a/config/locale/permissions.json b/config/locale/permissions.json index b8d8110..7c8a83f 100644 --- a/config/locale/permissions.json +++ b/config/locale/permissions.json @@ -13,7 +13,7 @@ "MANAGE_FILTERS": "Spravovat filtry", "ALL_FILTERS": "Spravovat filtry (+)", "MANAGE_REPORTS": "Spravovat hlášení", - "MANAGE_POSTS": "Spravovat příspěvky", + "MANAGE_ATTRIBUTES": "Spravovat atributy příspěvků", "DELETE_POSTS": "Mazat příspěvky", "DELETE_FILES": "Mazat soubory", "SPOILER_FILES": "Spoilerovat soubory", diff --git a/config/roles.json b/config/roles.json index 629d432..e9101ea 100755 --- a/config/roles.json +++ b/config/roles.json @@ -20,7 +20,7 @@ "MANAGE_BANS", "ALL_BANS", "MANAGE_REPORTS", - "MANAGE_POSTS", + "MANAGE_ATTRIBUTES", "DELETE_POSTS", "DELETE_FILES", "SPOILER_FILES", @@ -48,7 +48,7 @@ "MANAGE_RESTRICTIONS", "MANAGE_FILTERS", "MANAGE_REPORTS", - "MANAGE_POSTS", + "MANAGE_ATTRIBUTES", "DELETE_POSTS", "DELETE_FILES", "SPOILER_FILES", @@ -87,7 +87,7 @@ "MANAGE_FILTERS", "ALL_FILTERS", "MANAGE_REPORTS", - "MANAGE_POSTS", + "MANAGE_ATTRIBUTES", "DELETE_POSTS", "DELETE_FILES", "SPOILER_FILES", diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index 22d9e63..8929000 100755 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -17,7 +17,7 @@ enum CzchanPerm { ALL_FILTERS = "ALL_FILTERS", // Post permissions MANAGE_REPORTS = "MANAGE_REPORTS", - MANAGE_POSTS = "MANAGE_POSTS", + MANAGE_ATTRIBUTES = "MANAGE_ATTRIBUTES", DELETE_POSTS = "DELETE_POSTS", DELETE_FILES = "DELETE_FILES", SPOILER_FILES = "SPOILER_FILES", diff --git a/src/schema/validation/post.ts b/src/schema/validation/post.ts index 32030c3..d69761c 100644 --- a/src/schema/validation/post.ts +++ b/src/schema/validation/post.ts @@ -46,9 +46,9 @@ const zPostAction = z.enum( "delete_files", "delete_posts", "report_posts", - "ban_and_delete", + "delete_and_ban", "manage_files", - "manage_posts", + "manage_attributes", "move_posts", ], "Neplatná akce.", diff --git a/src/web/components/content/catalog_tile.tsx b/src/web/components/content/catalog_tile.tsx index 6ad93e1..a15a5ab 100644 --- a/src/web/components/content/catalog_tile.tsx +++ b/src/web/components/content/catalog_tile.tsx @@ -19,24 +19,24 @@ const CatalogTile = ({ post }: PropsWithChildren<{ post: Post }>) => (
R: {post.t_replies} / F: {post.t_files}{" "} - {post.f_sticky !== 0 && ( + {post.f_sticky > 0 && ( <> - {" "} + {" "} )} {post.f_locked && ( <> - {" "} + {" "} )} {post.f_bumplocked && ( <> - {" "} + {" "} )} {post.f_looped && ( <> - + )} diff --git a/src/web/components/content/post.tsx b/src/web/components/content/post.tsx index 7e7fa3e..1bef13f 100644 --- a/src/web/components/content/post.tsx +++ b/src/web/components/content/post.tsx @@ -97,24 +97,24 @@ const PostComponent = ({ [Otevřít]{" "} )} - {post.f_sticky !== 0 && ( + {post.f_sticky > 0 && ( <> - {" "} + {" "} )} {post.f_locked && ( <> - {" "} + {" "} )} {post.f_bumplocked && ( <> - {" "} + {" "} )} {post.f_looped && ( <> - {" "} + {" "} )} diff --git a/src/web/components/content/post_attributes.tsx b/src/web/components/content/post_attributes.tsx new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/web/components/content/post_attributes.tsx @@ -0,0 +1 @@ + diff --git a/src/web/components/content/thumbnail.tsx b/src/web/components/content/thumbnail.tsx index 157f43c..8ae84a5 100644 --- a/src/web/components/content/thumbnail.tsx +++ b/src/web/components/content/thumbnail.tsx @@ -1,13 +1,16 @@ import { File } from "../../../schema/jsonb"; import { PropsWithChildren } from "@kitajs/html"; -const Thumbnail = ({ file }: PropsWithChildren<{ file: File }>) => ( +const Thumbnail = ({ + file, + nospecial = false, +}: PropsWithChildren<{ file: File; nospecial?: boolean }>) => ( { if (file.deleted) return "/public/img/thumb/deleted.png"; - if (file.spoiler) return "/public/img/thumb/spoiler.png"; - if (file.thumb_url) return file.thumb_url; + if (!nospecial && file.spoiler) return "/public/img/thumb/spoiler.png"; + if (!nospecial && file.thumb_url) return file.thumb_url; switch (file.type) { case "image": diff --git a/src/web/components/forms/post_actions_form.tsx b/src/web/components/forms/post_actions_form.tsx index b1eb6fb..41dc42e 100644 --- a/src/web/components/forms/post_actions_form.tsx +++ b/src/web/components/forms/post_actions_form.tsx @@ -5,7 +5,7 @@ import { PropsWithChildren } from "@kitajs/html"; const PostActionsForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( <> -
+
Akce příspěvku
@@ -29,48 +29,17 @@ const PostActionsForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( {(canUser(ctx.user, CzchanPerm.DELETE_POSTS) || canUser(ctx.user, CzchanPerm.MANAGE_BANS)) && ( - - Odstranit/Zabanovat{" "} - - - } - action="ban_and_delete" - /> + )} {(canUser(ctx.user, CzchanPerm.DELETE_FILES) || canUser(ctx.user, CzchanPerm.SPOILER_FILES)) && ( - - Soubory - - } - action="manage_files" - /> + )} - {canUser(ctx.user, CzchanPerm.MANAGE_POSTS) && ( - - Atributy{" "} - - - } - action="manage_posts" - /> + {canUser(ctx.user, CzchanPerm.MANAGE_ATTRIBUTES) && ( + )} {canUser(ctx.user, CzchanPerm.MOVE_POSTS) && ( - - Přesunout{" "} - - - } - action="move_posts" - /> + )} {(canUser(ctx.user, CzchanPerm.VIEW_IPS) || canUser(ctx.user, CzchanPerm.VIEW_METADATA)) && ( @@ -82,13 +51,12 @@ const PostActionsForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( ); -// @ts-ignore const anyModTools = (ctx: TemplateCtx) => canUser(ctx.user, CzchanPerm.DELETE_POSTS) || canUser(ctx.user, CzchanPerm.MANAGE_BANS) || canUser(ctx.user, CzchanPerm.DELETE_FILES) || canUser(ctx.user, CzchanPerm.SPOILER_FILES) || - canUser(ctx.user, CzchanPerm.MANAGE_POSTS) || + canUser(ctx.user, CzchanPerm.MANAGE_ATTRIBUTES) || canUser(ctx.user, CzchanPerm.MOVE_POSTS) || canUser(ctx.user, CzchanPerm.VIEW_IPS) || canUser(ctx.user, CzchanPerm.VIEW_METADATA); diff --git a/src/web/routes/actions/action_pages/delete_and_ban.tsx b/src/web/routes/actions/action_pages/delete_and_ban.tsx new file mode 100644 index 0000000..e09e1ca --- /dev/null +++ b/src/web/routes/actions/action_pages/delete_and_ban.tsx @@ -0,0 +1,122 @@ +import { canUser, CzchanPerm } from "../../../../lib/permissions"; +import { truncate } from "../../../../lib/util"; +import { Post } from "../../../../schema/tables"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import { Form, FormField, FormButton } from "../../../components/forms/form"; +import IPAddress from "../../../components/primitives/ip_address"; +import { TemplateCtx } from "../../../ctx"; +import { PropsWithChildren } from "@kitajs/html"; + +const DeleteAndBanPage = (ctx: TemplateCtx, posts: Post[]) => ( + + + +

Odstranit/Zabanovat

+
+ + + + + + + + + + + + {posts.map((post) => ( + + + + + + + ))} + +
IDIPObsah
+ + + >>>/{post.board}/{post.id} + + + {truncate(post.content_unformatted, 128)}
+
+ + +
+
+
+); + +const DeleteAndBanForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( +
+ {canUser(ctx.user, CzchanPerm.DELETE_POSTS) && ( +
+ + + + + + + {canUser(ctx.user, CzchanPerm.VIEW_IPS) && ( + + + + )} +
+ )} + {canUser(ctx.user, CzchanPerm.MANAGE_BANS) && ( +
+ + + + + + + + + + + + + + ↳ Délka banu (např. 4d2h3m1s) + + } + > + + + + + + + + + + + +
+ )} +
+ + +
+); + +export { DeleteAndBanPage }; diff --git a/src/web/routes/actions/action_pages/manage_attributes.tsx b/src/web/routes/actions/action_pages/manage_attributes.tsx new file mode 100644 index 0000000..2ee5579 --- /dev/null +++ b/src/web/routes/actions/action_pages/manage_attributes.tsx @@ -0,0 +1,114 @@ +import { truncate } from "../../../../lib/util"; +import { Post } from "../../../../schema/tables"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import { Form, FormField, FormButton } from "../../../components/forms/form"; +import IPAddress from "../../../components/primitives/ip_address"; +import { TemplateCtx } from "../../../ctx"; + +const ManageAttributesPage = (ctx: TemplateCtx, posts: Post[]) => ( + + + +

Atributy

+
+
+ + + + + + + + + + + + {posts.map((post) => ( + + + + + + + + ))} + +
IDIPObsahAtributy
+ + + >>>/{post.board}/{post.id} + + + {truncate(post.content_unformatted, 128)} + {post.thread !== null && ( + <> + Není vlákno{" "} + {" "} + + )} + {post.f_sticky > 0 && ( + <> + {" "} + + )} + {post.f_locked && ( + <> + {" "} + + )} + {post.f_bumplocked && ( + <> + {" "} + + )} + {post.f_looped && ( + <> + {" "} + + )} +
+
+ + +
+
+
+); + +const ManageAttributesForm = () => ( +
+
+ + + + + + + + + + + + +
+
+ + +
+); + +export { ManageAttributesPage }; diff --git a/src/web/routes/actions/action_pages/manage_files.tsx b/src/web/routes/actions/action_pages/manage_files.tsx new file mode 100644 index 0000000..2cd2580 --- /dev/null +++ b/src/web/routes/actions/action_pages/manage_files.tsx @@ -0,0 +1,122 @@ +import { canUser, CzchanPerm } from "../../../../lib/permissions"; +import { truncateFilename } from "../../../../lib/util"; +import { Post } from "../../../../schema/tables"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import Thumbnail from "../../../components/content/thumbnail"; +import { Form, FormField, FormButton } from "../../../components/forms/form"; +import IPAddress from "../../../components/primitives/ip_address"; +import { TemplateCtx } from "../../../ctx"; +import { PropsWithChildren } from "@kitajs/html"; + +const ManageFilesPage = (ctx: TemplateCtx, posts: Post[]) => ( + + + +

Soubory

+
+
+ + + + + + + + + + {posts.map((post) => ( + + + + + + ))} + +
IDIPSoubory
+ >>>/{post.board}/{post.id} + + + +
+ {post.files.map((file) => ( +
+
+ + {truncateFilename(file.original_filename, 16)} + {" "} + +
+ +
+ ))} +
+
+
+ + +
+
+
+); + +const ManageFilesForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( +
+ {canUser(ctx.user, CzchanPerm.SPOILER_FILES) && ( +
+ + + + + + +
+ )} + {canUser(ctx.user, CzchanPerm.DELETE_FILES) && ( +
+ + + + + + + + + +
+ )} + {canUser(ctx.user, CzchanPerm.MANAGE_FILTERS) && ( +
+ + + + + + + + + + + + + + + +
+ )} +
+ + +
+); + +export { ManageFilesPage }; diff --git a/src/web/routes/actions/create_post.ts b/src/web/routes/actions/create_post.ts index 7cc8143..c241df2 100644 --- a/src/web/routes/actions/create_post.ts +++ b/src/web/routes/actions/create_post.ts @@ -38,7 +38,7 @@ export default async (req: Request, res: Response) => { const form = CreatePostForm.parse(fields); const uploadedFiles = (uploads["files"] || []).filter( - (file) => file.size !== 0, + (file) => file.size > 0, ); const ip = req.ipInfo.realIP; @@ -211,7 +211,7 @@ export default async (req: Request, res: Response) => { board, req.ipInfo.atomicIP, postHash(content_unformatted), - content_unformatted.length !== 0, + content_unformatted.length > 0, ); if (floodDetected) throw new CzchanError("Příspěvek byl zamítnut.", 400); diff --git a/src/web/routes/actions/post_actions.ts b/src/web/routes/actions/post_actions.ts index 491eeaa..448f789 100644 --- a/src/web/routes/actions/post_actions.ts +++ b/src/web/routes/actions/post_actions.ts @@ -12,6 +12,9 @@ import { Board, Post } from "../../../schema/tables"; import { ZPostActionsForm } from "../../../schema/validation/post"; import { getCtx } from "../../ctx"; import { ResultPage } from "../../templates/result"; +import { DeleteAndBanPage } from "./action_pages/delete_and_ban"; +import { ManageAttributesPage } from "./action_pages/manage_attributes"; +import { ManageFilesPage } from "./action_pages/manage_files"; import bcrypt from "bcrypt"; import { Request, Response } from "express"; import formidable from "formidable"; @@ -32,28 +35,57 @@ export default async (req: Request, res: Response) => { !boards[post.board].config.private, ); + const ctx = await getCtx(req); + let html; + switch (form.action) { case "update_spoiler": if (!form.deletion_code) throw new CzchanError("Kód je povinný.", 400); await updateSpoiler(form.deletion_code, boards, posts); + html = ResultPage(ctx, ["Zvolená akce byla úspěšně provedena."]); break; case "delete_files": if (!form.deletion_code) throw new CzchanError("Kód je povinný.", 400); await deleteFiles(form.deletion_code, boards, posts); + html = ResultPage(ctx, ["Zvolená akce byla úspěšně provedena."]); break; case "delete_posts": if (!form.deletion_code) throw new CzchanError("Kód je povinný.", 400); await deletePosts(form.deletion_code, boards, posts); + html = ResultPage(ctx, ["Zvolená akce byla úspěšně provedena."]); break; case "report_posts": if (!form.report_reason) throw new CzchanError("Důvod je povinný.", 400); await reportPosts(req, form.report_reason, posts); + html = ResultPage(ctx, ["Zvolená akce byla úspěšně provedena."]); + break; + case "delete_and_ban": + if ( + !( + canUser(req.user, CzchanPerm.DELETE_POSTS) || + canUser(req.user, CzchanPerm.MANAGE_BANS) + ) + ) + throw new CzchanError("K tomuto nemáš oprávnění.", 403); + html = DeleteAndBanPage(ctx, posts); + break; + case "manage_files": + if ( + !( + canUser(req.user, CzchanPerm.DELETE_FILES) || + canUser(req.user, CzchanPerm.SPOILER_FILES) + ) + ) + throw new CzchanError("K tomuto nemáš oprávnění.", 403); + html = ManageFilesPage(ctx, posts); + break; + case "manage_attributes": + if (!canUser(req.user, CzchanPerm.MANAGE_ATTRIBUTES)) + throw new CzchanError("K tomuto nemáš oprávnění.", 403); + html = ManageAttributesPage(ctx, posts); break; } - const ctx = await getCtx(req); - const html = ResultPage(ctx, ["Zvolená akce byla úspěšně provedena."]); - res.send(html); }; diff --git a/src/web/routes/mod/actions/create_banner.ts b/src/web/routes/mod/actions/create_banner.ts index 0249f3f..5f0f99a 100644 --- a/src/web/routes/mod/actions/create_banner.ts +++ b/src/web/routes/mod/actions/create_banner.ts @@ -13,7 +13,7 @@ export default async (req: Request, res: Response) => { const parser = formidable({ allowEmptyFiles: true, minFileSize: 0 }); const [_, files] = await parser.parse(req); - const files_ = (files["banners"] || []).filter((file) => file.size !== 0); + const files_ = (files["banners"] || []).filter((file) => file.size > 0); const processedFiles = await processFiles( req.config, diff --git a/src/web/routes/mod/boards.tsx b/src/web/routes/mod/boards.tsx index 3667dd9..5b31027 100755 --- a/src/web/routes/mod/boards.tsx +++ b/src/web/routes/mod/boards.tsx @@ -103,7 +103,7 @@ const BoardRow = ({ {ctx.user?.username === board.created_by || canUser(ctx.user, CzchanPerm.ALL_BOARDS) ? ( - [Upravit] + [Upravit] ) : ( [Upravit] )} diff --git a/src/web/routes/mod/board_config.tsx b/src/web/routes/mod/edit/board_config.tsx similarity index 65% rename from src/web/routes/mod/board_config.tsx rename to src/web/routes/mod/edit/board_config.tsx index 8dd9dc3..bfa5d0b 100644 --- a/src/web/routes/mod/board_config.tsx +++ b/src/web/routes/mod/edit/board_config.tsx @@ -1,13 +1,13 @@ -import { readBoard } from "../../../db/board"; -import { CzchanError } from "../../../lib/error"; -import { canUser, CzchanPerm } from "../../../lib/permissions"; -import { Board } from "../../../schema/tables"; -import { zBoardID } from "../../../schema/validation/board"; -import Page from "../../components/chrome/page"; -import PageBody from "../../components/chrome/page_body"; -import PageHead from "../../components/chrome/page_head"; -import BoardConfigForm from "../../components/forms/board_config_form"; -import { getCtx, TemplateCtx } from "../../ctx"; +import { readBoard } from "../../../../db/board"; +import { CzchanError } from "../../../../lib/error"; +import { canUser, CzchanPerm } from "../../../../lib/permissions"; +import { Board } from "../../../../schema/tables"; +import { zBoardID } from "../../../../schema/validation/board"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import BoardConfigForm from "../../../components/forms/board_config_form"; +import { getCtx, TemplateCtx } from "../../../ctx"; import { Request, Response } from "express"; export default async (req: Request, res: Response) => { diff --git a/src/web/routes/mod/edit/news_content.tsx b/src/web/routes/mod/edit/news_content.tsx new file mode 100644 index 0000000..27e0e55 --- /dev/null +++ b/src/web/routes/mod/edit/news_content.tsx @@ -0,0 +1,70 @@ +import { readNewsPost } from "../../../../db/news"; +import { CzchanError } from "../../../../lib/error"; +import { canUser, CzchanPerm } from "../../../../lib/permissions"; +import { News } from "../../../../schema/tables"; +import { zNewsID } from "../../../../schema/validation/news"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import { Form, FormButton, FormField } from "../../../components/forms/form"; +import { getCtx, TemplateCtx } from "../../../ctx"; +import { PropsWithChildren } from "@kitajs/html"; +import { Request, Response } from "express"; + +export default async (req: Request, res: Response) => { + const ctx = await getCtx(req); + + if (!canUser(req.user, CzchanPerm.MANAGE_NEWS)) + throw new CzchanError("K tomuto nemáš oprávnění.", 403); + + const news = await readNewsPost(zNewsID.parse(req.params.news)); + + if ( + !( + req.user?.username === news.created_by || + canUser(req.user, CzchanPerm.ALL_NEWS) + ) + ) { + throw new CzchanError( + `K upravení novinek ${news.id} nemáš oprávnění.`, + 403, + ); + } + + const html = Template(ctx, news); + + res.send(html); +}; + +const Template = (ctx: TemplateCtx, news: News) => ( + + + +

+ Novinky +
({news.subject}) +

+ +
+
+
+); + +const UpdateForm = ({ + news, +}: PropsWithChildren<{ ctx: TemplateCtx; news: News }>) => ( +
+ + + + + + + + + + + +); diff --git a/src/web/routes/mod/user_permissions.tsx b/src/web/routes/mod/edit/user_permissions.tsx similarity index 78% rename from src/web/routes/mod/user_permissions.tsx rename to src/web/routes/mod/edit/user_permissions.tsx index a7371ba..0b2adfd 100644 --- a/src/web/routes/mod/user_permissions.tsx +++ b/src/web/routes/mod/edit/user_permissions.tsx @@ -1,12 +1,12 @@ -import { readUser } from "../../../db/user"; -import { canUser, canUserActOn, CzchanPerm } from "../../../lib/permissions"; -import { User } from "../../../schema/tables"; -import { zUsername } from "../../../schema/validation/user"; -import Page from "../../components/chrome/page"; -import PageBody from "../../components/chrome/page_body"; -import PageHead from "../../components/chrome/page_head"; -import { Form, FormButton, FormField } from "../../components/forms/form"; -import { getCtx, TemplateCtx } from "../../ctx"; +import { readUser } from "../../../../db/user"; +import { canUser, canUserActOn, CzchanPerm } from "../../../../lib/permissions"; +import { User } from "../../../../schema/tables"; +import { zUsername } from "../../../../schema/validation/user"; +import Page from "../../../components/chrome/page"; +import PageBody from "../../../components/chrome/page_body"; +import PageHead from "../../../components/chrome/page_head"; +import { Form, FormButton, FormField } from "../../../components/forms/form"; +import { getCtx, TemplateCtx } from "../../../ctx"; import { PropsWithChildren } from "@kitajs/html"; import { Request, Response } from "express"; diff --git a/src/web/routes/mod/index.tsx b/src/web/routes/mod/index.tsx index 847a82f..4298b47 100755 --- a/src/web/routes/mod/index.tsx +++ b/src/web/routes/mod/index.tsx @@ -66,7 +66,7 @@ const UserTable = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => ( Oprávnění [{ctx.user?.permissions.length}]{" "} - + [Zobrazit] diff --git a/src/web/routes/mod/news.tsx b/src/web/routes/mod/news.tsx index 1e133fe..f0efa68 100755 --- a/src/web/routes/mod/news.tsx +++ b/src/web/routes/mod/news.tsx @@ -100,7 +100,7 @@ const NewsRow = ({ {ctx.user?.username === news.created_by || canUser(ctx.user, CzchanPerm.ALL_NEWS) ? ( - [Upravit] + [Upravit] ) : ( [Upravit] )} diff --git a/src/web/routes/mod/news_content.tsx b/src/web/routes/mod/news_content.tsx deleted file mode 100644 index ea2226f..0000000 --- a/src/web/routes/mod/news_content.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { readNewsPost } from "../../../db/news"; -import { CzchanError } from "../../../lib/error"; -import { canUser, CzchanPerm } from "../../../lib/permissions"; -import { News } from "../../../schema/tables"; -import { zNewsID } from "../../../schema/validation/news"; -import Page from "../../components/chrome/page"; -import PageBody from "../../components/chrome/page_body"; -import PageHead from "../../components/chrome/page_head"; -import { Form, FormButton, FormField } from "../../components/forms/form"; -import { getCtx, TemplateCtx } from "../../ctx"; -import { PropsWithChildren } from "@kitajs/html"; -import { Request, Response } from "express"; - -export default async (req: Request, res: Response) => { - const ctx = await getCtx(req); - - if (!canUser(req.user, CzchanPerm.MANAGE_NEWS)) - throw new CzchanError("K tomuto nemáš oprávnění.", 403); - - const news = await readNewsPost(zNewsID.parse(req.params.news)); - - if ( - !( - req.user?.username === news.created_by || - canUser(req.user, CzchanPerm.ALL_NEWS) - ) - ) { - throw new CzchanError( - `K upravení novinek ${news.id} nemáš oprávnění.`, - 403, - ); - } - - const html = Template(ctx, news); - - res.send(html); -}; - -const Template = (ctx: TemplateCtx, news: News) => ( - - - -

- Novinky -
({news.subject}) -

- -
-
-
-); - -const UpdateForm = ({ - news, -}: PropsWithChildren<{ ctx: TemplateCtx; news: News }>) => ( - <> -
-
- - - - - - - - - - - - -); diff --git a/src/web/routes/mod/users.tsx b/src/web/routes/mod/users.tsx index dcd8644..63f189a 100755 --- a/src/web/routes/mod/users.tsx +++ b/src/web/routes/mod/users.tsx @@ -122,7 +122,7 @@ const UserRow = ({ [{user.permissions.length}]{" "} - [Zobrazit] + [Zobrazit] diff --git a/src/web/routing/register_mod.ts b/src/web/routing/register_mod.ts index 028c38c..07552ca 100644 --- a/src/web/routing/register_mod.ts +++ b/src/web/routing/register_mod.ts @@ -17,21 +17,22 @@ import modActionUpdateUserPermissions from "../routes/mod/actions/update_user_pe import modActionUserActions from "../routes/mod/actions/user_actions"; import modBanners from "../routes/mod/banners"; import modBans from "../routes/mod/bans"; -import modBoardConfig from "../routes/mod/board_config"; import modBoards from "../routes/mod/boards"; +import modEditBoardConfig from "../routes/mod/edit/board_config"; +import modEditNewsContent from "../routes/mod/edit/news_content"; +import modEditUserPermissions from "../routes/mod/edit/user_permissions"; import modFilters from "../routes/mod/filters"; import modIndex from "../routes/mod/index"; import modLogs from "../routes/mod/logs"; import modNews from "../routes/mod/news"; -import modNewsContent from "../routes/mod/news_content"; import modReports from "../routes/mod/reports"; import modRestrictions from "../routes/mod/restrictions"; -import modUserPermissions from "../routes/mod/user_permissions"; import modUsers from "../routes/mod/users"; import { e } from "../templates/error_page"; import { Router } from "express"; const registerModRoutes = (router: Router) => { + // Dashboards router.get("/mod/", e(modIndex)); router.get("/mod/users", e(modUsers)); router.get("/mod/boards", e(modBoards)); @@ -43,10 +44,26 @@ const registerModRoutes = (router: Router) => { router.get("/mod/filters", e(modFilters)); router.get("/mod/logs", e(modLogs)); - router.get("/mod/user_permissions/:user", e(modUserPermissions)); - router.get("/mod/board_config/:board", e(modBoardConfig)); - router.get("/mod/news_content/:news", e(modNewsContent)); + // Edit pages + router.get("/mod/edit/user_permissions/:user", e(modEditUserPermissions)); + router.get("/mod/edit/board_config/:board", e(modEditBoardConfig)); + router.get("/mod/edit/news_content/:news", e(modEditNewsContent)); + // Edit page actions + router.post( + "/mod/actions/update_user_permissions", + e(modActionUpdateUserPermissions), + ); + router.post( + "/mod/actions/update_board_config", + e(modActionUpdateBoardConfig), + ); + router.post( + "/mod/actions/update_news_content", + e(modActionUpdateNewsContent), + ); + + // Action pages router.post("/mod/actions/create_user", e(modActionCreateUser)); router.post("/mod/actions/user_actions", e(modActionUserActions)); router.post( @@ -67,18 +84,6 @@ const registerModRoutes = (router: Router) => { ); router.post("/mod/actions/create_filter", e(modActionCreateFilter)); router.post("/mod/actions/filter_actions", e(modActionFilterActions)); - router.post( - "/mod/actions/update_user_permissions", - e(modActionUpdateUserPermissions), - ); - router.post( - "/mod/actions/update_board_config", - e(modActionUpdateBoardConfig), - ); - router.post( - "/mod/actions/update_news_content", - e(modActionUpdateNewsContent), - ); }; export { registerModRoutes }; diff --git a/web/sass/base.scss b/web/sass/base.scss index 6821109..eb88c4f 100755 --- a/web/sass/base.scss +++ b/web/sass/base.scss @@ -158,7 +158,7 @@ footer { list-style-type: none; } -.action-details { +.form-group { display: inline-block; padding: 8px; } @@ -216,7 +216,8 @@ footer { display: none; } -.catalog-tile .thumb { +.catalog-tile .thumb, +.mod-thumb .thumb { margin: 8px auto; max-width: 75%; max-height: 50%; diff --git a/web/sass/yotsuba-base.scss b/web/sass/yotsuba-base.scss index 81cf075..e082343 100644 --- a/web/sass/yotsuba-base.scss +++ b/web/sass/yotsuba-base.scss @@ -168,7 +168,7 @@ select, .inner-footer, .reply, .catalog-tile, -.action-details { +.form-group { border-right: 1px solid $box-border; border-bottom: 1px solid $box-border; background-color: $box-bg; @@ -183,7 +183,7 @@ select, /* Actions */ -.action-details .form-table { +.form-group .form-table { border: none; background: none; }