client: lint with useTabs

This commit is contained in:
Max Leiter 2022-03-23 15:42:22 -07:00
parent 48a8e9f6a9
commit 60f2ab99b3
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: A3512F2F2F17EBDA
13 changed files with 241 additions and 240 deletions

View file

@ -2,5 +2,6 @@
"semi": false, "semi": false,
"trailingComma": "none", "trailingComma": "none",
"singleQuote": false, "singleQuote": false,
"printWidth": 80 "printWidth": 80,
"useTabs": true
} }

View file

@ -1,4 +1,4 @@
import { ChangeEvent, memo, useCallback, useEffect, useState } from 'react' import { ChangeEvent, memo, useEffect, useState } from 'react'
import { Text } from '@geist-ui/core' import { Text } from '@geist-ui/core'
import ShiftBy from '@components/shift-by' import ShiftBy from '@components/shift-by'

View file

@ -1,39 +1,39 @@
export default function generateUUID() { export default function generateUUID() {
if (typeof crypto === "object") { if (typeof crypto === "object") {
if (typeof crypto.randomUUID === "function") { if (typeof crypto.randomUUID === "function") {
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
return crypto.randomUUID() return crypto.randomUUID()
} }
if ( if (
typeof crypto.getRandomValues === "function" && typeof crypto.getRandomValues === "function" &&
typeof Uint8Array === "function" typeof Uint8Array === "function"
) { ) {
// https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid
const callback = (c: string) => { const callback = (c: string) => {
const num = Number(c) const num = Number(c)
return ( return (
num ^ num ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4))) (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))
).toString(16) ).toString(16)
} }
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback) return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback)
} }
} }
let timestamp = new Date().getTime() let timestamp = new Date().getTime()
let perforNow = let perforNow =
(typeof performance !== "undefined" && (typeof performance !== "undefined" &&
performance.now && performance.now &&
performance.now() * 1000) || performance.now() * 1000) ||
0 0
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
let random = Math.random() * 16 let random = Math.random() * 16
if (timestamp > 0) { if (timestamp > 0) {
random = (timestamp + random) % 16 | 0 random = (timestamp + random) % 16 | 0
timestamp = Math.floor(timestamp / 16) timestamp = Math.floor(timestamp / 16)
} else { } else {
random = (perforNow + random) % 16 | 0 random = (perforNow + random) % 16 | 0
perforNow = Math.floor(perforNow / 16) perforNow = Math.floor(perforNow / 16)
} }
return (c === "x" ? random : (random & 0x3) | 0x8).toString(16) return (c === "x" ? random : (random & 0x3) | 0x8).toString(16)
}) })
} }

View file

@ -1,13 +1,13 @@
import type { PostVisibility } from "./types" import type { PostVisibility } from "./types"
export default function getPostPath(visibility: PostVisibility, id: string) { export default function getPostPath(visibility: PostVisibility, id: string) {
switch (visibility) { switch (visibility) {
case "private": case "private":
return `/post/private/${id}` return `/post/private/${id}`
case "protected": case "protected":
return `/post/protected/${id}` return `/post/protected/${id}`
case "unlisted": case "unlisted":
case "public": case "public":
return `/post/${id}` return `/post/${id}`
} }
} }

View file

@ -2,10 +2,10 @@ import useSWR from "swr"
// https://2020.paco.me/blog/shared-hook-state-with-swr // https://2020.paco.me/blog/shared-hook-state-with-swr
const useSharedState = <T>(key: string, initial?: T) => { const useSharedState = <T>(key: string, initial?: T) => {
const { data: state, mutate: setState } = useSWR(key, { const { data: state, mutate: setState } = useSWR(key, {
fallbackData: initial fallbackData: initial
}) })
return [state, setState] as const return [state, setState] as const
} }
export default useSharedState export default useSharedState

View file

@ -4,32 +4,32 @@ import { useEffect, useState } from "react"
import useSharedState from "./use-shared-state" import useSharedState from "./use-shared-state"
const useSignedIn = () => { const useSignedIn = () => {
const [signedIn, setSignedIn] = useSharedState( const [signedIn, setSignedIn] = useSharedState(
"signedIn", "signedIn",
typeof window === "undefined" ? false : !!Cookies.get("drift-token") typeof window === "undefined" ? false : !!Cookies.get("drift-token")
) )
const token = Cookies.get("drift-token") const token = Cookies.get("drift-token")
const router = useRouter() const router = useRouter()
const signin = (token: string) => { const signin = (token: string) => {
setSignedIn(true) setSignedIn(true)
Cookies.set("drift-token", token) Cookies.set("drift-token", token)
} }
const signout = () => { const signout = () => {
setSignedIn(false) setSignedIn(false)
Cookies.remove("drift-token") Cookies.remove("drift-token")
router.push("/") router.push("/")
} }
useEffect(() => { useEffect(() => {
if (token) { if (token) {
setSignedIn(true) setSignedIn(true)
} else { } else {
setSignedIn(false) setSignedIn(false)
} }
}, [setSignedIn, token]) }, [setSignedIn, token])
return { signedIn, signin, token, signout } return { signedIn, signin, token, signout }
} }
export default useSignedIn export default useSignedIn

View file

@ -2,26 +2,26 @@ import { useCallback, useEffect } from "react"
import useSharedState from "./use-shared-state" import useSharedState from "./use-shared-state"
const useTheme = () => { const useTheme = () => {
const isClient = typeof window === "object" const isClient = typeof window === "object"
const [themeType, setThemeType] = useSharedState<string>("theme", "light") const [themeType, setThemeType] = useSharedState<string>("theme", "light")
useEffect(() => { useEffect(() => {
if (!isClient) return if (!isClient) return
const storedTheme = localStorage.getItem("drift-theme") const storedTheme = localStorage.getItem("drift-theme")
if (storedTheme) { if (storedTheme) {
setThemeType(storedTheme) setThemeType(storedTheme)
} }
}, [isClient, setThemeType]) }, [isClient, setThemeType])
const changeTheme = useCallback(() => { const changeTheme = useCallback(() => {
setThemeType((last) => { setThemeType((last) => {
const newTheme = last === "dark" ? "light" : "dark" const newTheme = last === "dark" ? "light" : "dark"
localStorage.setItem("drift-theme", newTheme) localStorage.setItem("drift-theme", newTheme)
return newTheme return newTheme
}) })
}, [setThemeType]) }, [setThemeType])
return { theme: themeType, changeTheme } return { theme: themeType, changeTheme }
} }
export default useTheme export default useTheme

View file

@ -1,19 +1,19 @@
import { useRef, useEffect } from "react" import { useRef, useEffect } from "react"
function useTraceUpdate(props: { [key: string]: any }) { function useTraceUpdate(props: { [key: string]: any }) {
const prev = useRef(props) const prev = useRef(props)
useEffect(() => { useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => { const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) { if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v] ps[k] = [prev.current[k], v]
} }
return ps return ps
}, {} as { [key: string]: any }) }, {} as { [key: string]: any })
if (Object.keys(changedProps).length > 0) { if (Object.keys(changedProps).length > 0) {
console.log("Changed props:", changedProps) console.log("Changed props:", changedProps)
} }
prev.current = props prev.current = props
}) })
} }
export default useTraceUpdate export default useTraceUpdate

View file

@ -2,42 +2,42 @@
// which is based on https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site // which is based on https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
const epochs = [ const epochs = [
["year", 31536000], ["year", 31536000],
["month", 2592000], ["month", 2592000],
["day", 86400], ["day", 86400],
["hour", 3600], ["hour", 3600],
["minute", 60], ["minute", 60],
["second", 1] ["second", 1]
] as const ] as const
// Get duration // Get duration
const getDuration = (timeAgoInSeconds: number) => { const getDuration = (timeAgoInSeconds: number) => {
for (let [name, seconds] of epochs) { for (let [name, seconds] of epochs) {
const interval = Math.floor(timeAgoInSeconds / seconds) const interval = Math.floor(timeAgoInSeconds / seconds)
if (interval >= 1) { if (interval >= 1) {
return { return {
interval: interval, interval: interval,
epoch: name epoch: name
} }
} }
} }
return { return {
interval: 0, interval: 0,
epoch: "second" epoch: "second"
} }
} }
// Calculate // Calculate
const timeAgo = (date: Date) => { const timeAgo = (date: Date) => {
const timeAgoInSeconds = Math.floor( const timeAgoInSeconds = Math.floor(
(new Date().getTime() - new Date(date).getTime()) / 1000 (new Date().getTime() - new Date(date).getTime()) / 1000
) )
const { interval, epoch } = getDuration(timeAgoInSeconds) const { interval, epoch } = getDuration(timeAgoInSeconds)
const suffix = interval === 1 ? "" : "s" const suffix = interval === 1 ? "" : "s"
return `${interval} ${epoch}${suffix} ago` return `${interval} ${epoch}${suffix} ago`
} }
export default timeAgo export default timeAgo

28
client/lib/types.d.ts vendored
View file

@ -1,29 +1,29 @@
export type PostVisibility = "unlisted" | "private" | "public" | "protected" export type PostVisibility = "unlisted" | "private" | "public" | "protected"
export type ThemeProps = { export type ThemeProps = {
theme: "light" | "dark" | string theme: "light" | "dark" | string
changeTheme: () => void changeTheme: () => void
} }
export type Document = { export type Document = {
title: string title: string
content: string content: string
id: string id: string
} }
export type File = { export type File = {
id: string id: string
title: string title: string
content: string content: string
html: string html: string
} }
type Files = File[] type Files = File[]
export type Post = { export type Post = {
id: string id: string
title: string title: string
description: string description: string
visibility: PostVisibility visibility: PostVisibility
files: Files files: Files
} }

View file

@ -3,52 +3,52 @@ import type { NextApiHandler } from "next"
import markdown from "@lib/render-markdown" import markdown from "@lib/render-markdown"
const renderMarkdown: NextApiHandler = async (req, res) => { const renderMarkdown: NextApiHandler = async (req, res) => {
const { id } = req.query const { id } = req.query
const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, {
headers: { headers: {
Accept: "text/plain", Accept: "text/plain",
"x-secret-key": process.env.SECRET_KEY || "", "x-secret-key": process.env.SECRET_KEY || "",
Authorization: `Bearer ${req.cookies["drift-token"]}` Authorization: `Bearer ${req.cookies["drift-token"]}`
} }
}) })
const json = await file.json() const json = await file.json()
const { content, title } = json const { content, title } = json
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 = title.split(".") const pathParts = 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 = "\n" + content let contentToRender: string = "\n" + content
if (!renderAsMarkdown.includes(type)) { if (!renderAsMarkdown.includes(type)) {
contentToRender = `~~~${type} contentToRender = `~~~${type}
${content} ${content}
~~~` ~~~`
} }
if (typeof contentToRender !== "string") { if (typeof contentToRender !== "string") {
res.status(400).send("content must be a string") res.status(400).send("content must be a string")
return return
} }
res.setHeader("Content-Type", "text/plain") res.setHeader("Content-Type", "text/plain")
res.setHeader("Cache-Control", "public, max-age=4800") res.setHeader("Cache-Control", "public, max-age=4800")
res.status(200).write(markdown(contentToRender)) res.status(200).write(markdown(contentToRender))
res.end() res.end()
} }
export default renderMarkdown export default renderMarkdown

View file

@ -1,33 +1,33 @@
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => {
const { id, download } = req.query const { id, download } = req.query
const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, {
headers: { headers: {
Accept: "text/plain", Accept: "text/plain",
"x-secret-key": process.env.SECRET_KEY || "", "x-secret-key": process.env.SECRET_KEY || "",
Authorization: `Bearer ${req.cookies["drift-token"]}` Authorization: `Bearer ${req.cookies["drift-token"]}`
} }
}) })
const json = await file.json() const json = await file.json()
res.setHeader("Content-Type", "text/plain; charset=utf-8") res.setHeader("Content-Type", "text/plain; charset=utf-8")
res.setHeader("Cache-Control", "s-maxage=86400") res.setHeader("Cache-Control", "s-maxage=86400")
if (file.ok) { if (file.ok) {
const data = json const data = json
const { title, content } = data const { title, content } = data
// serve the file raw as plain text // serve the file raw as plain text
if (download) { if (download) {
res.setHeader("Content-Disposition", `attachment; filename="${title}"`) res.setHeader("Content-Disposition", `attachment; filename="${title}"`)
} else { } else {
res.setHeader("Content-Disposition", `inline; filename="${title}"`) res.setHeader("Content-Disposition", `inline; filename="${title}"`)
} }
res.status(200).write(content, "utf-8") res.status(200).write(content, "utf-8")
res.end() res.end()
} else { } else {
res.status(404).send("File not found") res.status(404).send("File not found")
} }
} }
export default getRawFile export default getRawFile

View file

@ -3,41 +3,41 @@ import type { NextApiHandler } from "next"
import markdown from "@lib/render-markdown" import markdown from "@lib/render-markdown"
const renderMarkdown: NextApiHandler = async (req, res) => { const renderMarkdown: NextApiHandler = async (req, res) => {
const { content, title } = req.body const { content, title } = req.body
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 = title.split(".") const pathParts = 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 = content || "" let contentToRender: string = content || ""
if (!renderAsMarkdown.includes(type)) { if (!renderAsMarkdown.includes(type)) {
contentToRender = `~~~${type} contentToRender = `~~~${type}
${content} ${content}
~~~` ~~~`
} else { } else {
contentToRender = "\n" + content contentToRender = "\n" + content
} }
if (typeof contentToRender !== "string") { if (typeof contentToRender !== "string") {
res.status(400).send("content must be a string") res.status(400).send("content must be a string")
return return
} }
res.status(200).write(markdown(contentToRender)) res.status(200).write(markdown(contentToRender))
res.end() res.end()
} }
export default renderMarkdown export default renderMarkdown