parent
2ba613d562
commit
5df56fbdae
9 changed files with 96 additions and 3 deletions
25
client/components/new-post/description/index.tsx
Normal file
25
client/components/new-post/description/index.tsx
Normal file
|
@ -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<HTMLInputElement>) => void
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Description = ({ onChange, description }: props) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Input
|
||||||
|
value={description}
|
||||||
|
onChange={onChange}
|
||||||
|
label="Description"
|
||||||
|
maxLength={256}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Description)
|
|
@ -24,6 +24,7 @@ import EditDocumentList from "@components/edit-document-list"
|
||||||
import { ChangeEvent } from "react"
|
import { ChangeEvent } from "react"
|
||||||
import DatePicker from "react-datepicker"
|
import DatePicker from "react-datepicker"
|
||||||
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
||||||
|
import Description from "./description"
|
||||||
|
|
||||||
const Post = ({
|
const Post = ({
|
||||||
initialPost,
|
initialPost,
|
||||||
|
@ -35,6 +36,7 @@ const Post = ({
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [title, setTitle] = useState<string>()
|
const [title, setTitle] = useState<string>()
|
||||||
|
const [description, setDescription] = useState<string>()
|
||||||
const [expiresAt, setExpiresAt] = useState<Date | null>(null)
|
const [expiresAt, setExpiresAt] = useState<Date | null>(null)
|
||||||
|
|
||||||
const emptyDoc = useMemo(
|
const emptyDoc = useMemo(
|
||||||
|
@ -62,6 +64,7 @@ const Post = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
setTitle(getTitleForPostCopy(initialPost.title))
|
setTitle(getTitleForPostCopy(initialPost.title))
|
||||||
|
setDescription(initialPost.description)
|
||||||
}
|
}
|
||||||
}, [emptyDoc, initialPost])
|
}, [emptyDoc, initialPost])
|
||||||
|
|
||||||
|
@ -88,6 +91,7 @@ const Post = ({
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
files: docs,
|
files: docs,
|
||||||
...data
|
...data
|
||||||
})
|
})
|
||||||
|
@ -187,6 +191,13 @@ const Post = ({
|
||||||
[setTitle]
|
[setTitle]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onChangeDescription = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setDescription(e.target.value)
|
||||||
|
},
|
||||||
|
[setDescription]
|
||||||
|
)
|
||||||
|
|
||||||
const updateDocTitle = useCallback(
|
const updateDocTitle = useCallback(
|
||||||
(i: number) => (title: string) => {
|
(i: number) => (title: string) => {
|
||||||
setDocs((docs) =>
|
setDocs((docs) =>
|
||||||
|
@ -284,6 +295,7 @@ const Post = ({
|
||||||
return (
|
return (
|
||||||
<div style={{ paddingBottom: 150 }}>
|
<div style={{ paddingBottom: 150 }}>
|
||||||
<Title title={title} onChange={onChangeTitle} />
|
<Title title={title} onChange={onChangeTitle} />
|
||||||
|
<Description description={description} onChange={onChangeDescription} />
|
||||||
<FileDropzone setDocs={uploadDocs} />
|
<FileDropzone setDocs={uploadDocs} />
|
||||||
<EditDocumentList
|
<EditDocumentList
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
|
|
|
@ -25,6 +25,12 @@
|
||||||
margin-bottom: var(--gap);
|
margin-bottom: var(--gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 650px) {
|
@media screen and (max-width: 650px) {
|
||||||
.title {
|
.title {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
gap: var(--gap-half);
|
gap: var(--gap-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oneline {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
@media screen and (max-width: 700px) {
|
||||||
.badges {
|
.badges {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -78,6 +78,12 @@ const ListItem = ({
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{post.description && (
|
||||||
|
<Text p className={styles.oneline}>
|
||||||
|
{post.description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.badges}>
|
<div className={styles.badges}>
|
||||||
<VisibilityBadge visibility={post.visibility} />
|
<VisibilityBadge visibility={post.visibility} />
|
||||||
<CreatedAgoBadge createdAt={post.createdAt} />
|
<CreatedAgoBadge createdAt={post.createdAt} />
|
||||||
|
|
|
@ -149,6 +149,11 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{post.description && (
|
||||||
|
<div>
|
||||||
|
<Text p>{post.description}</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* {post.files.length > 1 && <FileTree files={post.files} />} */}
|
{/* {post.files.length > 1 && <FileTree files={post.files} />} */}
|
||||||
{post.files?.map(({ id, content, title }: File) => (
|
{post.files?.map(({ id, content, title }: File) => (
|
||||||
<DocumentComponent
|
<DocumentComponent
|
||||||
|
|
|
@ -55,6 +55,9 @@ export class Post extends Model {
|
||||||
@Column
|
@Column
|
||||||
title!: string
|
title!: string
|
||||||
|
|
||||||
|
@Column
|
||||||
|
description?: string
|
||||||
|
|
||||||
@BelongsToMany(() => User, () => PostAuthor)
|
@BelongsToMany(() => User, () => PostAuthor)
|
||||||
users?: User[]
|
users?: User[]
|
||||||
|
|
||||||
|
|
12
server/src/migrations/08_description_posts.ts
Normal file
12
server/src/migrations/08_description_posts.ts
Normal file
|
@ -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")
|
|
@ -31,6 +31,7 @@ posts.post(
|
||||||
celebrate({
|
celebrate({
|
||||||
body: {
|
body: {
|
||||||
title: Joi.string().required(),
|
title: Joi.string().required(),
|
||||||
|
description: Joi.string().optional().min(0).max(256),
|
||||||
files: Joi.any().required(),
|
files: Joi.any().required(),
|
||||||
visibility: Joi.string()
|
visibility: Joi.string()
|
||||||
.custom(postVisibilitySchema, "valid visibility")
|
.custom(postVisibilitySchema, "valid visibility")
|
||||||
|
@ -66,6 +67,7 @@ posts.post(
|
||||||
|
|
||||||
const newPost = new Post({
|
const newPost = new Post({
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
|
description: req.body.description,
|
||||||
visibility: req.body.visibility,
|
visibility: req.body.visibility,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
expiresAt: req.body.expiresAt
|
expiresAt: req.body.expiresAt
|
||||||
|
@ -126,7 +128,7 @@ posts.post(
|
||||||
posts.get("/", secretKey, async (req, res, next) => {
|
posts.get("/", secretKey, async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const posts = await Post.findAll({
|
const posts = await Post.findAll({
|
||||||
attributes: ["id", "title", "visibility", "createdAt"]
|
attributes: ["id", "title", "description", "visibility", "createdAt"]
|
||||||
})
|
})
|
||||||
res.json(posts)
|
res.json(posts)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -159,7 +161,14 @@ posts.get("/mine", jwt, async (req: UserJwtRequest, res, next) => {
|
||||||
attributes: ["id", "title", "visibility"]
|
attributes: ["id", "title", "visibility"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
attributes: ["id", "title", "visibility", "createdAt", "expiresAt"]
|
attributes: [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"visibility",
|
||||||
|
"createdAt",
|
||||||
|
"expiresAt"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -205,6 +214,7 @@ posts.get(
|
||||||
where: {
|
where: {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ title: { [Op.like]: `%${q}%` } },
|
{ title: { [Op.like]: `%${q}%` } },
|
||||||
|
{ description: { [Op.like]: `%${q}%` } },
|
||||||
{ "$files.title$": { [Op.like]: `%${q}%` } },
|
{ "$files.title$": { [Op.like]: `%${q}%` } },
|
||||||
{ "$files.content$": { [Op.like]: `%${q}%` } }
|
{ "$files.content$": { [Op.like]: `%${q}%` } }
|
||||||
],
|
],
|
||||||
|
@ -227,7 +237,14 @@ posts.get(
|
||||||
attributes: ["id", "title", "visibility"]
|
attributes: ["id", "title", "visibility"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
attributes: ["id", "title", "visibility", "createdAt", "deletedAt"],
|
attributes: [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"visibility",
|
||||||
|
"createdAt",
|
||||||
|
"deletedAt"
|
||||||
|
],
|
||||||
order: [["createdAt", "DESC"]]
|
order: [["createdAt", "DESC"]]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -259,6 +276,7 @@ const fullPostSequelizeOptions = {
|
||||||
attributes: [
|
attributes: [
|
||||||
"id",
|
"id",
|
||||||
"title",
|
"title",
|
||||||
|
"description",
|
||||||
"visibility",
|
"visibility",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
|
|
Loading…
Reference in a new issue