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 (
-
+
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, () =>