diff --git a/client/components/app/index.tsx b/client/components/app/index.tsx index 66f85cba..8504e8f4 100644 --- a/client/components/app/index.tsx +++ b/client/components/app/index.tsx @@ -28,6 +28,21 @@ const App = ({ accents_8: 'var(--darkest-gray)', border: 'var(--light-gray)', }, + expressiveness: { + dropdownBoxShadow: '0 0 0 1px var(--light-gray)', + shadowSmall: '0 0 0 1px var(--light-gray)', + shadowLarge: '0 0 0 1px var(--light-gray)', + shadowMedium: '0 0 0 1px var(--light-gray)', + }, + layout: { + gap: 'var(--gap)', + gapHalf: 'var(--gap-half)', + gapQuarter: 'var(--gap-quarter)', + gapNegative: 'var(--gap-negative)', + gapHalfNegative: 'var(--gap-half-negative)', + gapQuarterNegative: 'var(--gap-quarter-negative)', + radius: 'var(--radius)', + }, font: { mono: 'var(--font-mono)', sans: 'var(--font-sans)', diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index e5ab8ef1..c5c9e049 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -130,7 +130,7 @@ const Post = () => { }, [title]) return ( -
+
<FileDropzone setDocs={uploadDocs} /> <EditDocumentList onPaste={onPaste} docs={docs} updateDocTitle={updateDocTitle} updateDocContent={updateDocContent} removeDoc={removeDoc} /> diff --git a/client/styles/globals.css b/client/styles/globals.css index 5d6b7283..78986236 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -8,6 +8,9 @@ --gap-half: 0.5rem; --gap: 1rem; --gap-double: 2rem; + --gap-negative: calc(-1 * var(--gap)); + --gap-half-negative: calc(-1 * var(--gap-half)); + --gap-quarter-negative: calc(-1 * var(--gap-quarter)); --small-gap: 4rem; --big-gap: 4rem; --main-content: 55rem; diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 8d98d12a..b9154f81 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -11,226 +11,227 @@ import markdown from "@lib/render-markdown" export const posts = Router() const postVisibilitySchema = (value: string) => { - if (value === "public" || value === "private") { - return value - } else { - throw new Error("Invalid post visibility") - } + if (value === "public" || value === "private" || + value === "unlisted" || value === "protected") { + return value + } else { + throw new Error("Invalid post visibility") + } } posts.post( - "/create", - jwt, - celebrate({ - body: { - title: Joi.string().required().allow("", null), - files: Joi.any().required(), - visibility: Joi.string() - .custom(postVisibilitySchema, "valid visibility") - .required(), - userId: Joi.string().required(), - password: Joi.string().optional() - } - }), - async (req, res, next) => { - try { - let hashedPassword: string = "" - if (req.body.visibility === "protected") { - hashedPassword = crypto - .createHash("sha256") - .update(req.body.password) - .digest("hex") - } + "/create", + jwt, + celebrate({ + body: { + title: Joi.string().required().allow("", null), + files: Joi.any().required(), + visibility: Joi.string() + .custom(postVisibilitySchema, "valid visibility") + .required(), + userId: Joi.string().required(), + password: Joi.string().optional() + } + }), + async (req, res, next) => { + try { + let hashedPassword: string = "" + if (req.body.visibility === "protected") { + hashedPassword = crypto + .createHash("sha256") + .update(req.body.password) + .digest("hex") + } - const newPost = new Post({ - title: req.body.title, - visibility: req.body.visibility, - password: hashedPassword - }) + const newPost = new Post({ + title: req.body.title, + visibility: req.body.visibility, + password: hashedPassword + }) - await newPost.save() - await newPost.$add("users", req.body.userId) - const newFiles = await Promise.all( - req.body.files.map(async (file) => { - const html = getHtmlFromFile(file) - const newFile = new File({ - title: file.title || "", - content: file.content, - sha: crypto - .createHash("sha256") - .update(file.content) - .digest("hex") - .toString(), - html - }) + await newPost.save() + await newPost.$add("users", req.body.userId) + const newFiles = await Promise.all( + req.body.files.map(async (file) => { + const html = getHtmlFromFile(file) + const newFile = new File({ + title: file.title || "", + content: file.content, + sha: crypto + .createHash("sha256") + .update(file.content) + .digest("hex") + .toString(), + html + }) - await newFile.$set("user", req.body.userId) - await newFile.$set("post", newPost.id) - await newFile.save() - return newFile - }) - ) + await newFile.$set("user", req.body.userId) + await newFile.$set("post", newPost.id) + await newFile.save() + return newFile + }) + ) - await Promise.all( - newFiles.map((file) => { - newPost.$add("files", file.id) - newPost.save() - }) - ) + await Promise.all( + newFiles.map((file) => { + newPost.$add("files", file.id) + newPost.save() + }) + ) - res.json(newPost) - } catch (e) { - next(e) - } - } + res.json(newPost) + } catch (e) { + next(e) + } + } ) posts.get("/", secretKey, async (req, res, next) => { - try { - const posts = await Post.findAll({ - attributes: ["id", "title", "visibility", "createdAt"] - }) + try { + const posts = await Post.findAll({ + attributes: ["id", "title", "visibility", "createdAt"] + }) - res.json(posts) - } catch (e) { - next(e) - } + res.json(posts) + } catch (e) { + next(e) + } }) posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => { - if (!req.user) { - return res.status(401).json({ error: "Unauthorized" }) - } + if (!req.user) { + return res.status(401).json({ error: "Unauthorized" }) + } - try { - const user = await User.findByPk(req.user.id, { - include: [ - { - model: Post, - as: "posts", - include: [ - { - model: File, - as: "files" - } - ] - } - ] - }) - if (!user) { - return res.status(404).json({ error: "User not found" }) - } - return res.json( - user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) - ) - } catch (error) { - next(error) - } + try { + const user = await User.findByPk(req.user.id, { + include: [ + { + model: Post, + as: "posts", + include: [ + { + model: File, + as: "files" + } + ] + } + ] + }) + if (!user) { + return res.status(404).json({ error: "User not found" }) + } + return res.json( + user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + ) + } catch (error) { + next(error) + } }) posts.get( - "/:id", - celebrate({ - params: { - id: Joi.string().required() - } - }), - async (req: UserJwtRequest, res, next) => { - try { - const post = await Post.findOne({ - where: { - id: req.params.id - }, - include: [ - { - model: File, - as: "files", - attributes: [ - "id", - "title", - "content", - "sha", - "createdAt", - "updatedAt" - ] - }, - { - model: User, - as: "users", - attributes: ["id", "username"] - } - ] - }) + "/:id", + celebrate({ + params: { + id: Joi.string().required() + } + }), + async (req: UserJwtRequest, res, next) => { + try { + const post = await Post.findOne({ + where: { + id: req.params.id + }, + include: [ + { + model: File, + as: "files", + attributes: [ + "id", + "title", + "content", + "sha", + "createdAt", + "updatedAt" + ] + }, + { + model: User, + as: "users", + attributes: ["id", "username"] + } + ] + }) - if (!post) { - return res.status(404).json({ error: "Post not found" }) - } + if (!post) { + return res.status(404).json({ error: "Post not found" }) + } - // if public or unlisted, cache - if (post.visibility === "public" || post.visibility === "unlisted") { - res.set("Cache-Control", "public, max-age=4800") - } + // if public or unlisted, cache + if (post.visibility === "public" || post.visibility === "unlisted") { + res.set("Cache-Control", "public, max-age=4800") + } - if (post.visibility === "public" || post?.visibility === "unlisted") { - secretKey(req, res, () => { - res.json(post) - }) - } else if (post.visibility === "private") { - jwt(req as UserJwtRequest, res, () => { - res.json(post) - }) - } else if (post.visibility === "protected") { - const { password } = req.query - if (!password || typeof password !== "string") { - return jwt(req as UserJwtRequest, res, () => { - res.json(post) - }) - } - const hash = crypto - .createHash("sha256") - .update(password) - .digest("hex") - .toString() - if (hash !== post.password) { - return res.status(400).json({ error: "Incorrect password." }) - } + if (post.visibility === "public" || post?.visibility === "unlisted") { + secretKey(req, res, () => { + res.json(post) + }) + } else if (post.visibility === "private") { + jwt(req as UserJwtRequest, res, () => { + res.json(post) + }) + } else if (post.visibility === "protected") { + const { password } = req.query + if (!password || typeof password !== "string") { + return jwt(req as UserJwtRequest, res, () => { + res.json(post) + }) + } + const hash = crypto + .createHash("sha256") + .update(password) + .digest("hex") + .toString() + if (hash !== post.password) { + return res.status(400).json({ error: "Incorrect password." }) + } - res.json(post) - } - } catch (e) { - next(e) - } - } + res.json(post) + } + } catch (e) { + next(e) + } + } ) function getHtmlFromFile(file: any) { - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = file.title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = file.content || "" + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = file.title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = file.content || "" - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${file.content} ~~~` - } else { - contentToRender = "\n" + file.content - } - const html = markdown(contentToRender) - return html + } else { + contentToRender = "\n" + file.content + } + const html = markdown(contentToRender) + return html } diff --git a/server/src/server.ts b/server/src/server.ts index 826154ec..4d729ec8 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -2,7 +2,6 @@ import { createServer } from "http" import { app } from "./app" import config from "./lib/config" import { sequelize } from "./lib/sequelize" - ;(async () => { await sequelize.sync({}) createServer(app).listen(config.port, () =>