client: improve theming variable consistency

This commit is contained in:
Max Leiter 2022-03-24 15:12:54 -07:00
parent 2823c217ea
commit e5f467b26a
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
5 changed files with 213 additions and 195 deletions

View file

@ -28,6 +28,21 @@ const App = ({
accents_8: 'var(--darkest-gray)', accents_8: 'var(--darkest-gray)',
border: 'var(--light-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: { font: {
mono: 'var(--font-mono)', mono: 'var(--font-mono)',
sans: 'var(--font-sans)', sans: 'var(--font-sans)',

View file

@ -130,7 +130,7 @@ const Post = () => {
}, [title]) }, [title])
return ( return (
<div style={{ marginBottom: 150 }}> <div style={{ paddingBottom: 150 }}>
<Title title={title} onChange={onChangeTitle} /> <Title title={title} onChange={onChangeTitle} />
<FileDropzone setDocs={uploadDocs} /> <FileDropzone setDocs={uploadDocs} />
<EditDocumentList onPaste={onPaste} docs={docs} updateDocTitle={updateDocTitle} updateDocContent={updateDocContent} removeDoc={removeDoc} /> <EditDocumentList onPaste={onPaste} docs={docs} updateDocTitle={updateDocTitle} updateDocContent={updateDocContent} removeDoc={removeDoc} />

View file

@ -8,6 +8,9 @@
--gap-half: 0.5rem; --gap-half: 0.5rem;
--gap: 1rem; --gap: 1rem;
--gap-double: 2rem; --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; --small-gap: 4rem;
--big-gap: 4rem; --big-gap: 4rem;
--main-content: 55rem; --main-content: 55rem;

View file

@ -11,226 +11,227 @@ import markdown from "@lib/render-markdown"
export const posts = Router() export const posts = Router()
const postVisibilitySchema = (value: string) => { const postVisibilitySchema = (value: string) => {
if (value === "public" || value === "private") { if (value === "public" || value === "private" ||
return value value === "unlisted" || value === "protected") {
} else { return value
throw new Error("Invalid post visibility") } else {
} throw new Error("Invalid post visibility")
}
} }
posts.post( posts.post(
"/create", "/create",
jwt, jwt,
celebrate({ celebrate({
body: { body: {
title: Joi.string().required().allow("", null), title: Joi.string().required().allow("", null),
files: Joi.any().required(), files: Joi.any().required(),
visibility: Joi.string() visibility: Joi.string()
.custom(postVisibilitySchema, "valid visibility") .custom(postVisibilitySchema, "valid visibility")
.required(), .required(),
userId: Joi.string().required(), userId: Joi.string().required(),
password: Joi.string().optional() password: Joi.string().optional()
} }
}), }),
async (req, res, next) => { async (req, res, next) => {
try { try {
let hashedPassword: string = "" let hashedPassword: string = ""
if (req.body.visibility === "protected") { if (req.body.visibility === "protected") {
hashedPassword = crypto hashedPassword = crypto
.createHash("sha256") .createHash("sha256")
.update(req.body.password) .update(req.body.password)
.digest("hex") .digest("hex")
} }
const newPost = new Post({ const newPost = new Post({
title: req.body.title, title: req.body.title,
visibility: req.body.visibility, visibility: req.body.visibility,
password: hashedPassword password: hashedPassword
}) })
await newPost.save() await newPost.save()
await newPost.$add("users", req.body.userId) await newPost.$add("users", req.body.userId)
const newFiles = await Promise.all( const newFiles = await Promise.all(
req.body.files.map(async (file) => { req.body.files.map(async (file) => {
const html = getHtmlFromFile(file) const html = getHtmlFromFile(file)
const newFile = new File({ const newFile = new File({
title: file.title || "", title: file.title || "",
content: file.content, content: file.content,
sha: crypto sha: crypto
.createHash("sha256") .createHash("sha256")
.update(file.content) .update(file.content)
.digest("hex") .digest("hex")
.toString(), .toString(),
html html
}) })
await newFile.$set("user", req.body.userId) await newFile.$set("user", req.body.userId)
await newFile.$set("post", newPost.id) await newFile.$set("post", newPost.id)
await newFile.save() await newFile.save()
return newFile return newFile
}) })
) )
await Promise.all( await Promise.all(
newFiles.map((file) => { newFiles.map((file) => {
newPost.$add("files", file.id) newPost.$add("files", file.id)
newPost.save() newPost.save()
}) })
) )
res.json(newPost) res.json(newPost)
} catch (e) { } catch (e) {
next(e) next(e)
} }
} }
) )
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", "visibility", "createdAt"]
}) })
res.json(posts) res.json(posts)
} catch (e) { } catch (e) {
next(e) next(e)
} }
}) })
posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => { posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => {
if (!req.user) { if (!req.user) {
return res.status(401).json({ error: "Unauthorized" }) return res.status(401).json({ error: "Unauthorized" })
} }
try { try {
const user = await User.findByPk(req.user.id, { const user = await User.findByPk(req.user.id, {
include: [ include: [
{ {
model: Post, model: Post,
as: "posts", as: "posts",
include: [ include: [
{ {
model: File, model: File,
as: "files" as: "files"
} }
] ]
} }
] ]
}) })
if (!user) { if (!user) {
return res.status(404).json({ error: "User not found" }) return res.status(404).json({ error: "User not found" })
} }
return res.json( return res.json(
user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
) )
} catch (error) { } catch (error) {
next(error) next(error)
} }
}) })
posts.get( posts.get(
"/:id", "/:id",
celebrate({ celebrate({
params: { params: {
id: Joi.string().required() id: Joi.string().required()
} }
}), }),
async (req: UserJwtRequest, res, next) => { async (req: UserJwtRequest, res, next) => {
try { try {
const post = await Post.findOne({ const post = await Post.findOne({
where: { where: {
id: req.params.id id: req.params.id
}, },
include: [ include: [
{ {
model: File, model: File,
as: "files", as: "files",
attributes: [ attributes: [
"id", "id",
"title", "title",
"content", "content",
"sha", "sha",
"createdAt", "createdAt",
"updatedAt" "updatedAt"
] ]
}, },
{ {
model: User, model: User,
as: "users", as: "users",
attributes: ["id", "username"] attributes: ["id", "username"]
} }
] ]
}) })
if (!post) { if (!post) {
return res.status(404).json({ error: "Post not found" }) return res.status(404).json({ error: "Post not found" })
} }
// if public or unlisted, cache // if public or unlisted, cache
if (post.visibility === "public" || post.visibility === "unlisted") { if (post.visibility === "public" || post.visibility === "unlisted") {
res.set("Cache-Control", "public, max-age=4800") res.set("Cache-Control", "public, max-age=4800")
} }
if (post.visibility === "public" || post?.visibility === "unlisted") { if (post.visibility === "public" || post?.visibility === "unlisted") {
secretKey(req, res, () => { secretKey(req, res, () => {
res.json(post) res.json(post)
}) })
} else if (post.visibility === "private") { } else if (post.visibility === "private") {
jwt(req as UserJwtRequest, res, () => { jwt(req as UserJwtRequest, res, () => {
res.json(post) res.json(post)
}) })
} else if (post.visibility === "protected") { } else if (post.visibility === "protected") {
const { password } = req.query const { password } = req.query
if (!password || typeof password !== "string") { if (!password || typeof password !== "string") {
return jwt(req as UserJwtRequest, res, () => { return jwt(req as UserJwtRequest, res, () => {
res.json(post) res.json(post)
}) })
} }
const hash = crypto const hash = crypto
.createHash("sha256") .createHash("sha256")
.update(password) .update(password)
.digest("hex") .digest("hex")
.toString() .toString()
if (hash !== post.password) { if (hash !== post.password) {
return res.status(400).json({ error: "Incorrect password." }) return res.status(400).json({ error: "Incorrect password." })
} }
res.json(post) res.json(post)
} }
} catch (e) { } catch (e) {
next(e) next(e)
} }
} }
) )
function getHtmlFromFile(file: any) { function getHtmlFromFile(file: any) {
const renderAsMarkdown = [ const renderAsMarkdown = [
"markdown", "markdown",
"md", "md",
"mdown", "mdown",
"mkdn", "mkdn",
"mkd", "mkd",
"mdwn", "mdwn",
"mdtxt", "mdtxt",
"mdtext", "mdtext",
"text", "text",
"" ""
] ]
const fileType = () => { const fileType = () => {
const pathParts = file.title.split(".") const pathParts = file.title.split(".")
const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ""
return language return language
} }
const type = fileType() const type = fileType()
let contentToRender: string = file.content || "" let contentToRender: string = file.content || ""
if (!renderAsMarkdown.includes(type)) { if (!renderAsMarkdown.includes(type)) {
contentToRender = `~~~${type} contentToRender = `~~~${type}
${file.content} ${file.content}
~~~` ~~~`
} else { } else {
contentToRender = "\n" + file.content contentToRender = "\n" + file.content
} }
const html = markdown(contentToRender) const html = markdown(contentToRender)
return html return html
} }

View file

@ -2,7 +2,6 @@ import { createServer } from "http"
import { app } from "./app" import { app } from "./app"
import config from "./lib/config" import config from "./lib/config"
import { sequelize } from "./lib/sequelize" import { sequelize } from "./lib/sequelize"
;(async () => { ;(async () => {
await sequelize.sync({}) await sequelize.sync({})
createServer(app).listen(config.port, () => createServer(app).listen(config.port, () =>