client: mine page fixes, remove lodash.debounce

This commit is contained in:
Max Leiter 2022-04-11 23:07:06 -07:00
parent 1369bdf996
commit fe589d63d8
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
11 changed files with 100 additions and 103 deletions

View file

@ -10,49 +10,43 @@ import { TextareaMarkdownRef } from "textarea-markdown-editor"
// TODO: clean up // TODO: clean up
const FormattingIcons = ({ const FormattingIcons = ({
textareaRef, textareaRef
}: { }: {
textareaRef?: RefObject<TextareaMarkdownRef> textareaRef?: RefObject<TextareaMarkdownRef>
}) => { }) => {
const formattingActions = useMemo(() => {
const handleBoldClick = () => textareaRef?.current?.trigger("bold")
const handleItalicClick = () => textareaRef?.current?.trigger("italic")
const formattingActions = useMemo( const handleLinkClick = () => textareaRef?.current?.trigger("link")
() => { const handleImageClick = () => textareaRef?.current?.trigger("image")
const handleBoldClick = () => textareaRef?.current?.trigger("bold") return [
const handleItalicClick = () => textareaRef?.current?.trigger("italic") {
const handleLinkClick = () => textareaRef?.current?.trigger("link") icon: <Bold />,
const handleImageClick = () => textareaRef?.current?.trigger("image") name: "bold",
return [ action: handleBoldClick
{ },
icon: <Bold />, {
name: "bold", icon: <Italic />,
action: handleBoldClick name: "italic",
}, action: handleItalicClick
{ },
icon: <Italic />, // {
name: "italic", // icon: <Underline />,
action: handleItalicClick // name: 'underline',
}, // action: handleUnderlineClick
// { // },
// icon: <Underline />, {
// name: 'underline', icon: <Link />,
// action: handleUnderlineClick name: "hyperlink",
// }, action: handleLinkClick
{ },
icon: <Link />, {
name: "hyperlink", icon: <ImageIcon />,
action: handleLinkClick name: "image",
}, action: handleImageClick
{ }
icon: <ImageIcon />, ]
name: "image", }, [textareaRef])
action: handleImageClick
}
]
},
[textareaRef]
)
return ( return (
<div className={styles.actionWrapper}> <div className={styles.actionWrapper}>

View file

@ -9,15 +9,9 @@ import {
import styles from "./document.module.css" import styles from "./document.module.css"
import Trash from "@geist-ui/icons/trash" import Trash from "@geist-ui/icons/trash"
import FormattingIcons from "./formatting-icons" import FormattingIcons from "./formatting-icons"
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"; import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
import { import { Button, Input, Spacer, Tabs, Textarea } from "@geist-ui/core"
Button,
Input,
Spacer,
Tabs,
Textarea,
} from "@geist-ui/core"
import Preview from "@components/preview" import Preview from "@components/preview"
// import Link from "next/link" // import Link from "next/link"
@ -121,9 +115,7 @@ const Document = ({
)} )}
</div> </div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
{tab === "edit" && ( {tab === "edit" && <FormattingIcons textareaRef={codeEditorRef} />}
<FormattingIcons textareaRef={codeEditorRef} />
)}
<Tabs <Tabs
onChange={handleTabChange} onChange={handleTabChange}
initialValue={initialTab} initialValue={initialTab}

View file

@ -46,7 +46,7 @@ const FileDropdown = ({
setItems(newItems) setItems(newItems)
}, [files]) }, [files])
const content = const content = (
<ul className={styles.content}> <ul className={styles.content}>
{items.map((item) => ( {items.map((item) => (
<li key={item.id} onClick={onClose}> <li key={item.id} onClick={onClose}>
@ -61,6 +61,7 @@ const FileDropdown = ({
</li> </li>
))} ))}
</ul> </ul>
)
// a list of files with an icon and a title // a list of files with an icon and a title
return ( return (

View file

@ -65,7 +65,6 @@ const Post = ({
} }
}, [emptyDoc, initialPost]) }, [emptyDoc, initialPost])
const [passwordModalVisible, setPasswordModalVisible] = useState(false) const [passwordModalVisible, setPasswordModalVisible] = useState(false)
const sendRequest = useCallback( const sendRequest = useCallback(

View file

@ -1,4 +1,4 @@
import { Button, Code, Dot, Input, Note, Text } from "@geist-ui/core" import { Button, Input, Select, Text } from "@geist-ui/core"
import NextLink from "next/link" import NextLink from "next/link"
import Link from "../Link" import Link from "../Link"
@ -7,8 +7,8 @@ import ListItemSkeleton from "./list-item-skeleton"
import ListItem from "./list-item" import ListItem from "./list-item"
import { Post } from "@lib/types" import { Post } from "@lib/types"
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react" import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"
import debounce from "lodash.debounce"
import Cookies from "js-cookie" import Cookies from "js-cookie"
import useDebounce from "@lib/hooks/use-debounce"
type Props = { type Props = {
initialPosts: Post[] initialPosts: Post[]
@ -21,6 +21,9 @@ const PostList = ({ morePosts, initialPosts, error }: Props) => {
const [posts, setPosts] = useState<Post[]>(initialPosts) const [posts, setPosts] = useState<Post[]>(initialPosts)
const [searching, setSearching] = useState(false) const [searching, setSearching] = useState(false)
const [hasMorePosts, setHasMorePosts] = useState(morePosts) const [hasMorePosts, setHasMorePosts] = useState(morePosts)
const debouncedSearchValue = useDebounce(search, 200)
const loadMoreClick = useCallback( const loadMoreClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => { (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault() e.preventDefault()
@ -46,13 +49,15 @@ const PostList = ({ morePosts, initialPosts, error }: Props) => {
// update posts on search // update posts on search
useEffect(() => { useEffect(() => {
if (search) { if (debouncedSearchValue) {
// fetch results from /server-api/posts/search // fetch results from /server-api/posts/search
const fetchResults = async () => { const fetchResults = async () => {
setSearching(true) setSearching(true)
//encode search //encode search
const res = await fetch( const res = await fetch(
`/server-api/posts/search?q=${encodeURIComponent(search)}`, `/server-api/posts/search?q=${encodeURIComponent(
debouncedSearchValue
)}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
@ -70,22 +75,22 @@ const PostList = ({ morePosts, initialPosts, error }: Props) => {
} else { } else {
setPosts(initialPosts) setPosts(initialPosts)
} }
}, [initialPosts, search]) }, [initialPosts, debouncedSearchValue])
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value) setSearchValue(e.target.value)
} }
const debouncedSearchHandler = useMemo( // const debouncedSearchHandler = useMemo(
() => debounce(handleSearchChange, 300), // () => debounce(handleSearchChange, 300),
[] // []
) // )
useEffect(() => { // useEffect(() => {
return () => { // return () => {
debouncedSearchHandler.cancel() // debouncedSearchHandler.cancel()
} // }
}, [debouncedSearchHandler]) // }, [debouncedSearchHandler])
const deletePost = useCallback( const deletePost = useCallback(
(postId: string) => async () => { (postId: string) => async () => {
@ -114,7 +119,7 @@ const PostList = ({ morePosts, initialPosts, error }: Props) => {
scale={3 / 2} scale={3 / 2}
clearable clearable
placeholder="Search..." placeholder="Search..."
onChange={debouncedSearchHandler} onChange={handleSearchChange}
/> />
</div> </div>
{error && <Text type="error">Failed to load.</Text>} {error && <Text type="error">Failed to load.</Text>}

View file

@ -28,7 +28,8 @@
.searchContainer { .searchContainer {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column-reverse; flex-direction: column;
justify-content: center; justify-content: center;
margin-bottom: var(--gap-double); gap: var(--gap-half);
margin-bottom: var(--gap);
} }

View file

@ -1,29 +1,35 @@
const replaceLastInString = ( const replaceLastInString = (
string: string, string: string,
search: string, search: string,
replace: string replace: string
): string => { ): string => {
const index = string.lastIndexOf(search); const index = string.lastIndexOf(search)
if (index === -1) { if (index === -1) {
return string; return string
} }
return string.substring(0, index) + replace + string.substring(index + search.length); return (
string.substring(0, index) +
replace +
string.substring(index + search.length)
)
} }
const getTitleForPostCopy = ( const getTitleForPostCopy = (title: string) => {
title: string, const numberAtEndOfTitle = title.split(" ").pop()
) => { if (numberAtEndOfTitle) {
const numberAtEndOfTitle = title.split(" ").pop() const number = parseInt(numberAtEndOfTitle)
if (numberAtEndOfTitle) { if (number) {
const number = parseInt(numberAtEndOfTitle) return replaceLastInString(
if (number) { title,
return replaceLastInString(title, numberAtEndOfTitle, (number + 1).toString()) numberAtEndOfTitle,
} else { (number + 1).toString()
return title + " 1" )
} } else {
} else { return title + " 1"
return title + " 1" }
} } else {
return title + " 1"
}
} }
export default getTitleForPostCopy export default getTitleForPostCopy

View file

@ -1,7 +1,6 @@
// useDebounce.js
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
export default function useDebounce(value: any, delay: number) { export default function useDebounce<T>(value: T, delay: number) {
const [debouncedValue, setDebouncedValue] = useState(value) const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => { useEffect(() => {

View file

@ -19,7 +19,6 @@
"cookie": "^0.4.2", "cookie": "^0.4.2",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash.debounce": "^4.0.8",
"marked": "^4.0.12", "marked": "^4.0.12",
"next": "^12.1.1-canary.15", "next": "^12.1.1-canary.15",
"next-themes": "^0.1.1", "next-themes": "^0.1.1",

View file

@ -2310,11 +2310,6 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.merge@^4.6.2: lodash.merge@^4.6.2:
version "4.6.2" version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"

View file

@ -42,7 +42,7 @@ posts.post(
parentId: Joi.string().optional().allow(null, "") parentId: Joi.string().optional().allow(null, "")
} }
}), }),
async (req, res, next) => { async (req, res) => {
try { try {
// check if all files have titles // check if all files have titles
const files = req.body.files as File[] const files = req.body.files as File[]
@ -218,7 +218,13 @@ posts.get(
}, },
{ {
model: User, model: User,
as: "users" as: "users",
attributes: ["id", "username"]
},
{
model: Post,
as: "parent",
attributes: ["id", "title", "visibility"]
} }
], ],
attributes: ["id", "title", "visibility", "createdAt", "deletedAt"], attributes: ["id", "title", "visibility", "createdAt", "deletedAt"],