From 5df56fbdae336ff0a0e69bb1f530a18e34cf174b Mon Sep 17 00:00:00 2001 From: "Joaquin \"Florius\" Azcarate" Date: Thu, 14 Apr 2022 23:25:31 +0200 Subject: [PATCH] Add description to posts (#71) Closes #37 --- .../components/new-post/description/index.tsx | 25 +++++++++++++++++++ client/components/new-post/index.tsx | 12 +++++++++ client/components/new-post/post.module.css | 6 +++++ .../components/post-list/list-item.module.css | 6 +++++ client/components/post-list/list-item.tsx | 6 +++++ client/components/post-page/index.tsx | 5 ++++ server/src/lib/models/Post.ts | 3 +++ server/src/migrations/08_description_posts.ts | 12 +++++++++ server/src/routes/posts.ts | 24 +++++++++++++++--- 9 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 client/components/new-post/description/index.tsx create mode 100644 server/src/migrations/08_description_posts.ts diff --git a/client/components/new-post/description/index.tsx b/client/components/new-post/description/index.tsx new file mode 100644 index 00000000..bf4983f7 --- /dev/null +++ b/client/components/new-post/description/index.tsx @@ -0,0 +1,25 @@ +import { ChangeEvent, memo } from "react" +import { Input } from "@geist-ui/core" + +import styles from "../post.module.css" + +type props = { + onChange: (e: ChangeEvent) => void + description?: string +} + +const Description = ({ onChange, description }: props) => { + return ( +
+ +
+ ) +} + +export default memo(Description) diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 0e0e177f..244d16ad 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -24,6 +24,7 @@ import EditDocumentList from "@components/edit-document-list" import { ChangeEvent } from "react" import DatePicker from "react-datepicker" import getTitleForPostCopy from "@lib/get-title-for-post-copy" +import Description from "./description" const Post = ({ initialPost, @@ -35,6 +36,7 @@ const Post = ({ const { setToast } = useToasts() const router = useRouter() const [title, setTitle] = useState() + const [description, setDescription] = useState() const [expiresAt, setExpiresAt] = useState(null) const emptyDoc = useMemo( @@ -62,6 +64,7 @@ const Post = ({ ) setTitle(getTitleForPostCopy(initialPost.title)) + setDescription(initialPost.description) } }, [emptyDoc, initialPost]) @@ -88,6 +91,7 @@ const Post = ({ }, body: JSON.stringify({ title, + description, files: docs, ...data }) @@ -187,6 +191,13 @@ const Post = ({ [setTitle] ) + const onChangeDescription = useCallback( + (e: ChangeEvent) => { + setDescription(e.target.value) + }, + [setDescription] + ) + const updateDocTitle = useCallback( (i: number) => (title: string) => { setDocs((docs) => @@ -284,6 +295,7 @@ const Post = ({ return (
+ <Description description={description} onChange={onChangeDescription} /> <FileDropzone setDocs={uploadDocs} /> <EditDocumentList onPaste={onPaste} diff --git a/client/components/new-post/post.module.css b/client/components/new-post/post.module.css index b6faa863..155b476e 100644 --- a/client/components/new-post/post.module.css +++ b/client/components/new-post/post.module.css @@ -25,6 +25,12 @@ margin-bottom: var(--gap); } +.description { + width: 100%; + margin-bottom: var(--gap); +} + + @media screen and (max-width: 650px) { .title { align-items: flex-start; diff --git a/client/components/post-list/list-item.module.css b/client/components/post-list/list-item.module.css index 5c8b732c..f3577667 100644 --- a/client/components/post-list/list-item.module.css +++ b/client/components/post-list/list-item.module.css @@ -13,6 +13,12 @@ gap: var(--gap-half); } +.oneline { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + @media screen and (max-width: 700px) { .badges { flex-direction: column; diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index 6474774a..3127046d 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -78,6 +78,12 @@ const ListItem = ({ )} </Text> + {post.description && ( + <Text p className={styles.oneline}> + {post.description} + </Text> + )} + <div className={styles.badges}> <VisibilityBadge visibility={post.visibility} /> <CreatedAgoBadge createdAt={post.createdAt} /> diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 8999ae83..712d554d 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -149,6 +149,11 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => { </span> </span> </div> + {post.description && ( + <div> + <Text p>{post.description}</Text> + </div> + )} {/* {post.files.length > 1 && <FileTree files={post.files} />} */} {post.files?.map(({ id, content, title }: File) => ( <DocumentComponent diff --git a/server/src/lib/models/Post.ts b/server/src/lib/models/Post.ts index b8c03a8c..d0023e3f 100644 --- a/server/src/lib/models/Post.ts +++ b/server/src/lib/models/Post.ts @@ -55,6 +55,9 @@ export class Post extends Model { @Column title!: string + @Column + description?: string + @BelongsToMany(() => User, () => PostAuthor) users?: User[] diff --git a/server/src/migrations/08_description_posts.ts b/server/src/migrations/08_description_posts.ts new file mode 100644 index 00000000..5139b029 --- /dev/null +++ b/server/src/migrations/08_description_posts.ts @@ -0,0 +1,12 @@ +"use strict" +import { DataTypes } from "sequelize" +import type { Migration } from "../database" + +export const up: Migration = async ({ context: queryInterface }) => + queryInterface.addColumn("posts", "description", { + type: DataTypes.STRING, + allowNull: true + }) + +export const down: Migration = async ({ context: queryInterface }) => + await queryInterface.removeColumn("posts", "description") diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index b2e34c89..36966bcf 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -31,6 +31,7 @@ posts.post( celebrate({ body: { title: Joi.string().required(), + description: Joi.string().optional().min(0).max(256), files: Joi.any().required(), visibility: Joi.string() .custom(postVisibilitySchema, "valid visibility") @@ -66,6 +67,7 @@ posts.post( const newPost = new Post({ title: req.body.title, + description: req.body.description, visibility: req.body.visibility, password: hashedPassword, expiresAt: req.body.expiresAt @@ -126,7 +128,7 @@ posts.post( posts.get("/", secretKey, async (req, res, next) => { try { const posts = await Post.findAll({ - attributes: ["id", "title", "visibility", "createdAt"] + attributes: ["id", "title", "description", "visibility", "createdAt"] }) res.json(posts) } catch (e) { @@ -159,7 +161,14 @@ posts.get("/mine", jwt, async (req: UserJwtRequest, res, next) => { attributes: ["id", "title", "visibility"] } ], - attributes: ["id", "title", "visibility", "createdAt", "expiresAt"] + attributes: [ + "id", + "title", + "description", + "visibility", + "createdAt", + "expiresAt" + ] } ] }) @@ -205,6 +214,7 @@ posts.get( where: { [Op.or]: [ { title: { [Op.like]: `%${q}%` } }, + { description: { [Op.like]: `%${q}%` } }, { "$files.title$": { [Op.like]: `%${q}%` } }, { "$files.content$": { [Op.like]: `%${q}%` } } ], @@ -227,7 +237,14 @@ posts.get( attributes: ["id", "title", "visibility"] } ], - attributes: ["id", "title", "visibility", "createdAt", "deletedAt"], + attributes: [ + "id", + "title", + "description", + "visibility", + "createdAt", + "deletedAt" + ], order: [["createdAt", "DESC"]] }) @@ -259,6 +276,7 @@ const fullPostSequelizeOptions = { attributes: [ "id", "title", + "description", "visibility", "createdAt", "updatedAt",