client: mine page fixes, remove lodash.debounce
This commit is contained in:
parent
1369bdf996
commit
fe589d63d8
11 changed files with 100 additions and 103 deletions
|
@ -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}>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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>}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"],
|
||||||
|
|
Loading…
Reference in a new issue