diff --git a/enshi/package-lock.json b/enshi/package-lock.json index 2109523..6cdc4e5 100644 --- a/enshi/package-lock.json +++ b/enshi/package-lock.json @@ -1,12 +1,12 @@ { "name": "enshi", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "enshi", - "version": "0.1.7", + "version": "0.1.8", "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-form": "^0.1.0", @@ -18,6 +18,7 @@ "@radix-ui/themes": "^3.1.3", "@tanstack/react-query": "^5.55.0", "@tanstack/react-query-devtools": "^5.61.0", + "@types/quill": "^2.0.14", "axios": "^1.7.7", "html-react-parser": "^5.1.16", "i18n": "^0.15.1", @@ -28,7 +29,7 @@ "jotai": "^2.9.3", "jotai-immer": "^0.4.1", "primereact": "^10.8.2", - "quill": "^2.0.2", + "quill": "^2.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^15.0.1", @@ -3422,12 +3423,13 @@ "license": "MIT" }, "node_modules/@types/quill": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", - "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-2.0.14.tgz", + "integrity": "sha512-zvoXCRnc2Dl8g+7/9VSAmRWPN6oH+MVhTPizmCR+GJCITplZ5VRVzMs4+a/nOE3yzNwEZqylJJrMB07bwbM1/g==", "license": "MIT", "dependencies": { - "parchment": "^1.1.2" + "parchment": "^1.1.2", + "quill-delta": "^5.1.0" } }, "node_modules/@types/quill/node_modules/parchment": { @@ -6409,9 +6411,9 @@ "license": "MIT" }, "node_modules/quill": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz", - "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz", + "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==", "license": "BSD-3-Clause", "dependencies": { "eventemitter3": "^5.0.1", @@ -6511,6 +6513,15 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/react-quill/node_modules/@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "license": "MIT", + "dependencies": { + "parchment": "^1.1.2" + } + }, "node_modules/react-quill/node_modules/eventemitter3": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", diff --git a/enshi/package.json b/enshi/package.json index af4b387..b5cb8c4 100644 --- a/enshi/package.json +++ b/enshi/package.json @@ -21,6 +21,7 @@ "@radix-ui/themes": "^3.1.3", "@tanstack/react-query": "^5.55.0", "@tanstack/react-query-devtools": "^5.61.0", + "@types/quill": "^2.0.14", "axios": "^1.7.7", "html-react-parser": "^5.1.16", "i18n": "^0.15.1", @@ -31,7 +32,7 @@ "jotai": "^2.9.3", "jotai-immer": "^0.4.1", "primereact": "^10.8.2", - "quill": "^2.0.2", + "quill": "^2.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^15.0.1", diff --git a/enshi/src/@types/PostTypes.ts b/enshi/src/@types/PostTypes.ts index 9068e49..0873cc1 100644 --- a/enshi/src/@types/PostTypes.ts +++ b/enshi/src/@types/PostTypes.ts @@ -6,3 +6,8 @@ export type GetRandomPostsRow = { // created_at: Date; } + +export type TPostData = { + title: string; + content: string; +}; \ No newline at end of file diff --git a/enshi/src/App.css b/enshi/src/App.css index 73de149..755b540 100644 --- a/enshi/src/App.css +++ b/enshi/src/App.css @@ -426,6 +426,16 @@ content: ''; display: table; } + +.ql-toolbar { + position: relative; + width: 100%; +} + +.ql-container { + overflow-y: auto; +} + .ql-snow.ql-toolbar button, .ql-snow .ql-toolbar button { background: none; diff --git a/enshi/src/App.tsx b/enshi/src/App.tsx index 82da0a2..8bec25a 100644 --- a/enshi/src/App.tsx +++ b/enshi/src/App.tsx @@ -1,23 +1,28 @@ -import { Theme, ThemePanel } from "@radix-ui/themes"; +import { Theme } from "@radix-ui/themes"; import "@radix-ui/themes/styles.css"; import { QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import "axios"; +import { useAtomValue } from "jotai"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import queryClient from "./api/QueryClient/QueryClient"; import "./App.css"; +import { themeAtom } from "./AtomStore/AtomStore"; import ToastProvider from "./Components/ToastProvider/ToastProvider"; import { routes } from "./routes/routes"; const router = createBrowserRouter(routes); export default function App() { + + const theme = useAtomValue(themeAtom); + return ( - + - + {/* */} diff --git a/enshi/src/AtomStore/AtomStore.ts b/enshi/src/AtomStore/AtomStore.ts index ed53496..a6aec68 100644 --- a/enshi/src/AtomStore/AtomStore.ts +++ b/enshi/src/AtomStore/AtomStore.ts @@ -1,5 +1,6 @@ import { atom } from "jotai"; import { atomWithStorage } from "jotai/utils"; +import { TPostData } from "../@types/PostTypes"; import { TUser } from "../@types/UserType"; export const userAtom = atom(); @@ -7,10 +8,18 @@ export const userAtom = atom(); export const postCreationAtom = atom(); export const postCreationTitleAtom = atom(); -type TPostData = { - title: string; - content: string; -}; +export const themeAtom = atomWithStorage<"light" | "dark">( + "theme", + "light", + { + getItem: (key) => localStorage.getItem(key) as any, + setItem: (key, value) => localStorage.setItem(key, value as any), + removeItem: (key) => localStorage.removeItem(key), + }, + { + getOnInit: true, + } +); export const storagePostAtom = atomWithStorage( "draft-post", diff --git a/enshi/src/Components/Editor/Editor.tsx b/enshi/src/Components/Editor/Editor.tsx index dd5354d..212517e 100644 --- a/enshi/src/Components/Editor/Editor.tsx +++ b/enshi/src/Components/Editor/Editor.tsx @@ -1,7 +1,5 @@ -import Sources from "quill"; -import Quill, { Delta } from "quill/core"; -import { forwardRef, useEffect, useRef, useState } from "react"; -import ReactQuill from "react-quill"; +import { Delta } from "quill/core"; +import { forwardRef } from "react"; type TEditor = { readOnly?: boolean; @@ -30,61 +28,55 @@ const modules = { * @param onChange - function that accepts Delta element */ const Editor = forwardRef((props: TEditor) => { - const editor = useRef(null); - const [quill, setQuill] = useState(null); - const [value, setValue] = useState(new Delta()); + // const editor = useRef(null); + // const [quill, setQuill] = useState(null); + // const [value, setValue] = useState(new Delta()); - const [loaded, setLoaded] = useState(false); + // const [loaded, setLoaded] = useState(false); - useEffect(() => { - if (editor.current) { - //@ts-ignore - const temp = editor.current.getEditor() as Quill; - setQuill(temp); - } - return () => { - setQuill(null); - }; - }, [editor.current]); + // useEffect(() => { + // if (editor.current) { + // //@ts-ignore + // const temp = editor.current.getEditor() as Quill; + // setQuill(temp); + // } + // return () => { + // setQuill(null); + // }; + // }, [editor.current]); - useEffect(() => { - const quill = new Quill(document.createElement("div")); - const t = quill.clipboard.convert({ - html: props.defaultValue as string, - }) as Delta; + // useEffect(() => { + // const quill = new Quill(document.createElement("div")); + // console.log(`AMOOOGUS`, props.defaultValue); - if (!loaded) { - setValue(t); + // const t = quill.clipboard.convert({ + // html: props.defaultValue as string, + // }) as Delta; - console.log(t); - } + // if (!loaded) { + // setValue(t); + // console.log(t); + // } - setLoaded(true); - }, [props.defaultValue]); + // setLoaded(true); + // }, [props.defaultValue]); - const changeHandler = ( - val: string, - _changeDelta: Delta, - _source: Sources, - _editor: ReactQuill.UnprivilegedEditor - ) => { - console.log(val); - console.log(JSON.stringify(quill?.getContents().ops, null, 2)); - let fullDelta = quill?.getContents(); - if (props.onChange) props.onChange(val || ""); - if (loaded) setValue(fullDelta || new Delta()); - }; + // const changeHandler = ( + // val: string, + // _changeDelta: Delta, + // _source: Sources, + // _editor: ReactQuill.UnprivilegedEditor + // ) => { + // console.log(val); + // console.log(JSON.stringify(quill?.getContents().ops, null, 2)); + // let fullDelta = quill?.getContents(); + // if (props.onChange) props.onChange(val || ""); + // if (loaded) setValue(fullDelta || new Delta()); + // }; return ( -
- +
+ DEPRECATED
); }); diff --git a/enshi/src/Components/Editor/TrueEditor.tsx b/enshi/src/Components/Editor/TrueEditor.tsx new file mode 100644 index 0000000..40ae69c --- /dev/null +++ b/enshi/src/Components/Editor/TrueEditor.tsx @@ -0,0 +1,85 @@ +import { Delta, default as Quill, default as Sources } from "quill"; +import "quill/dist/quill.snow.css"; // make sure to import Quill's CSS +import { useEffect, useRef } from "react"; + +type TEditor = { + readOnly?: boolean; + defaultValue?: string | Delta; + onChange?: (html: string) => void; + onSelectionChange?: (range: any) => void; +}; + +const modules = { + toolbar: [ + [{ header: [1, 2, 3, 4, 5, false] }], + ["bold", "italic", "underline", "strike", "blockquote", "span-wrapper"], + [ + { list: "ordered" }, + { list: "bullet" }, + { indent: "-1" }, + { indent: "+1" }, + ], + ["link", "image"], + ["clean"], + [{ align: [] }], + ], +}; + +const TrueEditor = (props: TEditor) => { + const containerRef = useRef(null); + const quillRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + quillRef.current = new Quill(containerRef.current, { + modules, + theme: "snow", + readOnly: props.readOnly || false, + placeholder: "Type your thoughts here...", + }); + + if (props.defaultValue) { + if (typeof props.defaultValue === "string") { + const delta = quillRef.current.clipboard.convert({ html: props.defaultValue }); + quillRef.current.setContents(delta, "silent"); + } else { + quillRef.current.setContents(props.defaultValue, "silent"); + } + } + + quillRef.current.on( + "text-change", + (_delta: Delta, _oldDelta: Delta, _source: Sources) => { + if (props.onChange) { + const html = + containerRef.current?.querySelector(".ql-editor")?.innerHTML || + ""; + props.onChange(html); + } + } + ); + + if (props.onSelectionChange) { + quillRef.current.on("selection-change", (range, _oldRange, _source) => { + if(props.onSelectionChange) props.onSelectionChange(range); + }); + } + } + + return () => { + if (quillRef.current) { + quillRef.current.off("text-change"); + quillRef.current.off("selection-change"); + quillRef.current = null; + } + }; + }, []); + + return ( +
+
+
+ ); +}; + +export default TrueEditor; \ No newline at end of file diff --git a/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx b/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx index eaaaaa6..95ed79b 100644 --- a/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx +++ b/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx @@ -1,4 +1,4 @@ -import { PlusIcon } from "@radix-ui/react-icons"; +import { Pencil1Icon } from "@radix-ui/react-icons"; import { Button, Text } from "@radix-ui/themes"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -8,8 +8,8 @@ export default function CreatePostButton() { return ( - diff --git a/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx b/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx index 3c1ff38..3638f0e 100644 --- a/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx +++ b/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx @@ -1,11 +1,13 @@ import CreatePostButton from "./CreatePostButton/CreatePostButton"; +import ThemeChangeButton from "./ThemeChangeButton/ThemeChangeButton"; import UserButton from "./UserButton/UserButton"; export default function RightButtonBar() { return ( -
+
+
) diff --git a/enshi/src/Components/NavBar/RightButtonBar/ThemeChangeButton/ThemeChangeButton.tsx b/enshi/src/Components/NavBar/RightButtonBar/ThemeChangeButton/ThemeChangeButton.tsx new file mode 100644 index 0000000..5280277 --- /dev/null +++ b/enshi/src/Components/NavBar/RightButtonBar/ThemeChangeButton/ThemeChangeButton.tsx @@ -0,0 +1,23 @@ +import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; +import { IconButton } from "@radix-ui/themes"; +import { useAtom } from "jotai"; +import { themeAtom } from "../../../../AtomStore/AtomStore"; + +export default function ThemeChangeButton() { + const [theme, setTheme] = useAtom(themeAtom); + + + const toggleTheme = () => { + if(theme === 'light') { + setTheme('dark') + } else { + setTheme('light') + } + } + + return ( + + {theme === 'light' ? : } + + ) +} diff --git a/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx b/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx index 4e893d7..45edf5b 100644 --- a/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx +++ b/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx @@ -1,5 +1,5 @@ import { LaptopIcon, PersonIcon } from "@radix-ui/react-icons"; -import { DropdownMenu, Flex, IconButton, Text } from "@radix-ui/themes"; +import { DropdownMenu, Flex, IconButton, Text, Tooltip } from "@radix-ui/themes"; import { Icon } from "@radix-ui/themes/dist/esm/components/callout.js"; import { useAtomValue } from "jotai"; import { useTranslation } from "react-i18next"; @@ -18,9 +18,15 @@ export default function UserButton() {
- - - + + + + + diff --git a/enshi/src/Components/Tooltip/Tooltip.tsx b/enshi/src/Components/Tooltip/Tooltip.tsx deleted file mode 100644 index 204282e..0000000 --- a/enshi/src/Components/Tooltip/Tooltip.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as RadixTooltip from "@radix-ui/react-tooltip"; -import { Card, Text, Theme } from "@radix-ui/themes"; - -type TTooltipProps = { - text: string; -} & React.PropsWithChildren; - -export default function Tooltip(props: TTooltipProps) { - return ( - - - {props.children} - - - - - - - {props.text} - - - - - - - - ); -} diff --git a/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx index 5fa2b2e..647166f 100644 --- a/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx +++ b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx @@ -1,10 +1,10 @@ -import { Box, Container, Flex, ScrollArea } from "@radix-ui/themes"; +import { Box, Container, Flex } from "@radix-ui/themes"; import { useAtom, useSetAtom } from "jotai"; import { postCreationAtom, postCreationTitleAtom, } from "../../AtomStore/AtomStore"; -import Editor from "../../Components/Editor/Editor"; +import TrueEditor from "../../Components/Editor/TrueEditor"; import SubmitPostButton from "./SubmitPostButton/SubmitPostButton"; export default function PostCreatorPage() { @@ -14,7 +14,11 @@ export default function PostCreatorPage() { return ( <> - + - - - - - + diff --git a/enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx b/enshi/src/Pages/PostRedactor/PostRedactor.tsx similarity index 52% rename from enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx rename to enshi/src/Pages/PostRedactor/PostRedactor.tsx index 50d1e9b..a4bf921 100644 --- a/enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx +++ b/enshi/src/Pages/PostRedactor/PostRedactor.tsx @@ -2,8 +2,8 @@ import { Box, Container, Flex, Spinner } from "@radix-ui/themes"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { axiosLocalhost } from "../../../api/axios/axios"; -import Editor from "../../../Components/Editor/Editor"; +import { axiosLocalhost } from "../../api/axios/axios"; +import TrueEditor from "../../Components/Editor/TrueEditor"; import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton"; export default function PostRedactor() { @@ -12,7 +12,7 @@ export default function PostRedactor() { const queryParams = useParams(); - const { isPending } = useQuery({ + const { isLoading } = useQuery({ queryKey: ["changePostKey", queryParams.postId], queryFn: async () => { try { @@ -31,16 +31,20 @@ export default function PostRedactor() { } }, gcTime: 0, - refetchOnMount: true + refetchOnMount: true, }); return ( <> - - - + + + - {isPending ? ( - - ) : ( - - )} - + {/* + {isPending ? ( + + ) : ( + + )} + */} + + {isLoading ? ( + + ) : ( + + )} - - + + diff --git a/enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx b/enshi/src/Pages/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx similarity index 93% rename from enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx rename to enshi/src/Pages/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx index 070448d..2f72c41 100644 --- a/enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx +++ b/enshi/src/Pages/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx @@ -3,8 +3,8 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; -import { axiosLocalhost } from "../../../../api/axios/axios"; -import useToast from "../../../../hooks/useToast"; +import { axiosLocalhost } from "../../../api/axios/axios"; +import useToast from "../../../hooks/useToast"; type TSubmitChangesButton = { className: string; diff --git a/enshi/src/routes/routes.tsx b/enshi/src/routes/routes.tsx index dfe716e..0847ce3 100644 --- a/enshi/src/routes/routes.tsx +++ b/enshi/src/routes/routes.tsx @@ -11,9 +11,9 @@ import ProfilePage from "../layout/ProfilePage/ProfilePage"; import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper"; import BlogPage from "../Pages/BlogPage/BlogPage"; import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage"; -import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor"; import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage"; import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage"; +import PostRedactor from "../Pages/PostRedactor/PostRedactor"; import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage"; import UserBlogsPage from "../Pages/UserBlogsPage/UserBlogsPage"; import UserPostsPage from "../Pages/UserPostsPage/UserPostsPage";