mirror of
https://github.com/usememos/memos.git
synced 2025-04-12 08:41:09 +02:00
refactor: introducing use{Module}Store
instead of service (#768)
* refactor: introducing `useEditorStore` * refactor: update * chore: update
This commit is contained in:
parent
bd00fa798d
commit
ef621a444f
@ -2,29 +2,30 @@ import { useColorScheme } from "@mui/joy";
|
|||||||
import { useEffect, Suspense } from "react";
|
import { useEffect, Suspense } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import { globalService, locationService } from "./services";
|
|
||||||
import { useAppSelector } from "./store";
|
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
import { useLocationStore, useGlobalStore } from "./store/module";
|
||||||
import * as storage from "./helpers/storage";
|
import * as storage from "./helpers/storage";
|
||||||
import { getSystemColorScheme } from "./helpers/utils";
|
import { getSystemColorScheme } from "./helpers/utils";
|
||||||
import Loading from "./pages/Loading";
|
import Loading from "./pages/Loading";
|
||||||
|
|
||||||
function App() {
|
const App = () => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const { appearance, locale, systemStatus } = useAppSelector((state) => state.global);
|
const globalStore = useGlobalStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
const { mode, setMode } = useColorScheme();
|
const { mode, setMode } = useColorScheme();
|
||||||
|
const { appearance, locale, systemStatus } = globalStore.state;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
locationService.updateStateWithLocation();
|
locationStore.updateStateWithLocation();
|
||||||
window.onpopstate = () => {
|
window.onpopstate = () => {
|
||||||
locationService.updateStateWithLocation();
|
locationStore.updateStateWithLocation();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
||||||
if (globalService.getState().appearance === "system") {
|
if (globalStore.getState().appearance === "system") {
|
||||||
const mode = e.matches ? "dark" : "light";
|
const mode = e.matches ? "dark" : "light";
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
}
|
}
|
||||||
@ -91,6 +92,6 @@ function App() {
|
|||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useGlobalStore } from "../store/module";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import GitHubBadge from "./GitHubBadge";
|
import GitHubBadge from "./GitHubBadge";
|
||||||
@ -9,7 +9,8 @@ type Props = DialogProps;
|
|||||||
|
|
||||||
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const profile = useAppSelector((state) => state.global.systemStatus.profile);
|
const globalStore = useGlobalStore();
|
||||||
|
const profile = globalStore.state.systemStatus.profile;
|
||||||
|
|
||||||
const handleCloseBtnClick = () => {
|
const handleCloseBtnClick = () => {
|
||||||
destroy();
|
destroy();
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Option, Select } from "@mui/joy";
|
import { Option, Select } from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { globalService, userService } from "../services";
|
import { useGlobalStore, useUserStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
const appearanceList = ["system", "light", "dark"];
|
const appearanceList = ["system", "light", "dark"];
|
||||||
|
|
||||||
const AppearanceSelect = () => {
|
const AppearanceSelect = () => {
|
||||||
const user = useAppSelector((state) => state.user.user);
|
|
||||||
const appearance = useAppSelector((state) => state.global.appearance);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { appearance } = globalStore.state;
|
||||||
|
const user = userStore.state.user;
|
||||||
|
|
||||||
const getPrefixIcon = (apperance: Appearance) => {
|
const getPrefixIcon = (apperance: Appearance) => {
|
||||||
const className = "w-4 h-auto";
|
const className = "w-4 h-auto";
|
||||||
@ -24,9 +25,9 @@ const AppearanceSelect = () => {
|
|||||||
|
|
||||||
const handleSelectChange = async (appearance: Appearance) => {
|
const handleSelectChange = async (appearance: Appearance) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
await userService.upsertUserSetting("appearance", appearance);
|
await userStore.upsertUserSetting("appearance", appearance);
|
||||||
}
|
}
|
||||||
globalService.setAppearance(appearance);
|
globalStore.setAppearance(appearance);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useMemoStore } from "../store/module";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { memoService } from "../services";
|
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import MemoContent from "./MemoContent";
|
import MemoContent from "./MemoContent";
|
||||||
import MemoResources from "./MemoResources";
|
import MemoResources from "./MemoResources";
|
||||||
@ -14,13 +14,13 @@ interface Props {
|
|||||||
const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||||
const { memo } = props;
|
const { memo } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
|
|
||||||
const handleDeleteMemoClick = async () => {
|
const handleDeleteMemoClick = async () => {
|
||||||
if (showConfirmDeleteBtn) {
|
if (showConfirmDeleteBtn) {
|
||||||
try {
|
try {
|
||||||
await memoService.deleteMemoById(memo.id);
|
await memoStore.deleteMemoById(memo.id);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
@ -32,11 +32,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
const handleRestoreMemoClick = async () => {
|
const handleRestoreMemoClick = async () => {
|
||||||
try {
|
try {
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: memo.id,
|
id: memo.id,
|
||||||
rowStatus: "NORMAL",
|
rowStatus: "NORMAL",
|
||||||
});
|
});
|
||||||
await memoService.fetchMemos();
|
await memoStore.fetchMemos();
|
||||||
toastHelper.info(t("message.restored-successfully"));
|
toastHelper.info(t("message.restored-successfully"));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useMemoStore } from "../store/module";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { memoService } from "../services";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -14,12 +13,13 @@ type Props = DialogProps;
|
|||||||
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const memos = useAppSelector((state) => state.memo.memos);
|
const memoStore = useMemoStore();
|
||||||
|
const memos = memoStore.state.memos;
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const [archivedMemos, setArchivedMemos] = useState<Memo[]>([]);
|
const [archivedMemos, setArchivedMemos] = useState<Memo[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService
|
memoStore
|
||||||
.fetchArchivedMemos()
|
.fetchArchivedMemos()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
setArchivedMemos(result);
|
setArchivedMemos(result);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useUserStore } from "../store/module";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import { userService } from "../services";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -20,6 +20,7 @@ interface Props extends DialogProps {
|
|||||||
const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { user: propsUser, destroy } = props;
|
const { user: propsUser, destroy } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const userStore = useUserStore();
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await userService.patchUser({
|
await userStore.patchUser({
|
||||||
id: propsUser.id,
|
id: propsUser.id,
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService } from "../services";
|
import { useMemoStore } from "../store/module";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -14,11 +14,12 @@ interface Props extends DialogProps {
|
|||||||
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { destroy, memoId } = props;
|
const { destroy, memoId } = props;
|
||||||
|
const memoStore = useMemoStore();
|
||||||
const [createdAt, setCreatedAt] = useState("");
|
const [createdAt, setCreatedAt] = useState("");
|
||||||
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService.getMemoById(memoId).then((memo) => {
|
memoStore.getMemoById(memoId).then((memo) => {
|
||||||
if (memo) {
|
if (memo) {
|
||||||
const datetime = dayjs(memo.createdTs).format("YYYY-MM-DDTHH:mm");
|
const datetime = dayjs(memo.createdTs).format("YYYY-MM-DDTHH:mm");
|
||||||
setCreatedAt(datetime);
|
setCreatedAt(datetime);
|
||||||
@ -48,7 +49,7 @@ const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: memoId,
|
id: memoId,
|
||||||
createdTs,
|
createdTs,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useUserStore } from "../store/module";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import { userService } from "../services";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -17,6 +17,7 @@ type Props = DialogProps;
|
|||||||
|
|
||||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const userStore = useUserStore();
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||||
|
|
||||||
@ -57,8 +58,8 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = userService.getState().user as User;
|
const user = userStore.getState().user as User;
|
||||||
await userService.patchUser({
|
await userStore.patchUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { resourceService } from "../services";
|
import { useResourceStore } from "../store/module";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -24,8 +24,9 @@ const validateFilename = (filename: string): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
|
const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { destroy, resourceId, resourceFilename } = props;
|
const { destroy, resourceId, resourceFilename } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const resourceStore = useResourceStore();
|
||||||
const [filename, setFilename] = useState<string>(resourceFilename);
|
const [filename, setFilename] = useState<string>(resourceFilename);
|
||||||
|
|
||||||
const handleFilenameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFilenameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -47,7 +48,7 @@ const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await resourceService.patchResource({
|
await resourceStore.patchResource({
|
||||||
id: resourceId,
|
id: resourceId,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { useMemoStore, useShortcutStore } from "../store/module";
|
||||||
import { filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
import { filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -16,6 +16,7 @@ interface Props extends DialogProps {
|
|||||||
|
|
||||||
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { destroy, shortcutId } = props;
|
const { destroy, shortcutId } = props;
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
const [filters, setFilters] = useState<Filter[]>([]);
|
const [filters, setFilters] = useState<Filter[]>([]);
|
||||||
const requestState = useLoading(false);
|
const requestState = useLoading(false);
|
||||||
@ -23,7 +24,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shortcutId) {
|
if (shortcutId) {
|
||||||
const shortcutTemp = shortcutService.getShortcutById(shortcutId);
|
const shortcutTemp = shortcutStore.getShortcutById(shortcutId);
|
||||||
if (shortcutTemp) {
|
if (shortcutTemp) {
|
||||||
setTitle(shortcutTemp.title);
|
setTitle(shortcutTemp.title);
|
||||||
const temp = JSON.parse(shortcutTemp.payload);
|
const temp = JSON.parse(shortcutTemp.payload);
|
||||||
@ -52,13 +53,13 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (shortcutId) {
|
if (shortcutId) {
|
||||||
await shortcutService.patchShortcut({
|
await shortcutStore.patchShortcut({
|
||||||
id: shortcutId,
|
id: shortcutId,
|
||||||
title,
|
title,
|
||||||
payload: JSON.stringify(filters),
|
payload: JSON.stringify(filters),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await shortcutService.createShortcut({
|
await shortcutStore.createShortcut({
|
||||||
title,
|
title,
|
||||||
payload: JSON.stringify(filters),
|
payload: JSON.stringify(filters),
|
||||||
});
|
});
|
||||||
@ -161,9 +162,9 @@ interface MemoFilterInputerProps {
|
|||||||
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
|
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
|
||||||
const { index, filter, handleFilterChange, handleFilterRemove } = props;
|
const { index, filter, handleFilterChange, handleFilterRemove } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
const [value, setValue] = useState<string>(filter.value.value);
|
const [value, setValue] = useState<string>(filter.value.value);
|
||||||
|
const tags = Array.from(memoStore.getState().tags);
|
||||||
const tags = Array.from(memoService.getState().tags);
|
|
||||||
const { type } = filter;
|
const { type } = filter;
|
||||||
|
|
||||||
const operatorDataSource = Object.values(filterConsts[type as FilterType].operators).map(({ text, value }) => ({ text: t(text), value }));
|
const operatorDataSource = Object.values(filterConsts[type as FilterType].operators).map(({ text, value }) => ({ text: t(text), value }));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useMemoStore } from "../store/module";
|
||||||
import toImage from "../labs/html2image";
|
import toImage from "../labs/html2image";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||||
@ -21,7 +21,8 @@ const weekdayChineseStrArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|||||||
|
|
||||||
const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const memos = useAppSelector((state) => state.memo.memos);
|
const memoStore = useMemoStore();
|
||||||
|
const memos = memoStore.state.memos;
|
||||||
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
|
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
|
||||||
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
||||||
const memosElRef = useRef<HTMLDivElement>(null);
|
const memosElRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -3,7 +3,7 @@ import dayjs from "dayjs";
|
|||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
import { useEditorStore, useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import MemoContent from "./MemoContent";
|
import MemoContent from "./MemoContent";
|
||||||
@ -30,9 +30,13 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
const { memo, highlightWord } = props;
|
const { memo, highlightWord } = props;
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const editorStore = useEditorStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
const [displayTimeStr, setDisplayTimeStr] = useState<string>(getFormatedMemoTimeStr(memo.displayTs, i18n.language));
|
const [displayTimeStr, setDisplayTimeStr] = useState<string>(getFormatedMemoTimeStr(memo.displayTs, i18n.language));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userStore.isVisitorMode();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let intervalFlag: any = -1;
|
let intervalFlag: any = -1;
|
||||||
@ -59,9 +63,9 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
const handleTogglePinMemoBtnClick = async () => {
|
const handleTogglePinMemoBtnClick = async () => {
|
||||||
try {
|
try {
|
||||||
if (memo.pinned) {
|
if (memo.pinned) {
|
||||||
await memoService.unpinMemo(memo.id);
|
await memoStore.unpinMemo(memo.id);
|
||||||
} else {
|
} else {
|
||||||
await memoService.pinMemo(memo.id);
|
await memoStore.pinMemo(memo.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
@ -69,12 +73,12 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEditMemoClick = () => {
|
const handleEditMemoClick = () => {
|
||||||
editorStateService.setEditMemoWithId(memo.id);
|
editorStore.setEditMemoWithId(memo.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleArchiveMemoClick = async () => {
|
const handleArchiveMemoClick = async () => {
|
||||||
try {
|
try {
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: memo.id,
|
id: memo.id,
|
||||||
rowStatus: "ARCHIVED",
|
rowStatus: "ARCHIVED",
|
||||||
});
|
});
|
||||||
@ -83,8 +87,8 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorStateService.getState().editMemoId === memo.id) {
|
if (editorStore.getState().editMemoId === memo.id) {
|
||||||
editorStateService.clearEditMemo();
|
editorStore.clearEditMemo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,14 +101,14 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
if (targetEl.className === "tag-span") {
|
if (targetEl.className === "tag-span") {
|
||||||
const tagName = targetEl.innerText.slice(1);
|
const tagName = targetEl.innerText.slice(1);
|
||||||
const currTagQuery = locationService.getState().query?.tag;
|
const currTagQuery = locationStore.getState().query?.tag;
|
||||||
if (currTagQuery === tagName) {
|
if (currTagQuery === tagName) {
|
||||||
locationService.setTagQuery(undefined);
|
locationStore.setTagQuery(undefined);
|
||||||
} else {
|
} else {
|
||||||
locationService.setTagQuery(tagName);
|
locationStore.setTagQuery(tagName);
|
||||||
}
|
}
|
||||||
} else if (targetEl.classList.contains("todo-block")) {
|
} else if (targetEl.classList.contains("todo-block")) {
|
||||||
if (userService.isVisitorMode()) {
|
if (userStore.isVisitorMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +132,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
finalContent += `${tempList[i]}`;
|
finalContent += `${tempList[i]}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: memo.id,
|
id: memo.id,
|
||||||
content: finalContent,
|
content: finalContent,
|
||||||
});
|
});
|
||||||
@ -151,7 +155,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editorStateService.setEditMemoWithId(memo.id);
|
editorStore.setEditMemoWithId(memo.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoDisplayTimeClick = () => {
|
const handleMemoDisplayTimeClick = () => {
|
||||||
@ -159,11 +163,11 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoVisibilityClick = (visibility: Visibility) => {
|
const handleMemoVisibilityClick = (visibility: Visibility) => {
|
||||||
const currVisibilityQuery = locationService.getState().query?.visibility;
|
const currVisibilityQuery = locationStore.getState().query?.visibility;
|
||||||
if (currVisibilityQuery === visibility) {
|
if (currVisibilityQuery === visibility) {
|
||||||
locationService.setMemoVisibilityQuery(undefined);
|
locationStore.setMemoVisibilityQuery(undefined);
|
||||||
} else {
|
} else {
|
||||||
locationService.setMemoVisibilityQuery(visibility);
|
locationStore.setMemoVisibilityQuery(visibility);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useUserStore } from "../store/module";
|
||||||
import { marked } from "../labs/marked";
|
import { marked } from "../labs/marked";
|
||||||
import { highlightWithWord } from "../labs/highlighter";
|
import { highlightWithWord } from "../labs/highlighter";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import "../less/memo-content.less";
|
import "../less/memo-content.less";
|
||||||
|
|
||||||
export interface DisplayConfig {
|
export interface DisplayConfig {
|
||||||
@ -36,7 +36,8 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
|||||||
return firstHorizontalRuleIndex !== -1 ? content.slice(0, firstHorizontalRuleIndex) : content;
|
return firstHorizontalRuleIndex !== -1 ? content.slice(0, firstHorizontalRuleIndex) : content;
|
||||||
}, [content]);
|
}, [content]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user;
|
||||||
|
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
expandButtonStatus: -1,
|
expandButtonStatus: -1,
|
||||||
@ -84,6 +85,9 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
|||||||
setState({
|
setState({
|
||||||
expandButtonStatus: Number(expandButtonStatus) as ExpandButtonStatus,
|
expandButtonStatus: Number(expandButtonStatus) as ExpandButtonStatus,
|
||||||
});
|
});
|
||||||
|
if (!expandButtonStatus) {
|
||||||
|
memoContentContainerRef.current?.scrollIntoView();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3,8 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { deleteMemoResource, upsertMemoResource } from "../helpers/api";
|
import { deleteMemoResource, upsertMemoResource } from "../helpers/api";
|
||||||
import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
import { useEditorStore, useLocationStore, useMemoStore, useResourceStore, useUserStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import * as storage from "../helpers/storage";
|
import * as storage from "../helpers/storage";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -41,19 +40,25 @@ interface State {
|
|||||||
|
|
||||||
const MemoEditor = () => {
|
const MemoEditor = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user as User);
|
const userStore = useUserStore();
|
||||||
const setting = user.setting;
|
const editorStore = useEditorStore();
|
||||||
const editorState = useAppSelector((state) => state.editor);
|
const locationStore = useLocationStore();
|
||||||
const tags = useAppSelector((state) => state.memo.tags);
|
const memoStore = useMemoStore();
|
||||||
|
const resourceStore = useResourceStore();
|
||||||
|
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
isUploadingResource: false,
|
isUploadingResource: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
shouldShowEmojiPicker: false,
|
shouldShowEmojiPicker: false,
|
||||||
});
|
});
|
||||||
const [allowSave, setAllowSave] = useState<boolean>(false);
|
const [allowSave, setAllowSave] = useState<boolean>(false);
|
||||||
|
const editorState = editorStore.state;
|
||||||
const prevEditorStateRef = useRef(editorState);
|
const prevEditorStateRef = useRef(editorState);
|
||||||
const editorRef = useRef<EditorRefActions>(null);
|
const editorRef = useRef<EditorRefActions>(null);
|
||||||
const tagSelectorRef = useRef<HTMLDivElement>(null);
|
const tagSelectorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const user = userStore.state.user as User;
|
||||||
|
const setting = user.setting;
|
||||||
|
const tags = memoStore.state.tags;
|
||||||
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
||||||
return {
|
return {
|
||||||
value: item.value,
|
value: item.value,
|
||||||
@ -64,22 +69,22 @@ const MemoEditor = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { editingMemoIdCache, editingMemoVisibilityCache } = storage.get(["editingMemoIdCache", "editingMemoVisibilityCache"]);
|
const { editingMemoIdCache, editingMemoVisibilityCache } = storage.get(["editingMemoIdCache", "editingMemoVisibilityCache"]);
|
||||||
if (editingMemoIdCache) {
|
if (editingMemoIdCache) {
|
||||||
editorStateService.setEditMemoWithId(editingMemoIdCache);
|
editorStore.setEditMemoWithId(editingMemoIdCache);
|
||||||
}
|
}
|
||||||
if (editingMemoVisibilityCache) {
|
if (editingMemoVisibilityCache) {
|
||||||
editorStateService.setMemoVisibility(editingMemoVisibilityCache as "PUBLIC" | "PROTECTED" | "PRIVATE");
|
editorStore.setMemoVisibility(editingMemoVisibilityCache as "PUBLIC" | "PROTECTED" | "PRIVATE");
|
||||||
} else {
|
} else {
|
||||||
editorStateService.setMemoVisibility(setting.memoVisibility);
|
editorStore.setMemoVisibility(setting.memoVisibility);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorState.editMemoId) {
|
if (editorState.editMemoId) {
|
||||||
memoService.getMemoById(editorState.editMemoId ?? UNKNOWN_ID).then((memo) => {
|
memoStore.getMemoById(editorState.editMemoId ?? UNKNOWN_ID).then((memo) => {
|
||||||
if (memo) {
|
if (memo) {
|
||||||
handleEditorFocus();
|
handleEditorFocus();
|
||||||
editorStateService.setMemoVisibility(memo.visibility);
|
editorStore.setMemoVisibility(memo.visibility);
|
||||||
editorStateService.setResourceList(memo.resourceList);
|
editorStore.setResourceList(memo.resourceList);
|
||||||
editorRef.current?.setContent(memo.content ?? "");
|
editorRef.current?.setContent(memo.content ?? "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -180,8 +185,8 @@ const MemoEditor = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (uploadedResourceList.length > 0) {
|
if (uploadedResourceList.length > 0) {
|
||||||
const resourceList = editorStateService.getState().resourceList;
|
const resourceList = editorStore.getState().resourceList;
|
||||||
editorStateService.setResourceList([...resourceList, ...uploadedResourceList]);
|
editorStore.setResourceList([...resourceList, ...uploadedResourceList]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,7 +215,7 @@ const MemoEditor = () => {
|
|||||||
let resource = undefined;
|
let resource = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resource = await resourceService.upload(file);
|
resource = await resourceStore.upload(file);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
@ -233,26 +238,26 @@ const MemoEditor = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { editMemoId } = editorStateService.getState();
|
const { editMemoId } = editorStore.getState();
|
||||||
if (editMemoId && editMemoId !== UNKNOWN_ID) {
|
if (editMemoId && editMemoId !== UNKNOWN_ID) {
|
||||||
const prevMemo = await memoService.getMemoById(editMemoId ?? UNKNOWN_ID);
|
const prevMemo = await memoStore.getMemoById(editMemoId ?? UNKNOWN_ID);
|
||||||
|
|
||||||
if (prevMemo) {
|
if (prevMemo) {
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: prevMemo.id,
|
id: prevMemo.id,
|
||||||
content,
|
content,
|
||||||
visibility: editorState.memoVisibility,
|
visibility: editorState.memoVisibility,
|
||||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
editorStateService.clearEditMemo();
|
editorStore.clearEditMemo();
|
||||||
} else {
|
} else {
|
||||||
await memoService.createMemo({
|
await memoStore.createMemo({
|
||||||
content,
|
content,
|
||||||
visibility: editorState.memoVisibility,
|
visibility: editorState.memoVisibility,
|
||||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
||||||
});
|
});
|
||||||
locationService.clearQuery();
|
locationStore.clearQuery();
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -265,7 +270,7 @@ const MemoEditor = () => {
|
|||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
editorStateService.clearResourceList();
|
editorStore.clearResourceList();
|
||||||
setEditorContentCache("");
|
setEditorContentCache("");
|
||||||
storage.remove(["editingMemoVisibilityCache"]);
|
storage.remove(["editingMemoVisibilityCache"]);
|
||||||
editorRef.current?.setContent("");
|
editorRef.current?.setContent("");
|
||||||
@ -273,8 +278,8 @@ const MemoEditor = () => {
|
|||||||
|
|
||||||
const handleCancelEdit = () => {
|
const handleCancelEdit = () => {
|
||||||
if (editorState.editMemoId) {
|
if (editorState.editMemoId) {
|
||||||
editorStateService.clearEditMemo();
|
editorStore.clearEditMemo();
|
||||||
editorStateService.clearResourceList();
|
editorStore.clearResourceList();
|
||||||
editorRef.current?.setContent("");
|
editorRef.current?.setContent("");
|
||||||
setEditorContentCache("");
|
setEditorContentCache("");
|
||||||
storage.remove(["editingMemoVisibilityCache"]);
|
storage.remove(["editingMemoVisibilityCache"]);
|
||||||
@ -338,7 +343,7 @@ const MemoEditor = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editorStateService.setResourceList([...editorState.resourceList, ...resourceList]);
|
editorStore.setResourceList([...editorState.resourceList, ...resourceList]);
|
||||||
document.body.removeChild(inputEl);
|
document.body.removeChild(inputEl);
|
||||||
};
|
};
|
||||||
inputEl.click();
|
inputEl.click();
|
||||||
@ -361,7 +366,7 @@ const MemoEditor = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteResource = async (resourceId: ResourceId) => {
|
const handleDeleteResource = async (resourceId: ResourceId) => {
|
||||||
editorStateService.setResourceList(editorState.resourceList.filter((resource) => resource.id !== resourceId));
|
editorStore.setResourceList(editorState.resourceList.filter((resource) => resource.id !== resourceId));
|
||||||
if (editorState.editMemoId) {
|
if (editorState.editMemoId) {
|
||||||
await deleteMemoResource(editorState.editMemoId, resourceId);
|
await deleteMemoResource(editorState.editMemoId, resourceId);
|
||||||
}
|
}
|
||||||
@ -369,7 +374,7 @@ const MemoEditor = () => {
|
|||||||
|
|
||||||
const handleMemoVisibilityOptionChanged = async (value: string) => {
|
const handleMemoVisibilityOptionChanged = async (value: string) => {
|
||||||
const visibilityValue = value as Visibility;
|
const visibilityValue = value as Visibility;
|
||||||
editorStateService.setMemoVisibility(visibilityValue);
|
editorStore.setMemoVisibility(visibilityValue);
|
||||||
setEditingMemoVisibilityCache(visibilityValue);
|
setEditingMemoVisibilityCache(visibilityValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useLocationStore, useShortcutStore } from "../store/module";
|
||||||
import { locationService, shortcutService } from "../services";
|
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { getTextWithMemoType } from "../helpers/filter";
|
import { getTextWithMemoType } from "../helpers/filter";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -8,10 +7,11 @@ import "../less/memo-filter.less";
|
|||||||
|
|
||||||
const MemoFilter = () => {
|
const MemoFilter = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useAppSelector((state) => state.shortcut.shortcuts);
|
const locationStore = useLocationStore();
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const shortcutStore = useShortcutStore();
|
||||||
|
const query = locationStore.state.query;
|
||||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId, visibility } = query;
|
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId, visibility } = query;
|
||||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
const shortcut = shortcutId ? shortcutStore.getShortcutById(shortcutId) : null;
|
||||||
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility);
|
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -20,7 +20,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className={"filter-item-container " + (shortcut ? "" : "!hidden")}
|
className={"filter-item-container " + (shortcut ? "" : "!hidden")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setMemoShortcut(undefined);
|
locationStore.setMemoShortcut(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Target className="icon-text" /> {shortcut?.title}
|
<Icon.Target className="icon-text" /> {shortcut?.title}
|
||||||
@ -28,7 +28,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className={"filter-item-container " + (tagQuery ? "" : "!hidden")}
|
className={"filter-item-container " + (tagQuery ? "" : "!hidden")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setTagQuery(undefined);
|
locationStore.setTagQuery(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Tag className="icon-text" /> {tagQuery}
|
<Icon.Tag className="icon-text" /> {tagQuery}
|
||||||
@ -36,7 +36,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className={"filter-item-container " + (memoType ? "" : "!hidden")}
|
className={"filter-item-container " + (memoType ? "" : "!hidden")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setMemoTypeQuery(undefined);
|
locationStore.setMemoTypeQuery(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))}
|
<Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))}
|
||||||
@ -44,7 +44,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className={"filter-item-container " + (visibility ? "" : "!hidden")}
|
className={"filter-item-container " + (visibility ? "" : "!hidden")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setMemoVisibilityQuery(undefined);
|
locationStore.setMemoVisibilityQuery(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Eye className="icon-text" /> {visibility}
|
<Icon.Eye className="icon-text" /> {visibility}
|
||||||
@ -53,7 +53,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className="filter-item-container"
|
className="filter-item-container"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setFromAndToQuery();
|
locationStore.setFromAndToQuery();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Calendar className="icon-text" /> {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)}
|
<Icon.Calendar className="icon-text" /> {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)}
|
||||||
@ -62,7 +62,7 @@ const MemoFilter = () => {
|
|||||||
<div
|
<div
|
||||||
className={"filter-item-container " + (textQuery ? "" : "!hidden")}
|
className={"filter-item-container " + (textQuery ? "" : "!hidden")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.setTextQuery(undefined);
|
locationStore.setTextQuery(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon.Search className="icon-text" /> {textQuery}
|
<Icon.Search className="icon-text" /> {textQuery}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { useLocationStore, useMemoStore, useShortcutStore, useUserStore } from "../store/module";
|
||||||
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import { TAG_REG, LINK_REG } from "../labs/marked/parser";
|
import { TAG_REG, LINK_REG } from "../labs/marked/parser";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
import { DEFAULT_MEMO_LIMIT } from "../helpers/consts";
|
||||||
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
|
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Memo from "./Memo";
|
import Memo from "./Memo";
|
||||||
@ -12,14 +11,18 @@ import "../less/memo-list.less";
|
|||||||
|
|
||||||
const MemoList = () => {
|
const MemoList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const userStore = useUserStore();
|
||||||
const memoDisplayTsOption = useAppSelector((state) => state.user.user?.setting.memoDisplayTsOption);
|
const memoStore = useMemoStore();
|
||||||
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
const shortcutStore = useShortcutStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const query = locationStore.state.query;
|
||||||
|
const memoDisplayTsOption = userStore.state.user?.setting.memoDisplayTsOption;
|
||||||
|
const { memos, isFetching } = memoStore.state;
|
||||||
const [isComplete, setIsComplete] = useState<boolean>(false);
|
const [isComplete, setIsComplete] = useState<boolean>(false);
|
||||||
const [highlightWord, setHighlightWord] = useState<string | undefined>("");
|
const [highlightWord, setHighlightWord] = useState<string | undefined>("");
|
||||||
|
|
||||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId, visibility } = query ?? {};
|
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId, visibility } = query ?? {};
|
||||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
const shortcut = shortcutId ? shortcutStore.getShortcutById(shortcutId) : null;
|
||||||
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility);
|
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility);
|
||||||
|
|
||||||
const shownMemos =
|
const shownMemos =
|
||||||
@ -84,7 +87,7 @@ const MemoList = () => {
|
|||||||
const sortedMemos = pinnedMemos.concat(unpinnedMemos).filter((m) => m.rowStatus === "NORMAL");
|
const sortedMemos = pinnedMemos.concat(unpinnedMemos).filter((m) => m.rowStatus === "NORMAL");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService
|
memoStore
|
||||||
.fetchMemos()
|
.fetchMemos()
|
||||||
.then((fetchedMemos) => {
|
.then((fetchedMemos) => {
|
||||||
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
@ -118,7 +121,7 @@ const MemoList = () => {
|
|||||||
|
|
||||||
const handleFetchMoreClick = async () => {
|
const handleFetchMoreClick = async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedMemos = await memoService.fetchMemos(DEFAULT_MEMO_LIMIT, memos.length);
|
const fetchedMemos = await memoStore.fetchMemos(DEFAULT_MEMO_LIMIT, memos.length);
|
||||||
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
setIsComplete(true);
|
setIsComplete(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { useLocationStore, useMemoStore, useShortcutStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import { toggleSidebar } from "./Sidebar";
|
import { toggleSidebar } from "./Sidebar";
|
||||||
@ -9,8 +8,11 @@ import "../less/memos-header.less";
|
|||||||
let prevRequestTimestamp = Date.now();
|
let prevRequestTimestamp = Date.now();
|
||||||
|
|
||||||
const MemosHeader = () => {
|
const MemosHeader = () => {
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const locationStore = useLocationStore();
|
||||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
const memoStore = useMemoStore();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
|
const query = locationStore.state.query;
|
||||||
|
const shortcuts = shortcutStore.state.shortcuts;
|
||||||
const [titleText, setTitleText] = useState("MEMOS");
|
const [titleText, setTitleText] = useState("MEMOS");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -19,7 +21,7 @@ const MemosHeader = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortcut = shortcutService.getShortcutById(query?.shortcutId);
|
const shortcut = shortcutStore.getShortcutById(query?.shortcutId);
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
setTitleText(shortcut.title);
|
setTitleText(shortcut.title);
|
||||||
}
|
}
|
||||||
@ -29,7 +31,7 @@ const MemosHeader = () => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - prevRequestTimestamp > 1 * 1000) {
|
if (now - prevRequestTimestamp > 1 * 1000) {
|
||||||
prevRequestTimestamp = now;
|
prevRequestTimestamp = now;
|
||||||
memoService.fetchMemos().catch(() => {
|
memoStore.fetchMemos().catch(() => {
|
||||||
// do nth
|
// do nth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,7 @@ import copy from "copy-to-clipboard";
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { resourceService } from "../services";
|
import { useResourceStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
@ -24,13 +23,14 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const { resources } = useAppSelector((state) => state.resource);
|
const resourceStore = useResourceStore();
|
||||||
|
const resources = resourceStore.state.resources;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
isUploadingResource: false,
|
isUploadingResource: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resourceService
|
resourceStore
|
||||||
.fetchResourceList()
|
.fetchResourceList()
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -66,7 +66,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
for (const file of inputEl.files) {
|
for (const file of inputEl.files) {
|
||||||
try {
|
try {
|
||||||
await resourceService.upload(file);
|
await resourceStore.upload(file);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
@ -127,7 +127,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
style: "warning",
|
style: "warning",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
for (const resource of unusedResources) {
|
for (const resource of unusedResources) {
|
||||||
await resourceService.deleteResourceById(resource.id);
|
await resourceStore.deleteResourceById(resource.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -144,7 +144,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
content: warningText,
|
content: warningText,
|
||||||
style: "warning",
|
style: "warning",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await resourceService.deleteResourceById(resource.id);
|
await resourceStore.deleteResourceById(resource.id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,8 +2,7 @@ import { Checkbox, Tooltip } from "@mui/joy";
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { editorStateService, resourceService } from "../services";
|
import { useEditorStore, useResourceStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -20,14 +19,15 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const { resources } = useAppSelector((state) => state.resource);
|
const editorStore = useEditorStore();
|
||||||
const editorState = useAppSelector((state) => state.editor);
|
const resourceStore = useResourceStore();
|
||||||
|
const resources = resourceStore.state.resources;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
checkedArray: [],
|
checkedArray: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resourceService
|
resourceStore
|
||||||
.fetchResourceList()
|
.fetchResourceList()
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -39,7 +39,7 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkedResourceIdArray = editorState.resourceList.map((resource) => resource.id);
|
const checkedResourceIdArray = editorStore.state.resourceList.map((resource) => resource.id);
|
||||||
setState({
|
setState({
|
||||||
checkedArray: resources.map((resource) => {
|
checkedArray: resources.map((resource) => {
|
||||||
return checkedResourceIdArray.includes(resource.id);
|
return checkedResourceIdArray.includes(resource.id);
|
||||||
@ -75,7 +75,7 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const resourceList = resources.filter((_, index) => {
|
const resourceList = resources.filter((_, index) => {
|
||||||
return state.checkedArray[index];
|
return state.checkedArray[index];
|
||||||
});
|
});
|
||||||
editorStateService.setResourceList(resourceList);
|
editorStore.setResourceList(resourceList);
|
||||||
destroy();
|
destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { locationService } from "../services";
|
import { useLocationStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import { memoSpecialTypes } from "../helpers/filter";
|
import { memoSpecialTypes } from "../helpers/filter";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import "../less/search-bar.less";
|
import "../less/search-bar.less";
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const memoType = useAppSelector((state) => state.location.query?.type);
|
const locationStore = useLocationStore();
|
||||||
|
const memoType = locationStore.state.query.type;
|
||||||
const [queryText, setQueryText] = useState("");
|
const [queryText, setQueryText] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const text = locationService.getState().query.text;
|
const text = locationStore.getState().query.text;
|
||||||
setQueryText(text === undefined ? "" : text);
|
setQueryText(text === undefined ? "" : text);
|
||||||
}, [locationService.getState().query.text]);
|
}, [locationStore.getState().query.text]);
|
||||||
|
|
||||||
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
||||||
const { type: prevType } = locationService.getState().query ?? {};
|
const { type: prevType } = locationStore.getState().query ?? {};
|
||||||
if (type === prevType) {
|
if (type === prevType) {
|
||||||
type = undefined;
|
type = undefined;
|
||||||
}
|
}
|
||||||
locationService.setMemoTypeQuery(type);
|
locationStore.setMemoTypeQuery(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTextQueryInput = (event: React.FormEvent<HTMLInputElement>) => {
|
const handleTextQueryInput = (event: React.FormEvent<HTMLInputElement>) => {
|
||||||
const text = event.currentTarget.value;
|
const text = event.currentTarget.value;
|
||||||
setQueryText(text);
|
setQueryText(text);
|
||||||
locationService.setTextQuery(text.length === 0 ? undefined : text);
|
locationStore.setTextQuery(text.length === 0 ? undefined : text);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import MyAccountSection from "./Settings/MyAccountSection";
|
import MyAccountSection from "./Settings/MyAccountSection";
|
||||||
@ -8,6 +7,7 @@ import PreferencesSection from "./Settings/PreferencesSection";
|
|||||||
import MemberSection from "./Settings/MemberSection";
|
import MemberSection from "./Settings/MemberSection";
|
||||||
import SystemSection from "./Settings/SystemSection";
|
import SystemSection from "./Settings/SystemSection";
|
||||||
import "../less/setting-dialog.less";
|
import "../less/setting-dialog.less";
|
||||||
|
import { useUserStore } from "../store/module";
|
||||||
|
|
||||||
type Props = DialogProps;
|
type Props = DialogProps;
|
||||||
|
|
||||||
@ -20,7 +20,8 @@ interface State {
|
|||||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
selectedSection: "my-account",
|
selectedSection: "my-account",
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { userService } from "../../services";
|
import { useUserStore } from "../../store/module";
|
||||||
import { useAppSelector } from "../../store";
|
|
||||||
import * as api from "../../helpers/api";
|
import * as api from "../../helpers/api";
|
||||||
import toastHelper from "../Toast";
|
import toastHelper from "../Toast";
|
||||||
import Dropdown from "../common/Dropdown";
|
import Dropdown from "../common/Dropdown";
|
||||||
@ -16,7 +15,8 @@ interface State {
|
|||||||
|
|
||||||
const PreferencesSection = () => {
|
const PreferencesSection = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currentUser = useAppSelector((state) => state.user.user);
|
const userStore = useUserStore();
|
||||||
|
const currentUser = userStore.state.user;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
createUserUsername: "",
|
createUserUsername: "",
|
||||||
createUserPassword: "",
|
createUserPassword: "",
|
||||||
@ -80,7 +80,7 @@ const PreferencesSection = () => {
|
|||||||
content: `❗️Are you sure to archive ${user.username}?`,
|
content: `❗️Are you sure to archive ${user.username}?`,
|
||||||
style: "warning",
|
style: "warning",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await userService.patchUser({
|
await userStore.patchUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
rowStatus: "ARCHIVED",
|
rowStatus: "ARCHIVED",
|
||||||
});
|
});
|
||||||
@ -90,7 +90,7 @@ const PreferencesSection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRestoreUserClick = async (user: User) => {
|
const handleRestoreUserClick = async (user: User) => {
|
||||||
await userService.patchUser({
|
await userStore.patchUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
rowStatus: "NORMAL",
|
rowStatus: "NORMAL",
|
||||||
});
|
});
|
||||||
@ -103,7 +103,7 @@ const PreferencesSection = () => {
|
|||||||
content: `Are you sure to delete ${user.username}? THIS ACTION IS IRREVERSIABLE.❗️`,
|
content: `Are you sure to delete ${user.username}? THIS ACTION IS IRREVERSIABLE.❗️`,
|
||||||
style: "warning",
|
style: "warning",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await userService.deleteUser({
|
await userStore.deleteUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
});
|
});
|
||||||
fetchUserList();
|
fetchUserList();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../../store";
|
import { useUserStore } from "../../store/module";
|
||||||
import { userService } from "../../services";
|
|
||||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||||
import showChangePasswordDialog from "../ChangePasswordDialog";
|
import showChangePasswordDialog from "../ChangePasswordDialog";
|
||||||
import showUpdateAccountDialog from "../UpdateAccountDialog";
|
import showUpdateAccountDialog from "../UpdateAccountDialog";
|
||||||
@ -8,7 +7,8 @@ import "../../less/settings/my-account-section.less";
|
|||||||
|
|
||||||
const MyAccountSection = () => {
|
const MyAccountSection = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user as User);
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user as User;
|
||||||
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
||||||
|
|
||||||
const handleResetOpenIdBtnClick = async () => {
|
const handleResetOpenIdBtnClick = async () => {
|
||||||
@ -17,7 +17,7 @@ const MyAccountSection = () => {
|
|||||||
content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?",
|
content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?",
|
||||||
style: "warning",
|
style: "warning",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await userService.patchUser({
|
await userStore.patchUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
resetOpenId: true,
|
resetOpenId: true,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Select, Switch, Option } from "@mui/joy";
|
import { Select, Switch, Option } from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { globalService, userService } from "../../services";
|
import { useGlobalStore, useUserStore } from "../../store/module";
|
||||||
import { useAppSelector } from "../../store";
|
|
||||||
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import AppearanceSelect from "../AppearanceSelect";
|
import AppearanceSelect from "../AppearanceSelect";
|
||||||
@ -44,7 +43,9 @@ const localeSelectorItems = [
|
|||||||
|
|
||||||
const PreferencesSection = () => {
|
const PreferencesSection = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setting, localSetting } = useAppSelector((state) => state.user.user as User);
|
const globalStore = useGlobalStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { setting, localSetting } = userStore.state.user as User;
|
||||||
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
||||||
return {
|
return {
|
||||||
value: item.value,
|
value: item.value,
|
||||||
@ -60,20 +61,20 @@ const PreferencesSection = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleLocaleChanged = async (value: string) => {
|
const handleLocaleChanged = async (value: string) => {
|
||||||
await userService.upsertUserSetting("locale", value);
|
await userStore.upsertUserSetting("locale", value);
|
||||||
globalService.setLocale(value as Locale);
|
globalStore.setLocale(value as Locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDefaultMemoVisibilityChanged = async (value: string) => {
|
const handleDefaultMemoVisibilityChanged = async (value: string) => {
|
||||||
await userService.upsertUserSetting("memoVisibility", value);
|
await userStore.upsertUserSetting("memoVisibility", value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoDisplayTsOptionChanged = async (value: string) => {
|
const handleMemoDisplayTsOptionChanged = async (value: string) => {
|
||||||
await userService.upsertUserSetting("memoDisplayTsOption", value);
|
await userStore.upsertUserSetting("memoDisplayTsOption", value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIsFoldingEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleIsFoldingEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
userService.upsertLocalSetting("enableFoldMemo", event.target.checked);
|
userStore.upsertLocalSetting("enableFoldMemo", event.target.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { toLower } from "lodash";
|
import { toLower } from "lodash";
|
||||||
import toImage from "../labs/html2image";
|
import toImage from "../labs/html2image";
|
||||||
|
import { useMemoStore, useUserStore } from "../store/module";
|
||||||
import { VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
import { VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { getMemoStats } from "../helpers/api";
|
import { getMemoStats } from "../helpers/api";
|
||||||
import { memoService, userService } from "../services";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -29,7 +29,9 @@ interface State {
|
|||||||
const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { memo: propsMemo, destroy } = props;
|
const { memo: propsMemo, destroy } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = userService.getState().user as User;
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
|
const user = userStore.state.user as User;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
memoAmount: 0,
|
memoAmount: 0,
|
||||||
memoVisibility: propsMemo.visibility,
|
memoVisibility: propsMemo.visibility,
|
||||||
@ -113,7 +115,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
|||||||
...state,
|
...state,
|
||||||
memoVisibility: visibilityValue,
|
memoVisibility: visibilityValue,
|
||||||
});
|
});
|
||||||
await memoService.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: memo.id,
|
id: memo.id,
|
||||||
visibility: visibilityValue,
|
visibility: visibilityValue,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { locationService, shortcutService } from "../services";
|
import { useLocationStore, useShortcutStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
@ -11,10 +10,12 @@ import showCreateShortcutDialog from "./CreateShortcutDialog";
|
|||||||
import "../less/shortcut-list.less";
|
import "../less/shortcut-list.less";
|
||||||
|
|
||||||
const ShortcutList = () => {
|
const ShortcutList = () => {
|
||||||
const query = useAppSelector((state) => state.location.query);
|
|
||||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
|
||||||
const loadingState = useLoading();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
|
const query = locationStore.state.query;
|
||||||
|
const shortcuts = shortcutStore.state.shortcuts;
|
||||||
|
const loadingState = useLoading();
|
||||||
|
|
||||||
const pinnedShortcuts = shortcuts
|
const pinnedShortcuts = shortcuts
|
||||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
.filter((s) => s.rowStatus === "ARCHIVED")
|
||||||
@ -25,7 +26,7 @@ const ShortcutList = () => {
|
|||||||
const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts);
|
const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
shortcutService
|
shortcutStore
|
||||||
.getMyAllShortcuts()
|
.getMyAllShortcuts()
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// do nth
|
// do nth
|
||||||
@ -60,13 +61,15 @@ interface ShortcutContainerProps {
|
|||||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||||
const { shortcut, isActive } = props;
|
const { shortcut, isActive } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
|
|
||||||
const handleShortcutClick = () => {
|
const handleShortcutClick = () => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
locationService.setMemoShortcut(undefined);
|
locationStore.setMemoShortcut(undefined);
|
||||||
} else {
|
} else {
|
||||||
locationService.setMemoShortcut(shortcut.id);
|
locationStore.setMemoShortcut(shortcut.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,10 +78,10 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
|||||||
|
|
||||||
if (showConfirmDeleteBtn) {
|
if (showConfirmDeleteBtn) {
|
||||||
try {
|
try {
|
||||||
await shortcutService.deleteShortcutById(shortcut.id);
|
await shortcutStore.deleteShortcutById(shortcut.id);
|
||||||
if (locationService.getState().query?.shortcutId === shortcut.id) {
|
if (locationStore.getState().query?.shortcutId === shortcut.id) {
|
||||||
// need clear shortcut filter
|
// need clear shortcut filter
|
||||||
locationService.setMemoShortcut(undefined);
|
locationStore.setMemoShortcut(undefined);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -102,7 +105,7 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
|||||||
id: shortcut.id,
|
id: shortcut.id,
|
||||||
rowStatus: shortcut.rowStatus === "ARCHIVED" ? "NORMAL" : "ARCHIVED",
|
rowStatus: shortcut.rowStatus === "ARCHIVED" ? "NORMAL" : "ARCHIVED",
|
||||||
};
|
};
|
||||||
await shortcutService.patchShortcut(shortcutPatch);
|
await shortcutStore.patchShortcut(shortcutPatch);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,7 @@ import { isUndefined } from "lodash-es";
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { userService } from "../services";
|
import { useLocationStore, useUserStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||||
import showSettingDialog from "./SettingDialog";
|
import showSettingDialog from "./SettingDialog";
|
||||||
import UserBanner from "./UserBanner";
|
import UserBanner from "./UserBanner";
|
||||||
@ -14,11 +13,13 @@ import "../less/siderbar.less";
|
|||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useAppSelector((state) => state.location);
|
const userStore = useUserStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const query = locationStore.state.query;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
toggleSidebar(false);
|
toggleSidebar(false);
|
||||||
}, [location.query]);
|
}, [query]);
|
||||||
|
|
||||||
const handleSettingBtnClick = () => {
|
const handleSettingBtnClick = () => {
|
||||||
showSettingDialog();
|
showSettingDialog();
|
||||||
@ -34,7 +35,7 @@ const Sidebar = () => {
|
|||||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||||
</button>
|
</button>
|
||||||
{!userService.isVisitorMode() && (
|
{!userStore.isVisitorMode() && (
|
||||||
<>
|
<>
|
||||||
<Link to="/explore" className="btn action-btn">
|
<Link to="/explore" className="btn action-btn">
|
||||||
<span className="icon">🏂</span> {t("common.explore")}
|
<span className="icon">🏂</span> {t("common.explore")}
|
||||||
@ -45,7 +46,7 @@ const Sidebar = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!userService.isVisitorMode() && <ShortcutList />}
|
{!userStore.isVisitorMode() && <ShortcutList />}
|
||||||
<TagList />
|
<TagList />
|
||||||
</aside>
|
</aside>
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import { locationService, memoService, userService } from "../services";
|
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import "../less/tag-list.less";
|
import "../less/tag-list.less";
|
||||||
@ -14,13 +13,16 @@ interface Tag {
|
|||||||
|
|
||||||
const TagList = () => {
|
const TagList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
const locationStore = useLocationStore();
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
|
const { memos, tags: tagsText } = memoStore.state;
|
||||||
|
const query = locationStore.state.query;
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (memos.length > 0) {
|
if (memos.length > 0) {
|
||||||
memoService.updateTagsState();
|
memoStore.updateTagsState();
|
||||||
}
|
}
|
||||||
}, [memos]);
|
}, [memos]);
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ const TagList = () => {
|
|||||||
{tags.map((t, idx) => (
|
{tags.map((t, idx) => (
|
||||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||||
))}
|
))}
|
||||||
{!userService.isVisitorMode() && tags.length === 0 && <p className="tip-text">{t("tag-list.tip-text")}</p>}
|
{!userStore.isVisitorMode() && tags.length === 0 && <p className="tip-text">{t("tag-list.tip-text")}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -87,6 +89,7 @@ interface TagItemContainerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
||||||
|
const locationStore = useLocationStore();
|
||||||
const { tag, tagQuery } = props;
|
const { tag, tagQuery } = props;
|
||||||
const isActive = tagQuery === tag.text;
|
const isActive = tagQuery === tag.text;
|
||||||
const hasSubTags = tag.subTags.length > 0;
|
const hasSubTags = tag.subTags.length > 0;
|
||||||
@ -94,9 +97,9 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
|
|||||||
|
|
||||||
const handleTagClick = () => {
|
const handleTagClick = () => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
locationService.setTagQuery(undefined);
|
locationStore.setTagQuery(undefined);
|
||||||
} else {
|
} else {
|
||||||
locationService.setTagQuery(tag.text);
|
locationStore.setTagQuery(tag.text);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useUserStore } from "../store/module";
|
||||||
import { userService } from "../services";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
|
||||||
|
|
||||||
const validateConfig: ValidatorConfig = {
|
const validateConfig: ValidatorConfig = {
|
||||||
minLength: 4,
|
minLength: 4,
|
||||||
@ -25,7 +24,8 @@ interface State {
|
|||||||
|
|
||||||
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user as User);
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user as User;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
username: user.username,
|
username: user.username,
|
||||||
nickname: user.nickname,
|
nickname: user.nickname,
|
||||||
@ -78,7 +78,7 @@ const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = userService.getState().user as User;
|
const user = userStore.getState().user as User;
|
||||||
const userPatch: UserPatch = {
|
const userPatch: UserPatch = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
};
|
};
|
||||||
@ -91,7 +91,7 @@ const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
if (!isEqual(user.email, state.email)) {
|
if (!isEqual(user.email, state.email)) {
|
||||||
userPatch.email = state.email;
|
userPatch.email = state.email;
|
||||||
}
|
}
|
||||||
await userService.patchUser(userPatch);
|
await userStore.patchUser(userPatch);
|
||||||
toastHelper.info("Update succeed");
|
toastHelper.info("Update succeed");
|
||||||
handleCloseBtnClick();
|
handleCloseBtnClick();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import * as storage from "../helpers/storage";
|
import * as storage from "../helpers/storage";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import "../less/about-site-dialog.less";
|
import "../less/about-site-dialog.less";
|
||||||
|
import { useGlobalStore } from "../store/module";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
@ -11,7 +11,8 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UpdateVersionBanner: React.FC = () => {
|
const UpdateVersionBanner: React.FC = () => {
|
||||||
const profile = useAppSelector((state) => state.global.systemStatus.profile);
|
const globalStore = useGlobalStore();
|
||||||
|
const profile = globalStore.state.systemStatus.profile;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
latestVersion: "",
|
latestVersion: "",
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useAppSelector } from "../store";
|
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import { locationService, userService } from "../services";
|
|
||||||
import { getMemoStats } from "../helpers/api";
|
import { getMemoStats } from "../helpers/api";
|
||||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
@ -28,19 +27,21 @@ interface DailyUsageStat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UsageHeatMap = () => {
|
const UsageHeatMap = () => {
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
const todayTimeStamp = utils.getDateStampByDate(Date.now());
|
const todayTimeStamp = utils.getDateStampByDate(Date.now());
|
||||||
const todayDay = new Date(todayTimeStamp).getDay() + 1;
|
const todayDay = new Date(todayTimeStamp).getDay() + 1;
|
||||||
const nullCell = new Array(7 - todayDay).fill(0);
|
const nullCell = new Array(7 - todayDay).fill(0);
|
||||||
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
||||||
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
||||||
|
const memos = memoStore.state.memos;
|
||||||
const { memos } = useAppSelector((state) => state.memo);
|
|
||||||
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
||||||
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
||||||
const containerElRef = useRef<HTMLDivElement>(null);
|
const containerElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getMemoStats(userService.getCurrentUserId())
|
getMemoStats(userStore.getCurrentUserId())
|
||||||
.then(({ data: { data } }) => {
|
.then(({ data: { data } }) => {
|
||||||
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
||||||
for (const record of data) {
|
for (const record of data) {
|
||||||
@ -84,11 +85,11 @@ const UsageHeatMap = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => {
|
const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => {
|
||||||
if (locationService.getState().query?.duration?.from === item.timestamp) {
|
if (locationStore.getState().query?.duration?.from === item.timestamp) {
|
||||||
locationService.setFromAndToQuery();
|
locationStore.setFromAndToQuery();
|
||||||
setCurrentStat(null);
|
setCurrentStat(null);
|
||||||
} else if (item.count > 0) {
|
} else if (item.count > 0) {
|
||||||
locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);
|
locationStore.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);
|
||||||
setCurrentStat(item);
|
setCurrentStat(item);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import { getMemoStats } from "../helpers/api";
|
import { getMemoStats } from "../helpers/api";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import userService from "../services/userService";
|
|
||||||
import { locationService } from "../services";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
import showResourcesDialog from "./ResourcesDialog";
|
import showResourcesDialog from "./ResourcesDialog";
|
||||||
@ -16,12 +14,15 @@ import "../less/user-banner.less";
|
|||||||
const UserBanner = () => {
|
const UserBanner = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user, owner } = useAppSelector((state) => state.user);
|
const locationStore = useLocationStore();
|
||||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
|
const { user, owner } = userStore.state;
|
||||||
|
const { memos, tags } = memoStore.state;
|
||||||
const [username, setUsername] = useState("Memos");
|
const [username, setUsername] = useState("Memos");
|
||||||
const [memoAmount, setMemoAmount] = useState(0);
|
const [memoAmount, setMemoAmount] = useState(0);
|
||||||
const [createdDays, setCreatedDays] = useState(0);
|
const [createdDays, setCreatedDays] = useState(0);
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userStore.isVisitorMode();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisitorMode) {
|
if (isVisitorMode) {
|
||||||
@ -37,7 +38,7 @@ const UserBanner = () => {
|
|||||||
}, [isVisitorMode, user, owner]);
|
}, [isVisitorMode, user, owner]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getMemoStats(userService.getCurrentUserId())
|
getMemoStats(userStore.getCurrentUserId())
|
||||||
.then(({ data: { data } }) => {
|
.then(({ data: { data } }) => {
|
||||||
setMemoAmount(data.length);
|
setMemoAmount(data.length);
|
||||||
})
|
})
|
||||||
@ -47,7 +48,7 @@ const UserBanner = () => {
|
|||||||
}, [memos]);
|
}, [memos]);
|
||||||
|
|
||||||
const handleUsernameClick = useCallback(() => {
|
const handleUsernameClick = useCallback(() => {
|
||||||
locationService.clearQuery();
|
locationStore.clearQuery();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleResourcesBtnClick = () => {
|
const handleResourcesBtnClick = () => {
|
||||||
@ -78,7 +79,7 @@ const UserBanner = () => {
|
|||||||
actionsClassName="min-w-36"
|
actionsClassName="min-w-36"
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
{!userService.isVisitorMode() && (
|
{!userStore.isVisitorMode() && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
||||||
@ -100,7 +101,7 @@ const UserBanner = () => {
|
|||||||
>
|
>
|
||||||
<span className="mr-1">🤠</span> {t("common.about")}
|
<span className="mr-1">🤠</span> {t("common.about")}
|
||||||
</button>
|
</button>
|
||||||
{!userService.isVisitorMode() && (
|
{!userStore.isVisitorMode() && (
|
||||||
<button
|
<button
|
||||||
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
||||||
onClick={handleSignOutBtnClick}
|
onClick={handleSignOutBtnClick}
|
||||||
|
@ -18,4 +18,8 @@ export const MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS = [
|
|||||||
{ text: "updated_ts", value: "updated_ts" },
|
{ text: "updated_ts", value: "updated_ts" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// space width for tab action in editor
|
||||||
export const TAB_SPACE_WIDTH = 2;
|
export const TAB_SPACE_WIDTH = 2;
|
||||||
|
|
||||||
|
// default fetch memo amount
|
||||||
|
export const DEFAULT_MEMO_LIMIT = 30;
|
||||||
|
@ -2,11 +2,10 @@ import { Option, Select } from "@mui/joy";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAppSelector } from "../store";
|
import { useGlobalStore, useUserStore } from "../store/module";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { globalService, userService } from "../services";
|
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import AppearanceSelect from "../components/AppearanceSelect";
|
import AppearanceSelect from "../components/AppearanceSelect";
|
||||||
@ -22,14 +21,16 @@ const validateConfig: ValidatorConfig = {
|
|||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const systemStatus = useAppSelector((state) => state.global.systemStatus);
|
const globalStore = useGlobalStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const systemStatus = globalStore.state.systemStatus;
|
||||||
const actionBtnLoadingState = useLoading(false);
|
const actionBtnLoadingState = useLoading(false);
|
||||||
const mode = systemStatus.profile.mode;
|
const mode = systemStatus.profile.mode;
|
||||||
const [username, setUsername] = useState(mode === "dev" ? "demohero" : "");
|
const [username, setUsername] = useState(mode === "dev" ? "demohero" : "");
|
||||||
const [password, setPassword] = useState(mode === "dev" ? "secret" : "");
|
const [password, setPassword] = useState(mode === "dev" ? "secret" : "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
userService.doSignOut().catch();
|
userStore.doSignOut().catch();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -62,7 +63,7 @@ const Auth = () => {
|
|||||||
try {
|
try {
|
||||||
actionBtnLoadingState.setLoading();
|
actionBtnLoadingState.setLoading();
|
||||||
await api.signin(username, password);
|
await api.signin(username, password);
|
||||||
const user = await userService.doSignIn();
|
const user = await userStore.doSignIn();
|
||||||
if (user) {
|
if (user) {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
} else {
|
} else {
|
||||||
@ -95,7 +96,7 @@ const Auth = () => {
|
|||||||
try {
|
try {
|
||||||
actionBtnLoadingState.setLoading();
|
actionBtnLoadingState.setLoading();
|
||||||
await api.signup(username, password, role);
|
await api.signup(username, password, role);
|
||||||
const user = await userService.doSignIn();
|
const user = await userStore.doSignIn();
|
||||||
if (user) {
|
if (user) {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
} else {
|
} else {
|
||||||
@ -109,7 +110,7 @@ const Auth = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLocaleItemClick = (locale: Locale) => {
|
const handleLocaleItemClick = (locale: Locale) => {
|
||||||
globalService.setLocale(locale);
|
globalStore.setLocale(locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,9 +2,8 @@ import dayjs from "dayjs";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { memoService } from "../services";
|
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
import { DEFAULT_MEMO_LIMIT } from "../helpers/consts";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import MemoContent from "../components/MemoContent";
|
import MemoContent from "../components/MemoContent";
|
||||||
@ -17,8 +16,11 @@ interface State {
|
|||||||
|
|
||||||
const Explore = () => {
|
const Explore = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const locationStore = useLocationStore();
|
||||||
const location = useAppSelector((state) => state.location);
|
const userStore = useUserStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
|
const user = userStore.state.user;
|
||||||
|
const location = locationStore.state;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
memos: [],
|
memos: [],
|
||||||
});
|
});
|
||||||
@ -26,7 +28,7 @@ const Explore = () => {
|
|||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length).then((memos) => {
|
memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length).then((memos) => {
|
||||||
if (memos.length < DEFAULT_MEMO_LIMIT) {
|
if (memos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
setIsComplete(true);
|
setIsComplete(true);
|
||||||
}
|
}
|
||||||
@ -39,7 +41,7 @@ const Explore = () => {
|
|||||||
|
|
||||||
const handleFetchMoreClick = async () => {
|
const handleFetchMoreClick = async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedMemos = await memoService.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length);
|
const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length);
|
||||||
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
setIsComplete(true);
|
setIsComplete(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { globalService, userService } from "../services";
|
import { useGlobalStore, useUserStore } from "../store/module";
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import Sidebar from "../components/Sidebar";
|
import Sidebar from "../components/Sidebar";
|
||||||
import MemosHeader from "../components/MemosHeader";
|
import MemosHeader from "../components/MemosHeader";
|
||||||
@ -15,12 +14,14 @@ import "../less/home.less";
|
|||||||
function Home() {
|
function Home() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const globalStore = useGlobalStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { owner } = userService.getState();
|
const { owner } = userStore.getState();
|
||||||
|
|
||||||
if (userService.isVisitorMode()) {
|
if (userStore.isVisitorMode()) {
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
toastHelper.error(t("message.user-not-found"));
|
toastHelper.error(t("message.user-not-found"));
|
||||||
}
|
}
|
||||||
@ -29,7 +30,7 @@ function Home() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.setting.locale) {
|
if (user?.setting.locale) {
|
||||||
globalService.setLocale(user.setting.locale);
|
globalStore.setLocale(user.setting.locale);
|
||||||
}
|
}
|
||||||
}, [user?.setting.locale]);
|
}, [user?.setting.locale]);
|
||||||
|
|
||||||
@ -43,11 +44,11 @@ function Home() {
|
|||||||
<main className="memos-wrapper">
|
<main className="memos-wrapper">
|
||||||
<MemosHeader />
|
<MemosHeader />
|
||||||
<div className="memos-editor-wrapper">
|
<div className="memos-editor-wrapper">
|
||||||
{!userService.isVisitorMode() && <MemoEditor />}
|
{!userStore.isVisitorMode() && <MemoEditor />}
|
||||||
<MemoFilter />
|
<MemoFilter />
|
||||||
</div>
|
</div>
|
||||||
<MemoList />
|
<MemoList />
|
||||||
{userService.isVisitorMode() && (
|
{userStore.isVisitorMode() && (
|
||||||
<div className="addition-btn-container">
|
<div className="addition-btn-container">
|
||||||
{user ? (
|
{user ? (
|
||||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||||
|
@ -2,9 +2,8 @@ import dayjs from "dayjs";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { memoService } from "../services";
|
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { useAppSelector } from "../store";
|
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import MemoContent from "../components/MemoContent";
|
import MemoContent from "../components/MemoContent";
|
||||||
@ -18,8 +17,11 @@ interface State {
|
|||||||
const MemoDetail = () => {
|
const MemoDetail = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const locationStore = useLocationStore();
|
||||||
const location = useAppSelector((state) => state.location);
|
const memoStore = useMemoStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const user = userStore.state.user;
|
||||||
|
const location = locationStore.state;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
memo: {
|
memo: {
|
||||||
id: UNKNOWN_ID,
|
id: UNKNOWN_ID,
|
||||||
@ -30,7 +32,7 @@ const MemoDetail = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const memoId = Number(params.memoId);
|
const memoId = Number(params.memoId);
|
||||||
if (memoId && !isNaN(memoId)) {
|
if (memoId && !isNaN(memoId)) {
|
||||||
memoService
|
memoStore
|
||||||
.fetchMemoById(memoId)
|
.fetchMemoById(memoId)
|
||||||
.then((memo) => {
|
.then((memo) => {
|
||||||
setState({
|
setState({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { createBrowserRouter, redirect } from "react-router-dom";
|
import { createBrowserRouter, redirect } from "react-router-dom";
|
||||||
import { lazy } from "react";
|
import { lazy } from "react";
|
||||||
import { isNullorUndefined } from "../helpers/utils";
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import { globalService, userService } from "../services";
|
import store from "../store";
|
||||||
|
import { initialGlobalState, initialUserState } from "../store/module";
|
||||||
|
|
||||||
const Auth = lazy(() => import("../pages/Auth"));
|
const Auth = lazy(() => import("../pages/Auth"));
|
||||||
const Explore = lazy(() => import("../pages/Explore"));
|
const Explore = lazy(() => import("../pages/Explore"));
|
||||||
@ -14,7 +15,7 @@ const router = createBrowserRouter([
|
|||||||
element: <Auth />,
|
element: <Auth />,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
try {
|
try {
|
||||||
await globalService.initialState();
|
await initialGlobalState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
@ -26,13 +27,13 @@ const router = createBrowserRouter([
|
|||||||
element: <Home />,
|
element: <Home />,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
try {
|
try {
|
||||||
await globalService.initialState();
|
await initialGlobalState();
|
||||||
await userService.initialState();
|
await initialUserState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host, user } = userService.getState();
|
const { host, user } = store.getState().user;
|
||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
} else if (isNullorUndefined(user)) {
|
} else if (isNullorUndefined(user)) {
|
||||||
@ -46,13 +47,13 @@ const router = createBrowserRouter([
|
|||||||
element: <Home />,
|
element: <Home />,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
try {
|
try {
|
||||||
await globalService.initialState();
|
await initialGlobalState();
|
||||||
await userService.initialState();
|
await initialUserState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host } = userService.getState();
|
const { host } = store.getState().user;
|
||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
@ -64,13 +65,13 @@ const router = createBrowserRouter([
|
|||||||
element: <Explore />,
|
element: <Explore />,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
try {
|
try {
|
||||||
await globalService.initialState();
|
await initialGlobalState();
|
||||||
await userService.initialState();
|
await initialUserState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host } = userService.getState();
|
const { host } = store.getState().user;
|
||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
@ -82,13 +83,13 @@ const router = createBrowserRouter([
|
|||||||
element: <MemoDetail />,
|
element: <MemoDetail />,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
try {
|
try {
|
||||||
await globalService.initialState();
|
await initialGlobalState();
|
||||||
await userService.initialState();
|
await initialUserState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host } = userService.getState();
|
const { host } = store.getState().user;
|
||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
# Services
|
|
||||||
|
|
||||||
What should service do?
|
|
||||||
|
|
||||||
- request data api and throw error;
|
|
||||||
- dispatch state actions;
|
|
@ -1,30 +0,0 @@
|
|||||||
import store from "../store";
|
|
||||||
import { setEditMemoId, setMemoVisibility, setResourceList } from "../store/modules/editor";
|
|
||||||
|
|
||||||
const editorStateService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().editor;
|
|
||||||
},
|
|
||||||
|
|
||||||
setEditMemoWithId: (editMemoId: MemoId) => {
|
|
||||||
store.dispatch(setEditMemoId(editMemoId));
|
|
||||||
},
|
|
||||||
|
|
||||||
clearEditMemo: () => {
|
|
||||||
store.dispatch(setEditMemoId());
|
|
||||||
},
|
|
||||||
|
|
||||||
setMemoVisibility: (memoVisibility: Visibility) => {
|
|
||||||
store.dispatch(setMemoVisibility(memoVisibility));
|
|
||||||
},
|
|
||||||
|
|
||||||
setResourceList: (resourceList: Resource[]) => {
|
|
||||||
store.dispatch(setResourceList(resourceList));
|
|
||||||
},
|
|
||||||
|
|
||||||
clearResourceList: () => {
|
|
||||||
store.dispatch(setResourceList([]));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default editorStateService;
|
|
@ -1,51 +0,0 @@
|
|||||||
import store from "../store";
|
|
||||||
import * as api from "../helpers/api";
|
|
||||||
import * as storage from "../helpers/storage";
|
|
||||||
import { setAppearance, setGlobalState, setLocale } from "../store/modules/global";
|
|
||||||
|
|
||||||
const globalService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().global;
|
|
||||||
},
|
|
||||||
|
|
||||||
initialState: async () => {
|
|
||||||
const defaultGlobalState = {
|
|
||||||
locale: "en" as Locale,
|
|
||||||
appearance: "system" as Appearance,
|
|
||||||
systemStatus: {
|
|
||||||
allowSignUp: false,
|
|
||||||
additionalStyle: "",
|
|
||||||
additionalScript: "",
|
|
||||||
} as SystemStatus,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { locale: storageLocale, appearance: storageAppearance } = storage.get(["locale", "appearance"]);
|
|
||||||
if (storageLocale) {
|
|
||||||
defaultGlobalState.locale = storageLocale;
|
|
||||||
}
|
|
||||||
if (storageAppearance) {
|
|
||||||
defaultGlobalState.appearance = storageAppearance;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = (await api.getSystemStatus()).data;
|
|
||||||
if (data) {
|
|
||||||
defaultGlobalState.systemStatus = data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
|
|
||||||
store.dispatch(setGlobalState(defaultGlobalState));
|
|
||||||
},
|
|
||||||
|
|
||||||
setLocale: (locale: Locale) => {
|
|
||||||
store.dispatch(setLocale(locale));
|
|
||||||
},
|
|
||||||
|
|
||||||
setAppearance: (appearance: Appearance) => {
|
|
||||||
store.dispatch(setAppearance(appearance));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default globalService;
|
|
@ -1,9 +0,0 @@
|
|||||||
import globalService from "./globalService";
|
|
||||||
import editorStateService from "./editorStateService";
|
|
||||||
import locationService from "./locationService";
|
|
||||||
import memoService from "./memoService";
|
|
||||||
import shortcutService from "./shortcutService";
|
|
||||||
import userService from "./userService";
|
|
||||||
import resourceService from "./resourceService";
|
|
||||||
|
|
||||||
export { globalService, editorStateService, locationService, memoService, shortcutService, userService, resourceService };
|
|
@ -1,131 +0,0 @@
|
|||||||
import { stringify } from "qs";
|
|
||||||
import store from "../store";
|
|
||||||
import { setQuery, setPathname, Query, updateStateWithLocation, updatePathnameStateWithLocation } from "../store/modules/location";
|
|
||||||
|
|
||||||
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
|
||||||
// avoid pathname confusion when entering from non-home page
|
|
||||||
store.dispatch(updatePathnameStateWithLocation());
|
|
||||||
|
|
||||||
const { query, pathname, hash } = store.getState().location;
|
|
||||||
let queryString = stringify(query);
|
|
||||||
if (queryString) {
|
|
||||||
queryString = "?" + queryString;
|
|
||||||
} else {
|
|
||||||
queryString = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method === "replace") {
|
|
||||||
window.history.replaceState(null, "", pathname + hash + queryString);
|
|
||||||
} else {
|
|
||||||
window.history.pushState(null, "", pathname + hash + queryString);
|
|
||||||
}
|
|
||||||
store.dispatch(updateStateWithLocation());
|
|
||||||
};
|
|
||||||
|
|
||||||
const locationService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().location;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateStateWithLocation: () => {
|
|
||||||
store.dispatch(updateStateWithLocation());
|
|
||||||
},
|
|
||||||
|
|
||||||
setPathname: (pathname: string) => {
|
|
||||||
store.dispatch(setPathname(pathname));
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
pushHistory: (pathname: string) => {
|
|
||||||
store.dispatch(setPathname(pathname));
|
|
||||||
updateLocationUrl("push");
|
|
||||||
},
|
|
||||||
|
|
||||||
replaceHistory: (pathname: string) => {
|
|
||||||
store.dispatch(setPathname(pathname));
|
|
||||||
updateLocationUrl("replace");
|
|
||||||
},
|
|
||||||
|
|
||||||
setQuery: (query: Query) => {
|
|
||||||
store.dispatch(setQuery(query));
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
clearQuery: () => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
tag: undefined,
|
|
||||||
type: undefined,
|
|
||||||
duration: undefined,
|
|
||||||
text: undefined,
|
|
||||||
shortcutId: undefined,
|
|
||||||
visibility: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setMemoTypeQuery: (type?: MemoSpecType) => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
type: type,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setMemoShortcut: (shortcutId?: ShortcutId) => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
shortcutId: shortcutId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setTextQuery: (text?: string) => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
text: text,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setTagQuery: (tag?: string) => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
tag: tag,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setFromAndToQuery: (from?: number, to?: number) => {
|
|
||||||
let duration = undefined;
|
|
||||||
if (from && to && from < to) {
|
|
||||||
duration = {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
duration,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
|
|
||||||
setMemoVisibilityQuery: (visibility?: Visibility) => {
|
|
||||||
store.dispatch(
|
|
||||||
setQuery({
|
|
||||||
visibility: visibility,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
updateLocationUrl();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default locationService;
|
|
@ -1,143 +0,0 @@
|
|||||||
import { uniqBy } from "lodash";
|
|
||||||
import * as api from "../helpers/api";
|
|
||||||
import { createMemo, deleteMemo, patchMemo, setIsFetching, setMemos, setTags } from "../store/modules/memo";
|
|
||||||
import store from "../store";
|
|
||||||
import userService from "./userService";
|
|
||||||
|
|
||||||
export const DEFAULT_MEMO_LIMIT = 30;
|
|
||||||
|
|
||||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
|
||||||
return {
|
|
||||||
...memo,
|
|
||||||
createdTs: memo.createdTs * 1000,
|
|
||||||
updatedTs: memo.updatedTs * 1000,
|
|
||||||
displayTs: memo.displayTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const memoService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().memo;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchMemos: async (limit = DEFAULT_MEMO_LIMIT, offset = 0) => {
|
|
||||||
store.dispatch(setIsFetching(true));
|
|
||||||
const memoFind: MemoFind = {
|
|
||||||
rowStatus: "NORMAL",
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
};
|
|
||||||
if (userService.isVisitorMode()) {
|
|
||||||
memoFind.creatorId = userService.getUserIdFromPath();
|
|
||||||
}
|
|
||||||
const { data } = (await api.getMemoList(memoFind)).data;
|
|
||||||
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
|
|
||||||
if (offset === 0) {
|
|
||||||
store.dispatch(setMemos([]));
|
|
||||||
}
|
|
||||||
const memos = memoService.getState().memos;
|
|
||||||
store.dispatch(setMemos(uniqBy(memos.concat(fetchedMemos), "id")));
|
|
||||||
store.dispatch(setIsFetching(false));
|
|
||||||
|
|
||||||
return fetchedMemos;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchAllMemos: async (limit = DEFAULT_MEMO_LIMIT, offset?: number) => {
|
|
||||||
const memoFind: MemoFind = {
|
|
||||||
rowStatus: "NORMAL",
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = (await api.getAllMemos(memoFind)).data;
|
|
||||||
const memos = data.map((m) => convertResponseModelMemo(m));
|
|
||||||
return memos;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchArchivedMemos: async () => {
|
|
||||||
const memoFind: MemoFind = {
|
|
||||||
rowStatus: "ARCHIVED",
|
|
||||||
};
|
|
||||||
if (userService.isVisitorMode()) {
|
|
||||||
memoFind.creatorId = userService.getUserIdFromPath();
|
|
||||||
}
|
|
||||||
const { data } = (await api.getMemoList(memoFind)).data;
|
|
||||||
const archivedMemos = data.map((m) => {
|
|
||||||
return convertResponseModelMemo(m);
|
|
||||||
});
|
|
||||||
return archivedMemos;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchMemoById: async (memoId: MemoId) => {
|
|
||||||
const { data } = (await api.getMemoById(memoId)).data;
|
|
||||||
const memo = convertResponseModelMemo(data);
|
|
||||||
|
|
||||||
return memo;
|
|
||||||
},
|
|
||||||
|
|
||||||
getMemoById: async (memoId: MemoId) => {
|
|
||||||
for (const m of memoService.getState().memos) {
|
|
||||||
if (m.id === memoId) {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await memoService.fetchMemoById(memoId);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTagsState: async () => {
|
|
||||||
const tagFind: TagFind = {};
|
|
||||||
if (userService.isVisitorMode()) {
|
|
||||||
tagFind.creatorId = userService.getUserIdFromPath();
|
|
||||||
}
|
|
||||||
const { data } = (await api.getTagList(tagFind)).data;
|
|
||||||
store.dispatch(setTags(data));
|
|
||||||
},
|
|
||||||
|
|
||||||
getLinkedMemos: async (memoId: MemoId): Promise<Memo[]> => {
|
|
||||||
const { memos } = memoService.getState();
|
|
||||||
const regex = new RegExp(`[@(.+?)](${memoId})`);
|
|
||||||
return memos.filter((m) => m.content.match(regex));
|
|
||||||
},
|
|
||||||
|
|
||||||
createMemo: async (memoCreate: MemoCreate) => {
|
|
||||||
const { data } = (await api.createMemo(memoCreate)).data;
|
|
||||||
const memo = convertResponseModelMemo(data);
|
|
||||||
store.dispatch(createMemo(memo));
|
|
||||||
return memo;
|
|
||||||
},
|
|
||||||
|
|
||||||
patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => {
|
|
||||||
const { data } = (await api.patchMemo(memoPatch)).data;
|
|
||||||
const memo = convertResponseModelMemo(data);
|
|
||||||
store.dispatch(patchMemo(memo));
|
|
||||||
return memo;
|
|
||||||
},
|
|
||||||
|
|
||||||
pinMemo: async (memoId: MemoId) => {
|
|
||||||
await api.pinMemo(memoId);
|
|
||||||
store.dispatch(
|
|
||||||
patchMemo({
|
|
||||||
id: memoId,
|
|
||||||
pinned: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
unpinMemo: async (memoId: MemoId) => {
|
|
||||||
await api.unpinMemo(memoId);
|
|
||||||
store.dispatch(
|
|
||||||
patchMemo({
|
|
||||||
id: memoId,
|
|
||||||
pinned: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMemoById: async (memoId: MemoId) => {
|
|
||||||
await api.deleteMemo(memoId);
|
|
||||||
store.dispatch(deleteMemo(memoId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memoService;
|
|
@ -1,54 +0,0 @@
|
|||||||
import * as api from "../helpers/api";
|
|
||||||
import store from "../store";
|
|
||||||
import { patchResource, setResources, deleteResource } from "../store/modules/resource";
|
|
||||||
|
|
||||||
const convertResponseModelResource = (resource: Resource): Resource => {
|
|
||||||
return {
|
|
||||||
...resource,
|
|
||||||
createdTs: resource.createdTs * 1000,
|
|
||||||
updatedTs: resource.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourceService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().resource;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchResourceList(): Promise<Resource[]> {
|
|
||||||
const { data } = (await api.getResourceList()).data;
|
|
||||||
const resourceList = data.map((m) => convertResponseModelResource(m));
|
|
||||||
store.dispatch(setResources(resourceList));
|
|
||||||
return resourceList;
|
|
||||||
},
|
|
||||||
|
|
||||||
async upload(file: File): Promise<Resource> {
|
|
||||||
const { name: filename, size } = file;
|
|
||||||
|
|
||||||
if (size > 64 << 20) {
|
|
||||||
return Promise.reject("overload max size: 8MB");
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file, filename);
|
|
||||||
const { data } = (await api.uploadFile(formData)).data;
|
|
||||||
const resource = convertResponseModelResource(data);
|
|
||||||
const resourceList = resourceService.getState().resources;
|
|
||||||
store.dispatch(setResources([resource, ...resourceList]));
|
|
||||||
return resource;
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteResourceById(id: ResourceId) {
|
|
||||||
await api.deleteResourceById(id);
|
|
||||||
store.dispatch(deleteResource(id));
|
|
||||||
},
|
|
||||||
|
|
||||||
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
|
|
||||||
const { data } = (await api.patchResource(resourcePatch)).data;
|
|
||||||
const resource = convertResponseModelResource(data);
|
|
||||||
store.dispatch(patchResource(resource));
|
|
||||||
return resource;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default resourceService;
|
|
@ -1,52 +0,0 @@
|
|||||||
import * as api from "../helpers/api";
|
|
||||||
import store from "../store/";
|
|
||||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
|
||||||
|
|
||||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
|
||||||
return {
|
|
||||||
...shortcut,
|
|
||||||
createdTs: shortcut.createdTs * 1000,
|
|
||||||
updatedTs: shortcut.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const shortcutService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().shortcut;
|
|
||||||
},
|
|
||||||
|
|
||||||
getMyAllShortcuts: async () => {
|
|
||||||
const { data } = (await api.getShortcutList()).data;
|
|
||||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
|
||||||
store.dispatch(setShortcuts(shortcuts));
|
|
||||||
},
|
|
||||||
|
|
||||||
getShortcutById: (id: ShortcutId) => {
|
|
||||||
for (const s of shortcutService.getState().shortcuts) {
|
|
||||||
if (s.id === id) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
createShortcut: async (shortcutCreate: ShortcutCreate) => {
|
|
||||||
const { data } = (await api.createShortcut(shortcutCreate)).data;
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(createShortcut(shortcut));
|
|
||||||
},
|
|
||||||
|
|
||||||
patchShortcut: async (shortcutPatch: ShortcutPatch) => {
|
|
||||||
const { data } = (await api.patchShortcut(shortcutPatch)).data;
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(patchShortcut(shortcut));
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteShortcutById: async (shortcutId: ShortcutId) => {
|
|
||||||
await api.deleteShortcutById(shortcutId);
|
|
||||||
store.dispatch(deleteShortcut(shortcutId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default shortcutService;
|
|
@ -1,148 +0,0 @@
|
|||||||
import { globalService, locationService } from ".";
|
|
||||||
import * as api from "../helpers/api";
|
|
||||||
import * as storage from "../helpers/storage";
|
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
|
||||||
import store from "../store";
|
|
||||||
import { setUser, patchUser, setHost, setOwner } from "../store/modules/user";
|
|
||||||
import { getSystemColorScheme } from "../helpers/utils";
|
|
||||||
|
|
||||||
const defaultSetting: Setting = {
|
|
||||||
locale: "en",
|
|
||||||
appearance: getSystemColorScheme(),
|
|
||||||
memoVisibility: "PRIVATE",
|
|
||||||
memoDisplayTsOption: "created_ts",
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultLocalSetting: LocalSetting = {
|
|
||||||
enableFoldMemo: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convertResponseModelUser = (user: User): User => {
|
|
||||||
const setting: Setting = {
|
|
||||||
...defaultSetting,
|
|
||||||
};
|
|
||||||
const { localSetting: storageLocalSetting } = storage.get(["localSetting"]);
|
|
||||||
const localSetting: LocalSetting = {
|
|
||||||
...defaultLocalSetting,
|
|
||||||
...storageLocalSetting,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (user.userSettingList) {
|
|
||||||
for (const userSetting of user.userSettingList) {
|
|
||||||
(setting as any)[userSetting.key] = JSON.parse(userSetting.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...user,
|
|
||||||
setting,
|
|
||||||
localSetting,
|
|
||||||
createdTs: user.createdTs * 1000,
|
|
||||||
updatedTs: user.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const userService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().user;
|
|
||||||
},
|
|
||||||
|
|
||||||
initialState: async () => {
|
|
||||||
const { systemStatus } = globalService.getState();
|
|
||||||
if (systemStatus.host) {
|
|
||||||
store.dispatch(setHost(convertResponseModelUser(systemStatus.host)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ownerUserId = userService.getUserIdFromPath();
|
|
||||||
if (ownerUserId) {
|
|
||||||
const { data: owner } = (await api.getUserById(ownerUserId)).data;
|
|
||||||
if (owner) {
|
|
||||||
store.dispatch(setOwner(convertResponseModelUser(owner)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = (await api.getMyselfUser()).data;
|
|
||||||
if (data) {
|
|
||||||
const user = convertResponseModelUser(data);
|
|
||||||
store.dispatch(setUser(user));
|
|
||||||
if (user.setting.locale) {
|
|
||||||
globalService.setLocale(user.setting.locale);
|
|
||||||
}
|
|
||||||
if (user.setting.appearance) {
|
|
||||||
globalService.setAppearance(user.setting.appearance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentUserId: () => {
|
|
||||||
if (userService.isVisitorMode()) {
|
|
||||||
return userService.getUserIdFromPath() || UNKNOWN_ID;
|
|
||||||
} else {
|
|
||||||
return userService.getState().user?.id || UNKNOWN_ID;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isVisitorMode: () => {
|
|
||||||
return !(userService.getUserIdFromPath() === undefined);
|
|
||||||
},
|
|
||||||
|
|
||||||
getUserIdFromPath: () => {
|
|
||||||
const userIdRegex = /^\/u\/(\d+).*/;
|
|
||||||
const result = locationService.getState().pathname.match(userIdRegex);
|
|
||||||
if (result && result.length === 2) {
|
|
||||||
return Number(result[1]);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
doSignIn: async () => {
|
|
||||||
const { data: user } = (await api.getMyselfUser()).data;
|
|
||||||
if (user) {
|
|
||||||
store.dispatch(setUser(convertResponseModelUser(user)));
|
|
||||||
} else {
|
|
||||||
userService.doSignOut();
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
},
|
|
||||||
|
|
||||||
doSignOut: async () => {
|
|
||||||
store.dispatch(setUser());
|
|
||||||
await api.signout();
|
|
||||||
},
|
|
||||||
|
|
||||||
getUserById: async (userId: UserId) => {
|
|
||||||
const { data: user } = (await api.getUserById(userId)).data;
|
|
||||||
if (user) {
|
|
||||||
return convertResponseModelUser(user);
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
upsertUserSetting: async (key: keyof Setting, value: any) => {
|
|
||||||
await api.upsertUserSetting({
|
|
||||||
key: key as any,
|
|
||||||
value: JSON.stringify(value),
|
|
||||||
});
|
|
||||||
await userService.doSignIn();
|
|
||||||
},
|
|
||||||
|
|
||||||
upsertLocalSetting: async (key: keyof LocalSetting, value: any) => {
|
|
||||||
storage.set({ localSetting: { [key]: value } });
|
|
||||||
store.dispatch(patchUser({ localSetting: { [key]: value } }));
|
|
||||||
},
|
|
||||||
|
|
||||||
patchUser: async (userPatch: UserPatch): Promise<void> => {
|
|
||||||
const { data } = (await api.patchUser(userPatch)).data;
|
|
||||||
if (userPatch.id === store.getState().user.user?.id) {
|
|
||||||
const user = convertResponseModelUser(data);
|
|
||||||
store.dispatch(patchUser(user));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteUser: async (userDelete: UserDelete) => {
|
|
||||||
await api.deleteUser(userDelete);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default userService;
|
|
@ -1,12 +1,12 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
import globalReducer from "./modules/global";
|
import globalReducer from "./reducer/global";
|
||||||
import userReducer from "./modules/user";
|
import userReducer from "./reducer/user";
|
||||||
import memoReducer from "./modules/memo";
|
import memoReducer from "./reducer/memo";
|
||||||
import editorReducer from "./modules/editor";
|
import editorReducer from "./reducer/editor";
|
||||||
import shortcutReducer from "./modules/shortcut";
|
import shortcutReducer from "./reducer/shortcut";
|
||||||
import locationReducer from "./modules/location";
|
import locationReducer from "./reducer/location";
|
||||||
import resourceReducer from "./modules/resource";
|
import resourceReducer from "./reducer/resource";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
|
28
web/src/store/module/editor.ts
Normal file
28
web/src/store/module/editor.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import store, { useAppSelector } from "..";
|
||||||
|
import { setEditMemoId, setMemoVisibility, setResourceList } from "../reducer/editor";
|
||||||
|
|
||||||
|
export const useEditorStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.editor);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().editor;
|
||||||
|
},
|
||||||
|
setEditMemoWithId: (editMemoId: MemoId) => {
|
||||||
|
store.dispatch(setEditMemoId(editMemoId));
|
||||||
|
},
|
||||||
|
clearEditMemo: () => {
|
||||||
|
store.dispatch(setEditMemoId());
|
||||||
|
},
|
||||||
|
setMemoVisibility: (memoVisibility: Visibility) => {
|
||||||
|
store.dispatch(setMemoVisibility(memoVisibility));
|
||||||
|
},
|
||||||
|
setResourceList: (resourceList: Resource[]) => {
|
||||||
|
store.dispatch(setResourceList(resourceList));
|
||||||
|
},
|
||||||
|
clearResourceList: () => {
|
||||||
|
store.dispatch(setResourceList([]));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
52
web/src/store/module/global.ts
Normal file
52
web/src/store/module/global.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import * as api from "../../helpers/api";
|
||||||
|
import * as storage from "../../helpers/storage";
|
||||||
|
import store, { useAppSelector } from "../";
|
||||||
|
import { setAppearance, setGlobalState, setLocale } from "../reducer/global";
|
||||||
|
|
||||||
|
export const initialGlobalState = async () => {
|
||||||
|
const defaultGlobalState = {
|
||||||
|
locale: "en" as Locale,
|
||||||
|
appearance: "system" as Appearance,
|
||||||
|
systemStatus: {
|
||||||
|
allowSignUp: false,
|
||||||
|
additionalStyle: "",
|
||||||
|
additionalScript: "",
|
||||||
|
} as SystemStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { locale: storageLocale, appearance: storageAppearance } = storage.get(["locale", "appearance"]);
|
||||||
|
if (storageLocale) {
|
||||||
|
defaultGlobalState.locale = storageLocale;
|
||||||
|
}
|
||||||
|
if (storageAppearance) {
|
||||||
|
defaultGlobalState.appearance = storageAppearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = (await api.getSystemStatus()).data;
|
||||||
|
if (data) {
|
||||||
|
defaultGlobalState.systemStatus = data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// do nth
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch(setGlobalState(defaultGlobalState));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.global);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().global;
|
||||||
|
},
|
||||||
|
setLocale: (locale: Locale) => {
|
||||||
|
store.dispatch(setLocale(locale));
|
||||||
|
},
|
||||||
|
setAppearance: (appearance: Appearance) => {
|
||||||
|
store.dispatch(setAppearance(appearance));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
7
web/src/store/module/index.ts
Normal file
7
web/src/store/module/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./editor";
|
||||||
|
export * from "./global";
|
||||||
|
export * from "./location";
|
||||||
|
export * from "./memo";
|
||||||
|
export * from "./resource";
|
||||||
|
export * from "./shortcut";
|
||||||
|
export * from "./user";
|
122
web/src/store/module/location.ts
Normal file
122
web/src/store/module/location.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { stringify } from "qs";
|
||||||
|
import store, { useAppSelector } from "../";
|
||||||
|
import { setQuery, setPathname, Query, updateStateWithLocation, updatePathnameStateWithLocation } from "../reducer/location";
|
||||||
|
|
||||||
|
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
||||||
|
// avoid pathname confusion when entering from non-home page
|
||||||
|
store.dispatch(updatePathnameStateWithLocation());
|
||||||
|
|
||||||
|
const { query, pathname, hash } = store.getState().location;
|
||||||
|
let queryString = stringify(query);
|
||||||
|
if (queryString) {
|
||||||
|
queryString = "?" + queryString;
|
||||||
|
} else {
|
||||||
|
queryString = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === "replace") {
|
||||||
|
window.history.replaceState(null, "", pathname + hash + queryString);
|
||||||
|
} else {
|
||||||
|
window.history.pushState(null, "", pathname + hash + queryString);
|
||||||
|
}
|
||||||
|
store.dispatch(updateStateWithLocation());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLocationStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.location);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().location;
|
||||||
|
},
|
||||||
|
updateStateWithLocation: () => {
|
||||||
|
store.dispatch(updateStateWithLocation());
|
||||||
|
},
|
||||||
|
setPathname: (pathname: string) => {
|
||||||
|
store.dispatch(setPathname(pathname));
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
pushHistory: (pathname: string) => {
|
||||||
|
store.dispatch(setPathname(pathname));
|
||||||
|
updateLocationUrl("push");
|
||||||
|
},
|
||||||
|
replaceHistory: (pathname: string) => {
|
||||||
|
store.dispatch(setPathname(pathname));
|
||||||
|
updateLocationUrl("replace");
|
||||||
|
},
|
||||||
|
setQuery: (query: Query) => {
|
||||||
|
store.dispatch(setQuery(query));
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
clearQuery: () => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
tag: undefined,
|
||||||
|
type: undefined,
|
||||||
|
duration: undefined,
|
||||||
|
text: undefined,
|
||||||
|
shortcutId: undefined,
|
||||||
|
visibility: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setMemoTypeQuery: (type?: MemoSpecType) => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
type: type,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setMemoShortcut: (shortcutId?: ShortcutId) => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
shortcutId: shortcutId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setTextQuery: (text?: string) => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
text: text,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setTagQuery: (tag?: string) => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
tag: tag,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setFromAndToQuery: (from?: number, to?: number) => {
|
||||||
|
let duration = undefined;
|
||||||
|
if (from && to && from < to) {
|
||||||
|
duration = {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
duration,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
setMemoVisibilityQuery: (visibility?: Visibility) => {
|
||||||
|
store.dispatch(
|
||||||
|
setQuery({
|
||||||
|
visibility: visibility,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
updateLocationUrl();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
135
web/src/store/module/memo.ts
Normal file
135
web/src/store/module/memo.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { uniqBy } from "lodash";
|
||||||
|
import * as api from "../../helpers/api";
|
||||||
|
import { DEFAULT_MEMO_LIMIT } from "../../helpers/consts";
|
||||||
|
import { useUserStore } from "./";
|
||||||
|
import store, { useAppSelector } from "../";
|
||||||
|
import { createMemo, deleteMemo, patchMemo, setIsFetching, setMemos, setTags } from "../reducer/memo";
|
||||||
|
|
||||||
|
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||||
|
return {
|
||||||
|
...memo,
|
||||||
|
createdTs: memo.createdTs * 1000,
|
||||||
|
updatedTs: memo.updatedTs * 1000,
|
||||||
|
displayTs: memo.displayTs * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMemoStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.memo);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const fetchMemoById = async (memoId: MemoId) => {
|
||||||
|
const { data } = (await api.getMemoById(memoId)).data;
|
||||||
|
const memo = convertResponseModelMemo(data);
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().memo;
|
||||||
|
},
|
||||||
|
fetchMemos: async (limit = DEFAULT_MEMO_LIMIT, offset = 0) => {
|
||||||
|
store.dispatch(setIsFetching(true));
|
||||||
|
const memoFind: MemoFind = {
|
||||||
|
rowStatus: "NORMAL",
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
if (userStore.isVisitorMode()) {
|
||||||
|
memoFind.creatorId = userStore.getUserIdFromPath();
|
||||||
|
}
|
||||||
|
const { data } = (await api.getMemoList(memoFind)).data;
|
||||||
|
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
|
||||||
|
if (offset === 0) {
|
||||||
|
store.dispatch(setMemos([]));
|
||||||
|
}
|
||||||
|
const memos = state.memos;
|
||||||
|
store.dispatch(setMemos(uniqBy(memos.concat(fetchedMemos), "id")));
|
||||||
|
store.dispatch(setIsFetching(false));
|
||||||
|
|
||||||
|
return fetchedMemos;
|
||||||
|
},
|
||||||
|
fetchAllMemos: async (limit = DEFAULT_MEMO_LIMIT, offset?: number) => {
|
||||||
|
const memoFind: MemoFind = {
|
||||||
|
rowStatus: "NORMAL",
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = (await api.getAllMemos(memoFind)).data;
|
||||||
|
const memos = data.map((m) => convertResponseModelMemo(m));
|
||||||
|
return memos;
|
||||||
|
},
|
||||||
|
fetchArchivedMemos: async () => {
|
||||||
|
const memoFind: MemoFind = {
|
||||||
|
rowStatus: "ARCHIVED",
|
||||||
|
};
|
||||||
|
if (userStore.isVisitorMode()) {
|
||||||
|
memoFind.creatorId = userStore.getUserIdFromPath();
|
||||||
|
}
|
||||||
|
const { data } = (await api.getMemoList(memoFind)).data;
|
||||||
|
const archivedMemos = data.map((m) => {
|
||||||
|
return convertResponseModelMemo(m);
|
||||||
|
});
|
||||||
|
return archivedMemos;
|
||||||
|
},
|
||||||
|
fetchMemoById,
|
||||||
|
getMemoById: async (memoId: MemoId) => {
|
||||||
|
for (const m of state.memos) {
|
||||||
|
if (m.id === memoId) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fetchMemoById(memoId);
|
||||||
|
},
|
||||||
|
updateTagsState: async () => {
|
||||||
|
const tagFind: TagFind = {};
|
||||||
|
if (userStore.isVisitorMode()) {
|
||||||
|
tagFind.creatorId = userStore.getUserIdFromPath();
|
||||||
|
}
|
||||||
|
const { data } = (await api.getTagList(tagFind)).data;
|
||||||
|
store.dispatch(setTags(data));
|
||||||
|
},
|
||||||
|
getLinkedMemos: async (memoId: MemoId): Promise<Memo[]> => {
|
||||||
|
const regex = new RegExp(`[@(.+?)](${memoId})`);
|
||||||
|
return state.memos.filter((m) => m.content.match(regex));
|
||||||
|
},
|
||||||
|
createMemo: async (memoCreate: MemoCreate) => {
|
||||||
|
const { data } = (await api.createMemo(memoCreate)).data;
|
||||||
|
const memo = convertResponseModelMemo(data);
|
||||||
|
store.dispatch(createMemo(memo));
|
||||||
|
return memo;
|
||||||
|
},
|
||||||
|
patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => {
|
||||||
|
const { data } = (await api.patchMemo(memoPatch)).data;
|
||||||
|
const memo = convertResponseModelMemo(data);
|
||||||
|
store.dispatch(patchMemo(memo));
|
||||||
|
return memo;
|
||||||
|
},
|
||||||
|
pinMemo: async (memoId: MemoId) => {
|
||||||
|
await api.pinMemo(memoId);
|
||||||
|
store.dispatch(
|
||||||
|
patchMemo({
|
||||||
|
id: memoId,
|
||||||
|
pinned: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
unpinMemo: async (memoId: MemoId) => {
|
||||||
|
await api.unpinMemo(memoId);
|
||||||
|
store.dispatch(
|
||||||
|
patchMemo({
|
||||||
|
id: memoId,
|
||||||
|
pinned: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
deleteMemoById: async (memoId: MemoId) => {
|
||||||
|
await api.deleteMemo(memoId);
|
||||||
|
store.dispatch(deleteMemo(memoId));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
53
web/src/store/module/resource.ts
Normal file
53
web/src/store/module/resource.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import store, { useAppSelector } from "../";
|
||||||
|
import { patchResource, setResources, deleteResource } from "../reducer/resource";
|
||||||
|
import * as api from "../../helpers/api";
|
||||||
|
|
||||||
|
const convertResponseModelResource = (resource: Resource): Resource => {
|
||||||
|
return {
|
||||||
|
...resource,
|
||||||
|
createdTs: resource.createdTs * 1000,
|
||||||
|
updatedTs: resource.updatedTs * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResourceStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.resource);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().resource;
|
||||||
|
},
|
||||||
|
async fetchResourceList(): Promise<Resource[]> {
|
||||||
|
const { data } = (await api.getResourceList()).data;
|
||||||
|
const resourceList = data.map((m) => convertResponseModelResource(m));
|
||||||
|
store.dispatch(setResources(resourceList));
|
||||||
|
return resourceList;
|
||||||
|
},
|
||||||
|
async upload(file: File): Promise<Resource> {
|
||||||
|
const { name: filename, size } = file;
|
||||||
|
|
||||||
|
if (size > 64 << 20) {
|
||||||
|
return Promise.reject("overload max size: 8MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file, filename);
|
||||||
|
const { data } = (await api.uploadFile(formData)).data;
|
||||||
|
const resource = convertResponseModelResource(data);
|
||||||
|
const resourceList = state.resources;
|
||||||
|
store.dispatch(setResources([resource, ...resourceList]));
|
||||||
|
return resource;
|
||||||
|
},
|
||||||
|
async deleteResourceById(id: ResourceId) {
|
||||||
|
await api.deleteResourceById(id);
|
||||||
|
store.dispatch(deleteResource(id));
|
||||||
|
},
|
||||||
|
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
|
||||||
|
const { data } = (await api.patchResource(resourcePatch)).data;
|
||||||
|
const resource = convertResponseModelResource(data);
|
||||||
|
store.dispatch(patchResource(resource));
|
||||||
|
return resource;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
49
web/src/store/module/shortcut.ts
Normal file
49
web/src/store/module/shortcut.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import store, { useAppSelector } from "../";
|
||||||
|
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../reducer/shortcut";
|
||||||
|
import * as api from "../../helpers/api";
|
||||||
|
|
||||||
|
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
||||||
|
return {
|
||||||
|
...shortcut,
|
||||||
|
createdTs: shortcut.createdTs * 1000,
|
||||||
|
updatedTs: shortcut.updatedTs * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useShortcutStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.shortcut);
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().shortcut;
|
||||||
|
},
|
||||||
|
getMyAllShortcuts: async () => {
|
||||||
|
const { data } = (await api.getShortcutList()).data;
|
||||||
|
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||||
|
store.dispatch(setShortcuts(shortcuts));
|
||||||
|
},
|
||||||
|
getShortcutById: (id: ShortcutId) => {
|
||||||
|
for (const s of state.shortcuts) {
|
||||||
|
if (s.id === id) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
createShortcut: async (shortcutCreate: ShortcutCreate) => {
|
||||||
|
const { data } = (await api.createShortcut(shortcutCreate)).data;
|
||||||
|
const shortcut = convertResponseModelShortcut(data);
|
||||||
|
store.dispatch(createShortcut(shortcut));
|
||||||
|
},
|
||||||
|
patchShortcut: async (shortcutPatch: ShortcutPatch) => {
|
||||||
|
const { data } = (await api.patchShortcut(shortcutPatch)).data;
|
||||||
|
const shortcut = convertResponseModelShortcut(data);
|
||||||
|
store.dispatch(patchShortcut(shortcut));
|
||||||
|
},
|
||||||
|
deleteShortcutById: async (shortcutId: ShortcutId) => {
|
||||||
|
await api.deleteShortcutById(shortcutId);
|
||||||
|
store.dispatch(deleteShortcut(shortcutId));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
151
web/src/store/module/user.ts
Normal file
151
web/src/store/module/user.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import store, { useAppSelector } from "..";
|
||||||
|
import * as api from "../../helpers/api";
|
||||||
|
import * as storage from "../../helpers/storage";
|
||||||
|
import { UNKNOWN_ID } from "../../helpers/consts";
|
||||||
|
import { getSystemColorScheme } from "../../helpers/utils";
|
||||||
|
import { setAppearance, setLocale } from "../reducer/global";
|
||||||
|
import { setUser, patchUser, setHost, setOwner } from "../reducer/user";
|
||||||
|
|
||||||
|
const defaultSetting: Setting = {
|
||||||
|
locale: "en",
|
||||||
|
appearance: getSystemColorScheme(),
|
||||||
|
memoVisibility: "PRIVATE",
|
||||||
|
memoDisplayTsOption: "created_ts",
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultLocalSetting: LocalSetting = {
|
||||||
|
enableFoldMemo: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertResponseModelUser = (user: User): User => {
|
||||||
|
const setting: Setting = {
|
||||||
|
...defaultSetting,
|
||||||
|
};
|
||||||
|
const { localSetting: storageLocalSetting } = storage.get(["localSetting"]);
|
||||||
|
const localSetting: LocalSetting = {
|
||||||
|
...defaultLocalSetting,
|
||||||
|
...storageLocalSetting,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.userSettingList) {
|
||||||
|
for (const userSetting of user.userSettingList) {
|
||||||
|
(setting as any)[userSetting.key] = JSON.parse(userSetting.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
setting,
|
||||||
|
localSetting,
|
||||||
|
createdTs: user.createdTs * 1000,
|
||||||
|
updatedTs: user.updatedTs * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialUserState = async () => {
|
||||||
|
const { systemStatus } = store.getState().global;
|
||||||
|
|
||||||
|
if (systemStatus.host) {
|
||||||
|
store.dispatch(setHost(convertResponseModelUser(systemStatus.host)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerUserId = getUserIdFromPath();
|
||||||
|
if (ownerUserId) {
|
||||||
|
const { data: owner } = (await api.getUserById(ownerUserId)).data;
|
||||||
|
if (owner) {
|
||||||
|
store.dispatch(setOwner(convertResponseModelUser(owner)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = (await api.getMyselfUser()).data;
|
||||||
|
if (data) {
|
||||||
|
const user = convertResponseModelUser(data);
|
||||||
|
store.dispatch(setUser(user));
|
||||||
|
if (user.setting.locale) {
|
||||||
|
store.dispatch(setLocale(user.setting.locale));
|
||||||
|
}
|
||||||
|
if (user.setting.appearance) {
|
||||||
|
store.dispatch(setAppearance(user.setting.appearance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserIdFromPath = () => {
|
||||||
|
const { pathname } = store.getState().location;
|
||||||
|
const userIdRegex = /^\/u\/(\d+).*/;
|
||||||
|
const result = pathname.match(userIdRegex);
|
||||||
|
if (result && result.length === 2) {
|
||||||
|
return Number(result[1]);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doSignIn = async () => {
|
||||||
|
const { data: user } = (await api.getMyselfUser()).data;
|
||||||
|
if (user) {
|
||||||
|
store.dispatch(setUser(convertResponseModelUser(user)));
|
||||||
|
} else {
|
||||||
|
doSignOut();
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doSignOut = async () => {
|
||||||
|
store.dispatch(setUser());
|
||||||
|
await api.signout();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.user);
|
||||||
|
|
||||||
|
const isVisitorMode = () => {
|
||||||
|
return !(getUserIdFromPath() === undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().user;
|
||||||
|
},
|
||||||
|
isVisitorMode,
|
||||||
|
getUserIdFromPath,
|
||||||
|
doSignIn,
|
||||||
|
doSignOut,
|
||||||
|
getCurrentUserId: () => {
|
||||||
|
if (isVisitorMode()) {
|
||||||
|
return getUserIdFromPath() || UNKNOWN_ID;
|
||||||
|
} else {
|
||||||
|
return state.user?.id || UNKNOWN_ID;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getUserById: async (userId: UserId) => {
|
||||||
|
const { data: user } = (await api.getUserById(userId)).data;
|
||||||
|
if (user) {
|
||||||
|
return convertResponseModelUser(user);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsertUserSetting: async (key: keyof Setting, value: any) => {
|
||||||
|
await api.upsertUserSetting({
|
||||||
|
key: key as any,
|
||||||
|
value: JSON.stringify(value),
|
||||||
|
});
|
||||||
|
await doSignIn();
|
||||||
|
},
|
||||||
|
upsertLocalSetting: async (key: keyof LocalSetting, value: any) => {
|
||||||
|
storage.set({ localSetting: { [key]: value } });
|
||||||
|
store.dispatch(patchUser({ localSetting: { [key]: value } }));
|
||||||
|
},
|
||||||
|
patchUser: async (userPatch: UserPatch): Promise<void> => {
|
||||||
|
const { data } = (await api.patchUser(userPatch)).data;
|
||||||
|
if (userPatch.id === store.getState().user.user?.id) {
|
||||||
|
const user = convertResponseModelUser(data);
|
||||||
|
store.dispatch(patchUser(user));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteUser: async (userDelete: UserDelete) => {
|
||||||
|
await api.deleteUser(userDelete);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -1,9 +1,9 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
editMemoId?: MemoId;
|
|
||||||
memoVisibility: Visibility;
|
memoVisibility: Visibility;
|
||||||
resourceList: Resource[];
|
resourceList: Resource[];
|
||||||
|
editMemoId?: MemoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorSlice = createSlice({
|
const editorSlice = createSlice({
|
Loading…
x
Reference in New Issue
Block a user