diff --git a/web/package.json b/web/package.json index 305da2ba..354e6266 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "@mui/joy": "^5.0.0-alpha.75", "@reduxjs/toolkit": "^1.8.1", "axios": "^0.27.2", + "classnames": "^2.3.2", "copy-to-clipboard": "^3.3.2", "highlight.js": "^11.6.0", "i18next": "^21.9.2", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 569c2d5c..90fb2a54 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -23,6 +23,9 @@ dependencies: axios: specifier: ^0.27.2 version: 0.27.2 + classnames: + specifier: ^2.3.2 + version: 2.3.2 copy-to-clipboard: specifier: ^3.3.2 version: 3.3.2 @@ -1346,6 +1349,10 @@ packages: fsevents: 2.3.2 dev: false + /classnames@2.3.2: + resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} + dev: false + /clsx@1.2.1: resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} engines: {node: '>=6'} diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx index 5330053d..1553f3f0 100644 --- a/web/src/components/Header.tsx +++ b/web/src/components/Header.tsx @@ -1,7 +1,8 @@ +import classNames from "classnames"; import { useEffect } from "react"; import { NavLink, useLocation } from "react-router-dom"; -import { useTranslate } from "@/utils/i18n"; import { useLayoutStore, useUserStore } from "@/store/module"; +import { useTranslate } from "@/utils/i18n"; import { resolution } from "@/utils/layout"; import Icon from "./Icon"; import UserBanner from "./UserBanner"; @@ -53,9 +54,10 @@ const Header = () => { to="/" id="header-home" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -66,9 +68,10 @@ const Header = () => { to="/review" id="header-review" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -79,9 +82,10 @@ const Header = () => { to="/resources" id="header-resources" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -94,9 +98,10 @@ const Header = () => { to="/explore" id="header-explore" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -110,9 +115,10 @@ const Header = () => { to="/memo-chat" id="header-memo-chat" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 shadow" - } px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -123,9 +129,10 @@ const Header = () => { to="/archived" id="header-archived" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -136,9 +143,10 @@ const Header = () => { to="/setting" id="header-setting" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> @@ -154,9 +162,10 @@ const Header = () => { to="/auth" id="header-auth" className={({ isActive }) => - `${ - isActive && "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" - } px-4 pr-5 py-2 rounded-full border border-transparent flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700` + classNames( + "px-4 pr-5 py-2 rounded-full border flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-700", + isActive ? "bg-white dark:bg-zinc-700 border-gray-200 dark:border-zinc-600" : "border-transparent" + ) } > <> diff --git a/web/src/components/MemosChat/MemosChatInput.tsx b/web/src/components/MemoChat/ChatInput.tsx similarity index 80% rename from web/src/components/MemosChat/MemosChatInput.tsx rename to web/src/components/MemoChat/ChatInput.tsx index 4c56f0c8..b33c518a 100644 --- a/web/src/components/MemosChat/MemosChatInput.tsx +++ b/web/src/components/MemoChat/ChatInput.tsx @@ -2,20 +2,15 @@ import Icon from "@/components/Icon"; import Textarea from "@mui/joy/Textarea/Textarea"; import { useTranslation } from "react-i18next"; -interface MemosChatInputProps { +interface Props { question: string; handleQuestionTextareaChange: any; setIsInIME: any; handleKeyDown: any; handleSendQuestionButtonClick: any; } -const MemosChatInput = ({ - question, - handleQuestionTextareaChange, - setIsInIME, - handleKeyDown, - handleSendQuestionButtonClick, -}: MemosChatInputProps) => { + +const ChatInput = ({ question, handleQuestionTextareaChange, setIsInIME, handleKeyDown, handleSendQuestionButtonClick }: Props) => { const { t } = useTranslation(); return ( @@ -39,4 +34,4 @@ const MemosChatInput = ({ ); }; -export default MemosChatInput; +export default ChatInput; diff --git a/web/src/components/MemosChat/MemosChatMessage.tsx b/web/src/components/MemoChat/ChatMessage.tsx similarity index 91% rename from web/src/components/MemosChat/MemosChatMessage.tsx rename to web/src/components/MemoChat/ChatMessage.tsx index 2f6901d9..1c78fea6 100644 --- a/web/src/components/MemosChat/MemosChatMessage.tsx +++ b/web/src/components/MemoChat/ChatMessage.tsx @@ -7,7 +7,7 @@ interface MessageProps { message: Message; } -const MemosChatMessage = ({ index, message }: MessageProps) => { +const ChatMessage = ({ index, message }: MessageProps) => { return (
{message.role === "user" ? ( @@ -28,4 +28,4 @@ const MemosChatMessage = ({ index, message }: MessageProps) => { ); }; -export default MemosChatMessage; +export default ChatMessage; diff --git a/web/src/components/MemosChat/ConversationTab.tsx b/web/src/components/MemoChat/ConversationTab.tsx similarity index 100% rename from web/src/components/MemosChat/ConversationTab.tsx rename to web/src/components/MemoChat/ConversationTab.tsx diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 74bfbb86..5e540314 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -1,3 +1,4 @@ +import { Button } from "@mui/joy"; import { isNumber, last, uniq } from "lodash-es"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "react-hot-toast"; @@ -421,9 +422,9 @@ const MemoEditor = (props: Props) => {
- +
diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index 38cfae3b..04b4261d 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -76,7 +76,7 @@ const MemoList = () => { return shouldShow; }) : memos - ).filter((memo) => memo.creatorId === currentUserId); + ).filter((memo) => memo.creatorId === currentUserId && memo.rowStatus === "NORMAL"); const pinnedMemos = shownMemos.filter((m) => m.pinned); const unpinnedMemos = shownMemos.filter((m) => !m.pinned); diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 0ce3ef4b..3f7fb2bf 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -326,6 +326,14 @@ "and": "And", "or": "Or" }, + "amount-text": { + "memo_one": "MEMO", + "memo_other": "MEMOS", + "tag_one": "TAG", + "tag_other": "TAGS", + "day_one": "TAG", + "day_other": "TAGE" + }, "message": { "no-data": "Maybe no data was found, maybe it should be another option.", "memos-ready": "all memos are ready 🎉", diff --git a/web/src/pages/Auth.tsx b/web/src/pages/Auth.tsx index c2a32e8e..5adb61a0 100644 --- a/web/src/pages/Auth.tsx +++ b/web/src/pages/Auth.tsx @@ -1,4 +1,4 @@ -import { Button, Divider } from "@mui/joy"; +import { Button, Divider, Input } from "@mui/joy"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslate } from "@/utils/i18n"; @@ -131,71 +131,44 @@ const Auth = () => {
-
-
- -

{systemStatus.customizedProfile.name}

-
-

- {systemStatus.customizedProfile.description || t("common.memos-slogan")} -

+
+ +

{systemStatus.customizedProfile.name}

-
-
-
- - {t("common.username")} - - -
-
- - {t("common.password")} - - -
+ +
+ +
-
+
{actionBtnLoadingState.isLoading && } {systemStatus?.allowSignUp && ( <> - + / )} - +
{identityProviderList.length > 0 && ( diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 337a384f..44e1916c 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -11,19 +11,13 @@ import Memo from "@/components/Memo"; import MobileHeader from "@/components/MobileHeader"; import Empty from "@/components/Empty"; -interface State { - memos: Memo[]; -} - const Explore = () => { const t = useTranslate(); const location = useLocation(); const filterStore = useFilterStore(); const memoStore = useMemoStore(); const filter = filterStore.state; - const [state, setState] = useState({ - memos: [], - }); + const memos = memoStore.state.memos; const [isComplete, setIsComplete] = useState(false); const loadingState = useLoading(); @@ -32,9 +26,6 @@ const Explore = () => { if (memos.length < DEFAULT_MEMO_LIMIT) { setIsComplete(true); } - setState({ - memos, - }); loadingState.setFinish(); }); }, [location]); @@ -43,7 +34,7 @@ const Explore = () => { const showMemoFilter = Boolean(tagQuery || textQuery); const shownMemos = showMemoFilter - ? state.memos.filter((memo) => { + ? memos.filter((memo) => { let shouldShow = true; if (tagQuery) { @@ -64,21 +55,17 @@ const Explore = () => { } return shouldShow; }) - : state.memos; - - const sortedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL"); + : memos; + const sortedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL" && m.visibility !== "PRIVATE"); const handleFetchMoreClick = async () => { try { - const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length); + const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, memos.length); if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) { setIsComplete(true); } else { setIsComplete(false); } - setState({ - memos: state.memos.concat(fetchedMemos), - }); } catch (error: any) { console.error(error); toast.error(error.response.data.message); @@ -95,7 +82,7 @@ const Explore = () => { return ; })} {isComplete ? ( - state.memos.length === 0 && ( + memos.length === 0 && (

{t("message.no-data")}

diff --git a/web/src/pages/MemosChat.tsx b/web/src/pages/MemoChat.tsx similarity index 93% rename from web/src/pages/MemosChat.tsx rename to web/src/pages/MemoChat.tsx index 99401928..715ecb13 100644 --- a/web/src/pages/MemosChat.tsx +++ b/web/src/pages/MemoChat.tsx @@ -1,4 +1,5 @@ import { Button, Stack } from "@mui/joy"; +import { head } from "lodash-es"; import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -9,27 +10,23 @@ import { Conversation, useConversationStore } from "@/store/zustand/conversation import Icon from "@/components/Icon"; import { generateUUID } from "@/utils/uuid"; import MobileHeader from "@/components/MobileHeader"; -import MemosChatMessage from "@/components/MemosChat/MemosChatMessage"; -import MemosChatInput from "@/components/MemosChat/MemosChatInput"; -import head from "lodash-es/head"; -import ConversationTab from "@/components/MemosChat/ConversationTab"; +import ChatMessage from "@/components/MemoChat/ChatMessage"; +import ChatInput from "@/components/MemoChat/ChatInput"; +import ConversationTab from "@/components/MemoChat/ConversationTab"; import Empty from "@/components/Empty"; -const MemosChat = () => { +const MemoChat = () => { const { t } = useTranslation(); const fetchingState = useLoading(false); const [isEnabled, setIsEnabled] = useState(true); const [isInIME, setIsInIME] = useState(false); const [question, setQuestion] = useState(""); - const conversationStore = useConversationStore(); const conversationList = conversationStore.conversationList; - const [selectedConversationId, setSelectedConversationId] = useState(head(conversationList)?.messageStorageId || ""); const messageStore = useMessageStore(selectedConversationId)(); const messageList = messageStore.messageList; - - // the state didn't show in component, just for trigger re-render + // The state didn't show in component, just for trigger re-render const [message, setMessage] = useState(""); useEffect(() => { @@ -170,7 +167,7 @@ const MemosChat = () => {
)} {messageList.map((message, index) => ( - + ))} {fetchingState.isLoading && ( @@ -185,7 +182,7 @@ const MemosChat = () => {
)} - { ); }; -export default MemosChat; +export default MemoChat; diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 58f5b45f..a0c890f8 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -1,40 +1,28 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { toast } from "react-hot-toast"; import { useTranslate } from "@/utils/i18n"; import { Link, useLocation, useParams } from "react-router-dom"; -import { UNKNOWN_ID } from "@/helpers/consts"; import { useGlobalStore, useMemoStore } from "@/store/module"; import useLoading from "@/hooks/useLoading"; import Icon from "@/components/Icon"; import Memo from "@/components/Memo"; -interface State { - memo: Memo; -} - const MemoDetail = () => { const t = useTranslate(); const params = useParams(); const location = useLocation(); const globalStore = useGlobalStore(); const memoStore = useMemoStore(); - const [state, setState] = useState({ - memo: { - id: UNKNOWN_ID, - } as Memo, - }); const loadingState = useLoading(); const customizedProfile = globalStore.state.systemStatus.customizedProfile; + const memoId = Number(params.memoId); + const memo = memoStore.state.memos.find((memo) => memo.id === memoId); useEffect(() => { - const memoId = Number(params.memoId); if (memoId && !isNaN(memoId)) { memoStore .fetchMemoById(memoId) - .then((memo) => { - setState({ - memo, - }); + .then(() => { loadingState.setFinish(); }) .catch((error) => { @@ -53,21 +41,26 @@ const MemoDetail = () => {

{customizedProfile.name}

- {!loadingState.isLoading && ( - <> -
- -
-
- - {t("router.back-to-home")} - -
- - )} + {!loadingState.isLoading && + (memo ? ( + <> +
+ +
+
+ + {t("router.back-to-home")} + +
+ + ) : ( + <> +

Not found

+ + ))}
); diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index 732d7691..3929dd07 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -16,7 +16,7 @@ const Home = lazy(() => import("@/pages/Home")); const MemoDetail = lazy(() => import("@/pages/MemoDetail")); const EmbedMemo = lazy(() => import("@/pages/EmbedMemo")); const NotFound = lazy(() => import("@/pages/NotFound")); -const MemosChat = lazy(() => import("@/pages/MemosChat")); +const MemoChat = lazy(() => import("@/pages/MemoChat")); const initialGlobalStateLoader = (() => { let done = false; @@ -150,7 +150,7 @@ const router = createBrowserRouter([ }, { path: "memo-chat", - element: , + element: , loader: async () => { await initialGlobalStateLoader(); diff --git a/web/src/store/module/memo.ts b/web/src/store/module/memo.ts index a0df71b0..b246ff3f 100644 --- a/web/src/store/module/memo.ts +++ b/web/src/store/module/memo.ts @@ -23,6 +23,7 @@ export const useMemoStore = () => { const fetchMemoById = async (memoId: MemoId) => { const { data } = await api.getMemoById(memoId); const memo = convertResponseModelMemo(data); + store.dispatch(upsertMemos([memo])); return memo; }; @@ -62,6 +63,7 @@ export const useMemoStore = () => { const { data } = await api.getAllMemos(memoFind); const fetchedMemos = data.map((m) => convertResponseModelMemo(m)); + store.dispatch(upsertMemos(fetchedMemos)); for (const m of fetchedMemos) { memoCacheStore.setMemoCache(m);