mirror of
https://github.com/usememos/memos.git
synced 2025-03-28 00:20:13 +01: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 { useTranslation } from "react-i18next";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { globalService, locationService } from "./services";
|
||||
import { useAppSelector } from "./store";
|
||||
import router from "./router";
|
||||
import { useLocationStore, useGlobalStore } from "./store/module";
|
||||
import * as storage from "./helpers/storage";
|
||||
import { getSystemColorScheme } from "./helpers/utils";
|
||||
import Loading from "./pages/Loading";
|
||||
|
||||
function App() {
|
||||
const App = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const { appearance, locale, systemStatus } = useAppSelector((state) => state.global);
|
||||
const globalStore = useGlobalStore();
|
||||
const locationStore = useLocationStore();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
const { appearance, locale, systemStatus } = globalStore.state;
|
||||
|
||||
useEffect(() => {
|
||||
locationService.updateStateWithLocation();
|
||||
locationStore.updateStateWithLocation();
|
||||
window.onpopstate = () => {
|
||||
locationService.updateStateWithLocation();
|
||||
locationStore.updateStateWithLocation();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
||||
if (globalService.getState().appearance === "system") {
|
||||
if (globalStore.getState().appearance === "system") {
|
||||
const mode = e.matches ? "dark" : "light";
|
||||
setMode(mode);
|
||||
}
|
||||
@ -91,6 +92,6 @@ function App() {
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useGlobalStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import GitHubBadge from "./GitHubBadge";
|
||||
@ -9,7 +9,8 @@ type Props = DialogProps;
|
||||
|
||||
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const profile = useAppSelector((state) => state.global.systemStatus.profile);
|
||||
const globalStore = useGlobalStore();
|
||||
const profile = globalStore.state.systemStatus.profile;
|
||||
|
||||
const handleCloseBtnClick = () => {
|
||||
destroy();
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { Option, Select } from "@mui/joy";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { globalService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useGlobalStore, useUserStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
|
||||
const appearanceList = ["system", "light", "dark"];
|
||||
|
||||
const AppearanceSelect = () => {
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const appearance = useAppSelector((state) => state.global.appearance);
|
||||
const { t } = useTranslation();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const { appearance } = globalStore.state;
|
||||
const user = userStore.state.user;
|
||||
|
||||
const getPrefixIcon = (apperance: Appearance) => {
|
||||
const className = "w-4 h-auto";
|
||||
@ -24,9 +25,9 @@ const AppearanceSelect = () => {
|
||||
|
||||
const handleSelectChange = async (appearance: Appearance) => {
|
||||
if (user) {
|
||||
await userService.upsertUserSetting("appearance", appearance);
|
||||
await userStore.upsertUserSetting("appearance", appearance);
|
||||
}
|
||||
globalService.setAppearance(appearance);
|
||||
globalStore.setAppearance(appearance);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemoStore } from "../store/module";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { memoService } from "../services";
|
||||
import toastHelper from "./Toast";
|
||||
import MemoContent from "./MemoContent";
|
||||
import MemoResources from "./MemoResources";
|
||||
@ -14,13 +14,13 @@ interface Props {
|
||||
const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
const { memo } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const memoStore = useMemoStore();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleDeleteMemoClick = async () => {
|
||||
if (showConfirmDeleteBtn) {
|
||||
try {
|
||||
await memoService.deleteMemoById(memo.id);
|
||||
await memoStore.deleteMemoById(memo.id);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
@ -32,11 +32,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
|
||||
const handleRestoreMemoClick = async () => {
|
||||
try {
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: memo.id,
|
||||
rowStatus: "NORMAL",
|
||||
});
|
||||
await memoService.fetchMemos();
|
||||
await memoStore.fetchMemos();
|
||||
toastHelper.info(t("message.restored-successfully"));
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemoStore } from "../store/module";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { memoService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
@ -14,12 +13,13 @@ type Props = DialogProps;
|
||||
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { destroy } = props;
|
||||
const memos = useAppSelector((state) => state.memo.memos);
|
||||
const memoStore = useMemoStore();
|
||||
const memos = memoStore.state.memos;
|
||||
const loadingState = useLoading();
|
||||
const [archivedMemos, setArchivedMemos] = useState<Memo[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
memoService
|
||||
memoStore
|
||||
.fetchArchivedMemos()
|
||||
.then((result) => {
|
||||
setArchivedMemos(result);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUserStore } from "../store/module";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import { userService } from "../services";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
@ -20,6 +20,7 @@ interface Props extends DialogProps {
|
||||
const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
||||
const { user: propsUser, destroy } = props;
|
||||
const { t } = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||
|
||||
@ -60,7 +61,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await userService.patchUser({
|
||||
await userStore.patchUser({
|
||||
id: propsUser.id,
|
||||
password: newPassword,
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService } from "../services";
|
||||
import { useMemoStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
@ -14,11 +14,12 @@ interface Props extends DialogProps {
|
||||
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { destroy, memoId } = props;
|
||||
const memoStore = useMemoStore();
|
||||
const [createdAt, setCreatedAt] = useState("");
|
||||
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
||||
|
||||
useEffect(() => {
|
||||
memoService.getMemoById(memoId).then((memo) => {
|
||||
memoStore.getMemoById(memoId).then((memo) => {
|
||||
if (memo) {
|
||||
const datetime = dayjs(memo.createdTs).format("YYYY-MM-DDTHH:mm");
|
||||
setCreatedAt(datetime);
|
||||
@ -48,7 +49,7 @@ const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: memoId,
|
||||
createdTs,
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUserStore } from "../store/module";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import { userService } from "../services";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
@ -17,6 +17,7 @@ type Props = DialogProps;
|
||||
|
||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||
|
||||
@ -57,8 +58,8 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const user = userService.getState().user as User;
|
||||
await userService.patchUser({
|
||||
const user = userStore.getState().user as User;
|
||||
await userStore.patchUser({
|
||||
id: user.id,
|
||||
password: newPassword,
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { resourceService } from "../services";
|
||||
import { useResourceStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
@ -24,8 +24,9 @@ const validateFilename = (filename: string): boolean => {
|
||||
};
|
||||
|
||||
const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { destroy, resourceId, resourceFilename } = props;
|
||||
const { t } = useTranslation();
|
||||
const resourceStore = useResourceStore();
|
||||
const [filename, setFilename] = useState<string>(resourceFilename);
|
||||
|
||||
const handleFilenameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -47,7 +48,7 @@ const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await resourceService.patchResource({
|
||||
await resourceStore.patchResource({
|
||||
id: resourceId,
|
||||
filename: filename,
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import { useMemoStore, useShortcutStore } from "../store/module";
|
||||
import { filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Icon from "./Icon";
|
||||
@ -16,6 +16,7 @@ interface Props extends DialogProps {
|
||||
|
||||
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy, shortcutId } = props;
|
||||
const shortcutStore = useShortcutStore();
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
const requestState = useLoading(false);
|
||||
@ -23,7 +24,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (shortcutId) {
|
||||
const shortcutTemp = shortcutService.getShortcutById(shortcutId);
|
||||
const shortcutTemp = shortcutStore.getShortcutById(shortcutId);
|
||||
if (shortcutTemp) {
|
||||
setTitle(shortcutTemp.title);
|
||||
const temp = JSON.parse(shortcutTemp.payload);
|
||||
@ -52,13 +53,13 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
try {
|
||||
if (shortcutId) {
|
||||
await shortcutService.patchShortcut({
|
||||
await shortcutStore.patchShortcut({
|
||||
id: shortcutId,
|
||||
title,
|
||||
payload: JSON.stringify(filters),
|
||||
});
|
||||
} else {
|
||||
await shortcutService.createShortcut({
|
||||
await shortcutStore.createShortcut({
|
||||
title,
|
||||
payload: JSON.stringify(filters),
|
||||
});
|
||||
@ -161,9 +162,9 @@ interface MemoFilterInputerProps {
|
||||
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
|
||||
const { index, filter, handleFilterChange, handleFilterRemove } = props;
|
||||
const { t } = useTranslation();
|
||||
const memoStore = useMemoStore();
|
||||
const [value, setValue] = useState<string>(filter.value.value);
|
||||
|
||||
const tags = Array.from(memoService.getState().tags);
|
||||
const tags = Array.from(memoStore.getState().tags);
|
||||
const { type } = filter;
|
||||
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useMemoStore } from "../store/module";
|
||||
import toImage from "../labs/html2image";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
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 { 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 [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
||||
const memosElRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -3,7 +3,7 @@ import dayjs from "dayjs";
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 toastHelper from "./Toast";
|
||||
import MemoContent from "./MemoContent";
|
||||
@ -30,9 +30,13 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
const { memo, highlightWord } = props;
|
||||
const { t, i18n } = useTranslation();
|
||||
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 memoContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isVisitorMode = userService.isVisitorMode();
|
||||
const isVisitorMode = userStore.isVisitorMode();
|
||||
|
||||
useEffect(() => {
|
||||
let intervalFlag: any = -1;
|
||||
@ -59,9 +63,9 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
const handleTogglePinMemoBtnClick = async () => {
|
||||
try {
|
||||
if (memo.pinned) {
|
||||
await memoService.unpinMemo(memo.id);
|
||||
await memoStore.unpinMemo(memo.id);
|
||||
} else {
|
||||
await memoService.pinMemo(memo.id);
|
||||
await memoStore.pinMemo(memo.id);
|
||||
}
|
||||
} catch (error) {
|
||||
// do nth
|
||||
@ -69,12 +73,12 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleEditMemoClick = () => {
|
||||
editorStateService.setEditMemoWithId(memo.id);
|
||||
editorStore.setEditMemoWithId(memo.id);
|
||||
};
|
||||
|
||||
const handleArchiveMemoClick = async () => {
|
||||
try {
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: memo.id,
|
||||
rowStatus: "ARCHIVED",
|
||||
});
|
||||
@ -83,8 +87,8 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
toastHelper.error(error.response.data.message);
|
||||
}
|
||||
|
||||
if (editorStateService.getState().editMemoId === memo.id) {
|
||||
editorStateService.clearEditMemo();
|
||||
if (editorStore.getState().editMemoId === memo.id) {
|
||||
editorStore.clearEditMemo();
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,14 +101,14 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
|
||||
if (targetEl.className === "tag-span") {
|
||||
const tagName = targetEl.innerText.slice(1);
|
||||
const currTagQuery = locationService.getState().query?.tag;
|
||||
const currTagQuery = locationStore.getState().query?.tag;
|
||||
if (currTagQuery === tagName) {
|
||||
locationService.setTagQuery(undefined);
|
||||
locationStore.setTagQuery(undefined);
|
||||
} else {
|
||||
locationService.setTagQuery(tagName);
|
||||
locationStore.setTagQuery(tagName);
|
||||
}
|
||||
} else if (targetEl.classList.contains("todo-block")) {
|
||||
if (userService.isVisitorMode()) {
|
||||
if (userStore.isVisitorMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -128,7 +132,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
finalContent += `${tempList[i]}`;
|
||||
}
|
||||
}
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: memo.id,
|
||||
content: finalContent,
|
||||
});
|
||||
@ -151,7 +155,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
editorStateService.setEditMemoWithId(memo.id);
|
||||
editorStore.setEditMemoWithId(memo.id);
|
||||
};
|
||||
|
||||
const handleMemoDisplayTimeClick = () => {
|
||||
@ -159,11 +163,11 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleMemoVisibilityClick = (visibility: Visibility) => {
|
||||
const currVisibilityQuery = locationService.getState().query?.visibility;
|
||||
const currVisibilityQuery = locationStore.getState().query?.visibility;
|
||||
if (currVisibilityQuery === visibility) {
|
||||
locationService.setMemoVisibilityQuery(undefined);
|
||||
locationStore.setMemoVisibilityQuery(undefined);
|
||||
} else {
|
||||
locationService.setMemoVisibilityQuery(visibility);
|
||||
locationStore.setMemoVisibilityQuery(visibility);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUserStore } from "../store/module";
|
||||
import { marked } from "../labs/marked";
|
||||
import { highlightWithWord } from "../labs/highlighter";
|
||||
import Icon from "./Icon";
|
||||
import { useAppSelector } from "../store";
|
||||
import "../less/memo-content.less";
|
||||
|
||||
export interface DisplayConfig {
|
||||
@ -36,7 +36,8 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
return firstHorizontalRuleIndex !== -1 ? content.slice(0, firstHorizontalRuleIndex) : content;
|
||||
}, [content]);
|
||||
const { t } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
|
||||
const [state, setState] = useState<State>({
|
||||
expandButtonStatus: -1,
|
||||
@ -84,6 +85,9 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
setState({
|
||||
expandButtonStatus: Number(expandButtonStatus) as ExpandButtonStatus,
|
||||
});
|
||||
if (!expandButtonStatus) {
|
||||
memoContentContainerRef.current?.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -3,8 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteMemoResource, upsertMemoResource } from "../helpers/api";
|
||||
import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useEditorStore, useLocationStore, useMemoStore, useResourceStore, useUserStore } from "../store/module";
|
||||
import * as storage from "../helpers/storage";
|
||||
import Icon from "./Icon";
|
||||
import toastHelper from "./Toast";
|
||||
@ -41,19 +40,25 @@ interface State {
|
||||
|
||||
const MemoEditor = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user as User);
|
||||
const setting = user.setting;
|
||||
const editorState = useAppSelector((state) => state.editor);
|
||||
const tags = useAppSelector((state) => state.memo.tags);
|
||||
const userStore = useUserStore();
|
||||
const editorStore = useEditorStore();
|
||||
const locationStore = useLocationStore();
|
||||
const memoStore = useMemoStore();
|
||||
const resourceStore = useResourceStore();
|
||||
|
||||
const [state, setState] = useState<State>({
|
||||
isUploadingResource: false,
|
||||
fullscreen: false,
|
||||
shouldShowEmojiPicker: false,
|
||||
});
|
||||
const [allowSave, setAllowSave] = useState<boolean>(false);
|
||||
const editorState = editorStore.state;
|
||||
const prevEditorStateRef = useRef(editorState);
|
||||
const editorRef = useRef<EditorRefActions>(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) => {
|
||||
return {
|
||||
value: item.value,
|
||||
@ -64,22 +69,22 @@ const MemoEditor = () => {
|
||||
useEffect(() => {
|
||||
const { editingMemoIdCache, editingMemoVisibilityCache } = storage.get(["editingMemoIdCache", "editingMemoVisibilityCache"]);
|
||||
if (editingMemoIdCache) {
|
||||
editorStateService.setEditMemoWithId(editingMemoIdCache);
|
||||
editorStore.setEditMemoWithId(editingMemoIdCache);
|
||||
}
|
||||
if (editingMemoVisibilityCache) {
|
||||
editorStateService.setMemoVisibility(editingMemoVisibilityCache as "PUBLIC" | "PROTECTED" | "PRIVATE");
|
||||
editorStore.setMemoVisibility(editingMemoVisibilityCache as "PUBLIC" | "PROTECTED" | "PRIVATE");
|
||||
} else {
|
||||
editorStateService.setMemoVisibility(setting.memoVisibility);
|
||||
editorStore.setMemoVisibility(setting.memoVisibility);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (editorState.editMemoId) {
|
||||
memoService.getMemoById(editorState.editMemoId ?? UNKNOWN_ID).then((memo) => {
|
||||
memoStore.getMemoById(editorState.editMemoId ?? UNKNOWN_ID).then((memo) => {
|
||||
if (memo) {
|
||||
handleEditorFocus();
|
||||
editorStateService.setMemoVisibility(memo.visibility);
|
||||
editorStateService.setResourceList(memo.resourceList);
|
||||
editorStore.setMemoVisibility(memo.visibility);
|
||||
editorStore.setResourceList(memo.resourceList);
|
||||
editorRef.current?.setContent(memo.content ?? "");
|
||||
}
|
||||
});
|
||||
@ -180,8 +185,8 @@ const MemoEditor = () => {
|
||||
}
|
||||
}
|
||||
if (uploadedResourceList.length > 0) {
|
||||
const resourceList = editorStateService.getState().resourceList;
|
||||
editorStateService.setResourceList([...resourceList, ...uploadedResourceList]);
|
||||
const resourceList = editorStore.getState().resourceList;
|
||||
editorStore.setResourceList([...resourceList, ...uploadedResourceList]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -210,7 +215,7 @@ const MemoEditor = () => {
|
||||
let resource = undefined;
|
||||
|
||||
try {
|
||||
resource = await resourceService.upload(file);
|
||||
resource = await resourceStore.upload(file);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
@ -233,26 +238,26 @@ const MemoEditor = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const { editMemoId } = editorStateService.getState();
|
||||
const { editMemoId } = editorStore.getState();
|
||||
if (editMemoId && editMemoId !== UNKNOWN_ID) {
|
||||
const prevMemo = await memoService.getMemoById(editMemoId ?? UNKNOWN_ID);
|
||||
const prevMemo = await memoStore.getMemoById(editMemoId ?? UNKNOWN_ID);
|
||||
|
||||
if (prevMemo) {
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: prevMemo.id,
|
||||
content,
|
||||
visibility: editorState.memoVisibility,
|
||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
||||
});
|
||||
}
|
||||
editorStateService.clearEditMemo();
|
||||
editorStore.clearEditMemo();
|
||||
} else {
|
||||
await memoService.createMemo({
|
||||
await memoStore.createMemo({
|
||||
content,
|
||||
visibility: editorState.memoVisibility,
|
||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
||||
});
|
||||
locationService.clearQuery();
|
||||
locationStore.clearQuery();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
@ -265,7 +270,7 @@ const MemoEditor = () => {
|
||||
fullscreen: false,
|
||||
};
|
||||
});
|
||||
editorStateService.clearResourceList();
|
||||
editorStore.clearResourceList();
|
||||
setEditorContentCache("");
|
||||
storage.remove(["editingMemoVisibilityCache"]);
|
||||
editorRef.current?.setContent("");
|
||||
@ -273,8 +278,8 @@ const MemoEditor = () => {
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
if (editorState.editMemoId) {
|
||||
editorStateService.clearEditMemo();
|
||||
editorStateService.clearResourceList();
|
||||
editorStore.clearEditMemo();
|
||||
editorStore.clearResourceList();
|
||||
editorRef.current?.setContent("");
|
||||
setEditorContentCache("");
|
||||
storage.remove(["editingMemoVisibilityCache"]);
|
||||
@ -338,7 +343,7 @@ const MemoEditor = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
editorStateService.setResourceList([...editorState.resourceList, ...resourceList]);
|
||||
editorStore.setResourceList([...editorState.resourceList, ...resourceList]);
|
||||
document.body.removeChild(inputEl);
|
||||
};
|
||||
inputEl.click();
|
||||
@ -361,7 +366,7 @@ const MemoEditor = () => {
|
||||
}, []);
|
||||
|
||||
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) {
|
||||
await deleteMemoResource(editorState.editMemoId, resourceId);
|
||||
}
|
||||
@ -369,7 +374,7 @@ const MemoEditor = () => {
|
||||
|
||||
const handleMemoVisibilityOptionChanged = async (value: string) => {
|
||||
const visibilityValue = value as Visibility;
|
||||
editorStateService.setMemoVisibility(visibilityValue);
|
||||
editorStore.setMemoVisibility(visibilityValue);
|
||||
setEditingMemoVisibilityCache(visibilityValue);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import { useLocationStore, useShortcutStore } from "../store/module";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { getTextWithMemoType } from "../helpers/filter";
|
||||
import Icon from "./Icon";
|
||||
@ -8,10 +7,11 @@ import "../less/memo-filter.less";
|
||||
|
||||
const MemoFilter = () => {
|
||||
const { t } = useTranslation();
|
||||
useAppSelector((state) => state.shortcut.shortcuts);
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const locationStore = useLocationStore();
|
||||
const shortcutStore = useShortcutStore();
|
||||
const query = locationStore.state.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);
|
||||
|
||||
return (
|
||||
@ -20,7 +20,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (shortcut ? "" : "!hidden")}
|
||||
onClick={() => {
|
||||
locationService.setMemoShortcut(undefined);
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
}}
|
||||
>
|
||||
<Icon.Target className="icon-text" /> {shortcut?.title}
|
||||
@ -28,7 +28,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (tagQuery ? "" : "!hidden")}
|
||||
onClick={() => {
|
||||
locationService.setTagQuery(undefined);
|
||||
locationStore.setTagQuery(undefined);
|
||||
}}
|
||||
>
|
||||
<Icon.Tag className="icon-text" /> {tagQuery}
|
||||
@ -36,7 +36,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (memoType ? "" : "!hidden")}
|
||||
onClick={() => {
|
||||
locationService.setMemoTypeQuery(undefined);
|
||||
locationStore.setMemoTypeQuery(undefined);
|
||||
}}
|
||||
>
|
||||
<Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))}
|
||||
@ -44,7 +44,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (visibility ? "" : "!hidden")}
|
||||
onClick={() => {
|
||||
locationService.setMemoVisibilityQuery(undefined);
|
||||
locationStore.setMemoVisibilityQuery(undefined);
|
||||
}}
|
||||
>
|
||||
<Icon.Eye className="icon-text" /> {visibility}
|
||||
@ -53,7 +53,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className="filter-item-container"
|
||||
onClick={() => {
|
||||
locationService.setFromAndToQuery();
|
||||
locationStore.setFromAndToQuery();
|
||||
}}
|
||||
>
|
||||
<Icon.Calendar className="icon-text" /> {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)}
|
||||
@ -62,7 +62,7 @@ const MemoFilter = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (textQuery ? "" : "!hidden")}
|
||||
onClick={() => {
|
||||
locationService.setTextQuery(undefined);
|
||||
locationStore.setTextQuery(undefined);
|
||||
}}
|
||||
>
|
||||
<Icon.Search className="icon-text" /> {textQuery}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useMemoStore, useShortcutStore, useUserStore } from "../store/module";
|
||||
import { TAG_REG, LINK_REG } from "../labs/marked/parser";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { DEFAULT_MEMO_LIMIT } from "../helpers/consts";
|
||||
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
|
||||
import toastHelper from "./Toast";
|
||||
import Memo from "./Memo";
|
||||
@ -12,14 +11,18 @@ import "../less/memo-list.less";
|
||||
|
||||
const MemoList = () => {
|
||||
const { t } = useTranslation();
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const memoDisplayTsOption = useAppSelector((state) => state.user.user?.setting.memoDisplayTsOption);
|
||||
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
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 [highlightWord, setHighlightWord] = useState<string | undefined>("");
|
||||
|
||||
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 shownMemos =
|
||||
@ -84,7 +87,7 @@ const MemoList = () => {
|
||||
const sortedMemos = pinnedMemos.concat(unpinnedMemos).filter((m) => m.rowStatus === "NORMAL");
|
||||
|
||||
useEffect(() => {
|
||||
memoService
|
||||
memoStore
|
||||
.fetchMemos()
|
||||
.then((fetchedMemos) => {
|
||||
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||
@ -118,7 +121,7 @@ const MemoList = () => {
|
||||
|
||||
const handleFetchMoreClick = async () => {
|
||||
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) {
|
||||
setIsComplete(true);
|
||||
} else {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useMemoStore, useShortcutStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import SearchBar from "./SearchBar";
|
||||
import { toggleSidebar } from "./Sidebar";
|
||||
@ -9,8 +8,11 @@ import "../less/memos-header.less";
|
||||
let prevRequestTimestamp = Date.now();
|
||||
|
||||
const MemosHeader = () => {
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
||||
const locationStore = useLocationStore();
|
||||
const memoStore = useMemoStore();
|
||||
const shortcutStore = useShortcutStore();
|
||||
const query = locationStore.state.query;
|
||||
const shortcuts = shortcutStore.state.shortcuts;
|
||||
const [titleText, setTitleText] = useState("MEMOS");
|
||||
|
||||
useEffect(() => {
|
||||
@ -19,7 +21,7 @@ const MemosHeader = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = shortcutService.getShortcutById(query?.shortcutId);
|
||||
const shortcut = shortcutStore.getShortcutById(query?.shortcutId);
|
||||
if (shortcut) {
|
||||
setTitleText(shortcut.title);
|
||||
}
|
||||
@ -29,7 +31,7 @@ const MemosHeader = () => {
|
||||
const now = Date.now();
|
||||
if (now - prevRequestTimestamp > 1 * 1000) {
|
||||
prevRequestTimestamp = now;
|
||||
memoService.fetchMemos().catch(() => {
|
||||
memoStore.fetchMemos().catch(() => {
|
||||
// do nth
|
||||
});
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ import copy from "copy-to-clipboard";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { resourceService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useResourceStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import toastHelper from "./Toast";
|
||||
import Dropdown from "./common/Dropdown";
|
||||
@ -24,13 +23,14 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useTranslation();
|
||||
const loadingState = useLoading();
|
||||
const { resources } = useAppSelector((state) => state.resource);
|
||||
const resourceStore = useResourceStore();
|
||||
const resources = resourceStore.state.resources;
|
||||
const [state, setState] = useState<State>({
|
||||
isUploadingResource: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
resourceService
|
||||
resourceStore
|
||||
.fetchResourceList()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
@ -66,7 +66,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
|
||||
for (const file of inputEl.files) {
|
||||
try {
|
||||
await resourceService.upload(file);
|
||||
await resourceStore.upload(file);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
@ -127,7 +127,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
style: "warning",
|
||||
onConfirm: async () => {
|
||||
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,
|
||||
style: "warning",
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { editorStateService, resourceService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useEditorStore, useResourceStore } from "../store/module";
|
||||
import Icon from "./Icon";
|
||||
import toastHelper from "./Toast";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -20,14 +19,15 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useTranslation();
|
||||
const loadingState = useLoading();
|
||||
const { resources } = useAppSelector((state) => state.resource);
|
||||
const editorState = useAppSelector((state) => state.editor);
|
||||
const editorStore = useEditorStore();
|
||||
const resourceStore = useResourceStore();
|
||||
const resources = resourceStore.state.resources;
|
||||
const [state, setState] = useState<State>({
|
||||
checkedArray: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
resourceService
|
||||
resourceStore
|
||||
.fetchResourceList()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
@ -39,7 +39,7 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const checkedResourceIdArray = editorState.resourceList.map((resource) => resource.id);
|
||||
const checkedResourceIdArray = editorStore.state.resourceList.map((resource) => resource.id);
|
||||
setState({
|
||||
checkedArray: resources.map((resource) => {
|
||||
return checkedResourceIdArray.includes(resource.id);
|
||||
@ -75,7 +75,7 @@ const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
||||
const resourceList = resources.filter((_, index) => {
|
||||
return state.checkedArray[index];
|
||||
});
|
||||
editorStateService.setResourceList(resourceList);
|
||||
editorStore.setResourceList(resourceList);
|
||||
destroy();
|
||||
};
|
||||
|
||||
|
@ -1,33 +1,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore } from "../store/module";
|
||||
import { memoSpecialTypes } from "../helpers/filter";
|
||||
import Icon from "./Icon";
|
||||
import "../less/search-bar.less";
|
||||
|
||||
const SearchBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const memoType = useAppSelector((state) => state.location.query?.type);
|
||||
const locationStore = useLocationStore();
|
||||
const memoType = locationStore.state.query.type;
|
||||
const [queryText, setQueryText] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const text = locationService.getState().query.text;
|
||||
const text = locationStore.getState().query.text;
|
||||
setQueryText(text === undefined ? "" : text);
|
||||
}, [locationService.getState().query.text]);
|
||||
}, [locationStore.getState().query.text]);
|
||||
|
||||
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
||||
const { type: prevType } = locationService.getState().query ?? {};
|
||||
const { type: prevType } = locationStore.getState().query ?? {};
|
||||
if (type === prevType) {
|
||||
type = undefined;
|
||||
}
|
||||
locationService.setMemoTypeQuery(type);
|
||||
locationStore.setMemoTypeQuery(type);
|
||||
};
|
||||
|
||||
const handleTextQueryInput = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const text = event.currentTarget.value;
|
||||
setQueryText(text);
|
||||
locationService.setTextQuery(text.length === 0 ? undefined : text);
|
||||
locationStore.setTextQuery(text.length === 0 ? undefined : text);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import MyAccountSection from "./Settings/MyAccountSection";
|
||||
@ -8,6 +7,7 @@ import PreferencesSection from "./Settings/PreferencesSection";
|
||||
import MemberSection from "./Settings/MemberSection";
|
||||
import SystemSection from "./Settings/SystemSection";
|
||||
import "../less/setting-dialog.less";
|
||||
import { useUserStore } from "../store/module";
|
||||
|
||||
type Props = DialogProps;
|
||||
|
||||
@ -20,7 +20,8 @@ interface State {
|
||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
const [state, setState] = useState<State>({
|
||||
selectedSection: "my-account",
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { useUserStore } from "../../store/module";
|
||||
import * as api from "../../helpers/api";
|
||||
import toastHelper from "../Toast";
|
||||
import Dropdown from "../common/Dropdown";
|
||||
@ -16,7 +15,8 @@ interface State {
|
||||
|
||||
const PreferencesSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useAppSelector((state) => state.user.user);
|
||||
const userStore = useUserStore();
|
||||
const currentUser = userStore.state.user;
|
||||
const [state, setState] = useState<State>({
|
||||
createUserUsername: "",
|
||||
createUserPassword: "",
|
||||
@ -80,7 +80,7 @@ const PreferencesSection = () => {
|
||||
content: `❗️Are you sure to archive ${user.username}?`,
|
||||
style: "warning",
|
||||
onConfirm: async () => {
|
||||
await userService.patchUser({
|
||||
await userStore.patchUser({
|
||||
id: user.id,
|
||||
rowStatus: "ARCHIVED",
|
||||
});
|
||||
@ -90,7 +90,7 @@ const PreferencesSection = () => {
|
||||
};
|
||||
|
||||
const handleRestoreUserClick = async (user: User) => {
|
||||
await userService.patchUser({
|
||||
await userStore.patchUser({
|
||||
id: user.id,
|
||||
rowStatus: "NORMAL",
|
||||
});
|
||||
@ -103,7 +103,7 @@ const PreferencesSection = () => {
|
||||
content: `Are you sure to delete ${user.username}? THIS ACTION IS IRREVERSIABLE.❗️`,
|
||||
style: "warning",
|
||||
onConfirm: async () => {
|
||||
await userService.deleteUser({
|
||||
await userStore.deleteUser({
|
||||
id: user.id,
|
||||
});
|
||||
fetchUserList();
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { userService } from "../../services";
|
||||
import { useUserStore } from "../../store/module";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import showChangePasswordDialog from "../ChangePasswordDialog";
|
||||
import showUpdateAccountDialog from "../UpdateAccountDialog";
|
||||
@ -8,7 +7,8 @@ import "../../less/settings/my-account-section.less";
|
||||
|
||||
const MyAccountSection = () => {
|
||||
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 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?",
|
||||
style: "warning",
|
||||
onConfirm: async () => {
|
||||
await userService.patchUser({
|
||||
await userStore.patchUser({
|
||||
id: user.id,
|
||||
resetOpenId: true,
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Select, Switch, Option } from "@mui/joy";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { globalService, userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { useGlobalStore, useUserStore } from "../../store/module";
|
||||
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||
import Icon from "../Icon";
|
||||
import AppearanceSelect from "../AppearanceSelect";
|
||||
@ -44,7 +43,9 @@ const localeSelectorItems = [
|
||||
|
||||
const PreferencesSection = () => {
|
||||
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) => {
|
||||
return {
|
||||
value: item.value,
|
||||
@ -60,20 +61,20 @@ const PreferencesSection = () => {
|
||||
});
|
||||
|
||||
const handleLocaleChanged = async (value: string) => {
|
||||
await userService.upsertUserSetting("locale", value);
|
||||
globalService.setLocale(value as Locale);
|
||||
await userStore.upsertUserSetting("locale", value);
|
||||
globalStore.setLocale(value as Locale);
|
||||
};
|
||||
|
||||
const handleDefaultMemoVisibilityChanged = async (value: string) => {
|
||||
await userService.upsertUserSetting("memoVisibility", value);
|
||||
await userStore.upsertUserSetting("memoVisibility", value);
|
||||
};
|
||||
|
||||
const handleMemoDisplayTsOptionChanged = async (value: string) => {
|
||||
await userService.upsertUserSetting("memoDisplayTsOption", value);
|
||||
await userStore.upsertUserSetting("memoDisplayTsOption", value);
|
||||
};
|
||||
|
||||
const handleIsFoldingEnabledChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
userService.upsertLocalSetting("enableFoldMemo", event.target.checked);
|
||||
userStore.upsertLocalSetting("enableFoldMemo", event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { toLower } from "lodash";
|
||||
import toImage from "../labs/html2image";
|
||||
import { useMemoStore, useUserStore } from "../store/module";
|
||||
import { VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { getMemoStats } from "../helpers/api";
|
||||
import { memoService, userService } from "../services";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -29,7 +29,9 @@ interface State {
|
||||
const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
const { memo: propsMemo, destroy } = props;
|
||||
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>({
|
||||
memoAmount: 0,
|
||||
memoVisibility: propsMemo.visibility,
|
||||
@ -113,7 +115,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
...state,
|
||||
memoVisibility: visibilityValue,
|
||||
});
|
||||
await memoService.patchMemo({
|
||||
await memoStore.patchMemo({
|
||||
id: memo.id,
|
||||
visibility: visibilityValue,
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useShortcutStore } from "../store/module";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
@ -11,10 +10,12 @@ import showCreateShortcutDialog from "./CreateShortcutDialog";
|
||||
import "../less/shortcut-list.less";
|
||||
|
||||
const ShortcutList = () => {
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
||||
const loadingState = useLoading();
|
||||
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
|
||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
||||
@ -25,7 +26,7 @@ const ShortcutList = () => {
|
||||
const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts);
|
||||
|
||||
useEffect(() => {
|
||||
shortcutService
|
||||
shortcutStore
|
||||
.getMyAllShortcuts()
|
||||
.catch(() => {
|
||||
// do nth
|
||||
@ -60,13 +61,15 @@ interface ShortcutContainerProps {
|
||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||
const { shortcut, isActive } = props;
|
||||
const { t } = useTranslation();
|
||||
const locationStore = useLocationStore();
|
||||
const shortcutStore = useShortcutStore();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleShortcutClick = () => {
|
||||
if (isActive) {
|
||||
locationService.setMemoShortcut(undefined);
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
} else {
|
||||
locationService.setMemoShortcut(shortcut.id);
|
||||
locationStore.setMemoShortcut(shortcut.id);
|
||||
}
|
||||
};
|
||||
|
||||
@ -75,10 +78,10 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
|
||||
if (showConfirmDeleteBtn) {
|
||||
try {
|
||||
await shortcutService.deleteShortcutById(shortcut.id);
|
||||
if (locationService.getState().query?.shortcutId === shortcut.id) {
|
||||
await shortcutStore.deleteShortcutById(shortcut.id);
|
||||
if (locationStore.getState().query?.shortcutId === shortcut.id) {
|
||||
// need clear shortcut filter
|
||||
locationService.setMemoShortcut(undefined);
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
@ -102,7 +105,7 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
id: shortcut.id,
|
||||
rowStatus: shortcut.rowStatus === "ARCHIVED" ? "NORMAL" : "ARCHIVED",
|
||||
};
|
||||
await shortcutService.patchShortcut(shortcutPatch);
|
||||
await shortcutStore.patchShortcut(shortcutPatch);
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ import { isUndefined } from "lodash-es";
|
||||
import { useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useUserStore } from "../store/module";
|
||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||
import showSettingDialog from "./SettingDialog";
|
||||
import UserBanner from "./UserBanner";
|
||||
@ -14,11 +13,13 @@ import "../less/siderbar.less";
|
||||
|
||||
const Sidebar = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useAppSelector((state) => state.location);
|
||||
const userStore = useUserStore();
|
||||
const locationStore = useLocationStore();
|
||||
const query = locationStore.state.query;
|
||||
|
||||
useEffect(() => {
|
||||
toggleSidebar(false);
|
||||
}, [location.query]);
|
||||
}, [query]);
|
||||
|
||||
const handleSettingBtnClick = () => {
|
||||
showSettingDialog();
|
||||
@ -34,7 +35,7 @@ const Sidebar = () => {
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> {t("sidebar.daily-review")}
|
||||
</button>
|
||||
{!userService.isVisitorMode() && (
|
||||
{!userStore.isVisitorMode() && (
|
||||
<>
|
||||
<Link to="/explore" className="btn action-btn">
|
||||
<span className="icon">🏂</span> {t("common.explore")}
|
||||
@ -45,7 +46,7 @@ const Sidebar = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!userService.isVisitorMode() && <ShortcutList />}
|
||||
{!userStore.isVisitorMode() && <ShortcutList />}
|
||||
<TagList />
|
||||
</aside>
|
||||
</>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, memoService, userService } from "../services";
|
||||
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import Icon from "./Icon";
|
||||
import "../less/tag-list.less";
|
||||
@ -14,13 +13,16 @@ interface Tag {
|
||||
|
||||
const TagList = () => {
|
||||
const { t } = useTranslation();
|
||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const locationStore = useLocationStore();
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
const { memos, tags: tagsText } = memoStore.state;
|
||||
const query = locationStore.state.query;
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (memos.length > 0) {
|
||||
memoService.updateTagsState();
|
||||
memoStore.updateTagsState();
|
||||
}
|
||||
}, [memos]);
|
||||
|
||||
@ -75,7 +77,7 @@ const TagList = () => {
|
||||
{tags.map((t, idx) => (
|
||||
<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>
|
||||
);
|
||||
@ -87,6 +89,7 @@ interface TagItemContainerProps {
|
||||
}
|
||||
|
||||
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
||||
const locationStore = useLocationStore();
|
||||
const { tag, tagQuery } = props;
|
||||
const isActive = tagQuery === tag.text;
|
||||
const hasSubTags = tag.subTags.length > 0;
|
||||
@ -94,9 +97,9 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
|
||||
|
||||
const handleTagClick = () => {
|
||||
if (isActive) {
|
||||
locationService.setTagQuery(undefined);
|
||||
locationStore.setTagQuery(undefined);
|
||||
} else {
|
||||
locationService.setTagQuery(tag.text);
|
||||
locationStore.setTagQuery(tag.text);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { isEqual } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { userService } from "../services";
|
||||
import { useUserStore } from "../store/module";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
|
||||
const validateConfig: ValidatorConfig = {
|
||||
minLength: 4,
|
||||
@ -25,7 +24,8 @@ interface State {
|
||||
|
||||
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
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>({
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
@ -78,7 +78,7 @@ const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const user = userService.getState().user as User;
|
||||
const user = userStore.getState().user as User;
|
||||
const userPatch: UserPatch = {
|
||||
id: user.id,
|
||||
};
|
||||
@ -91,7 +91,7 @@ const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
if (!isEqual(user.email, state.email)) {
|
||||
userPatch.email = state.email;
|
||||
}
|
||||
await userService.patchUser(userPatch);
|
||||
await userStore.patchUser(userPatch);
|
||||
toastHelper.info("Update succeed");
|
||||
handleCloseBtnClick();
|
||||
} catch (error: any) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import * as api from "../helpers/api";
|
||||
import * as storage from "../helpers/storage";
|
||||
import Icon from "./Icon";
|
||||
import "../less/about-site-dialog.less";
|
||||
import { useGlobalStore } from "../store/module";
|
||||
|
||||
interface State {
|
||||
latestVersion: string;
|
||||
@ -11,7 +11,8 @@ interface State {
|
||||
}
|
||||
|
||||
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>({
|
||||
latestVersion: "",
|
||||
show: false,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, userService } from "../services";
|
||||
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import { getMemoStats } from "../helpers/api";
|
||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
@ -28,19 +27,21 @@ interface DailyUsageStat {
|
||||
}
|
||||
|
||||
const UsageHeatMap = () => {
|
||||
const locationStore = useLocationStore();
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
const todayTimeStamp = utils.getDateStampByDate(Date.now());
|
||||
const todayDay = new Date(todayTimeStamp).getDay() + 1;
|
||||
const nullCell = new Array(7 - todayDay).fill(0);
|
||||
const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay;
|
||||
const beginDayTimestamp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
||||
|
||||
const { memos } = useAppSelector((state) => state.memo);
|
||||
const memos = memoStore.state.memos;
|
||||
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestamp));
|
||||
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
||||
const containerElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getMemoStats(userService.getCurrentUserId())
|
||||
getMemoStats(userStore.getCurrentUserId())
|
||||
.then(({ data: { data } }) => {
|
||||
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
|
||||
for (const record of data) {
|
||||
@ -84,11 +85,11 @@ const UsageHeatMap = () => {
|
||||
}, []);
|
||||
|
||||
const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => {
|
||||
if (locationService.getState().query?.duration?.from === item.timestamp) {
|
||||
locationService.setFromAndToQuery();
|
||||
if (locationStore.getState().query?.duration?.from === item.timestamp) {
|
||||
locationStore.setFromAndToQuery();
|
||||
setCurrentStat(null);
|
||||
} else if (item.count > 0) {
|
||||
locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);
|
||||
locationStore.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);
|
||||
setCurrentStat(item);
|
||||
}
|
||||
}, []);
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import { getMemoStats } from "../helpers/api";
|
||||
import * as utils from "../helpers/utils";
|
||||
import userService from "../services/userService";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
import Dropdown from "./common/Dropdown";
|
||||
import showResourcesDialog from "./ResourcesDialog";
|
||||
@ -16,12 +14,15 @@ import "../less/user-banner.less";
|
||||
const UserBanner = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { user, owner } = useAppSelector((state) => state.user);
|
||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
||||
const locationStore = useLocationStore();
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
const { user, owner } = userStore.state;
|
||||
const { memos, tags } = memoStore.state;
|
||||
const [username, setUsername] = useState("Memos");
|
||||
const [memoAmount, setMemoAmount] = useState(0);
|
||||
const [createdDays, setCreatedDays] = useState(0);
|
||||
const isVisitorMode = userService.isVisitorMode();
|
||||
const isVisitorMode = userStore.isVisitorMode();
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisitorMode) {
|
||||
@ -37,7 +38,7 @@ const UserBanner = () => {
|
||||
}, [isVisitorMode, user, owner]);
|
||||
|
||||
useEffect(() => {
|
||||
getMemoStats(userService.getCurrentUserId())
|
||||
getMemoStats(userStore.getCurrentUserId())
|
||||
.then(({ data: { data } }) => {
|
||||
setMemoAmount(data.length);
|
||||
})
|
||||
@ -47,7 +48,7 @@ const UserBanner = () => {
|
||||
}, [memos]);
|
||||
|
||||
const handleUsernameClick = useCallback(() => {
|
||||
locationService.clearQuery();
|
||||
locationStore.clearQuery();
|
||||
}, []);
|
||||
|
||||
const handleResourcesBtnClick = () => {
|
||||
@ -78,7 +79,7 @@ const UserBanner = () => {
|
||||
actionsClassName="min-w-36"
|
||||
actions={
|
||||
<>
|
||||
{!userService.isVisitorMode() && (
|
||||
{!userStore.isVisitorMode() && (
|
||||
<>
|
||||
<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"
|
||||
@ -100,7 +101,7 @@ const UserBanner = () => {
|
||||
>
|
||||
<span className="mr-1">🤠</span> {t("common.about")}
|
||||
</button>
|
||||
{!userService.isVisitorMode() && (
|
||||
{!userStore.isVisitorMode() && (
|
||||
<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"
|
||||
onClick={handleSignOutBtnClick}
|
||||
|
@ -18,4 +18,8 @@ export const MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS = [
|
||||
{ text: "updated_ts", value: "updated_ts" },
|
||||
];
|
||||
|
||||
// space width for tab action in editor
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useGlobalStore, useUserStore } from "../store/module";
|
||||
import * as api from "../helpers/api";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { globalService, userService } from "../services";
|
||||
import Icon from "../components/Icon";
|
||||
import toastHelper from "../components/Toast";
|
||||
import AppearanceSelect from "../components/AppearanceSelect";
|
||||
@ -22,14 +21,16 @@ const validateConfig: ValidatorConfig = {
|
||||
const Auth = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
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 mode = systemStatus.profile.mode;
|
||||
const [username, setUsername] = useState(mode === "dev" ? "demohero" : "");
|
||||
const [password, setPassword] = useState(mode === "dev" ? "secret" : "");
|
||||
|
||||
useEffect(() => {
|
||||
userService.doSignOut().catch();
|
||||
userStore.doSignOut().catch();
|
||||
}, []);
|
||||
|
||||
const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -62,7 +63,7 @@ const Auth = () => {
|
||||
try {
|
||||
actionBtnLoadingState.setLoading();
|
||||
await api.signin(username, password);
|
||||
const user = await userService.doSignIn();
|
||||
const user = await userStore.doSignIn();
|
||||
if (user) {
|
||||
navigate("/");
|
||||
} else {
|
||||
@ -95,7 +96,7 @@ const Auth = () => {
|
||||
try {
|
||||
actionBtnLoadingState.setLoading();
|
||||
await api.signup(username, password, role);
|
||||
const user = await userService.doSignIn();
|
||||
const user = await userStore.doSignIn();
|
||||
if (user) {
|
||||
navigate("/");
|
||||
} else {
|
||||
@ -109,7 +110,7 @@ const Auth = () => {
|
||||
};
|
||||
|
||||
const handleLocaleItemClick = (locale: Locale) => {
|
||||
globalService.setLocale(locale);
|
||||
globalStore.setLocale(locale);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -2,9 +2,8 @@ import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { memoService } from "../services";
|
||||
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import { DEFAULT_MEMO_LIMIT } from "../helpers/consts";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import toastHelper from "../components/Toast";
|
||||
import MemoContent from "../components/MemoContent";
|
||||
@ -17,8 +16,11 @@ interface State {
|
||||
|
||||
const Explore = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const location = useAppSelector((state) => state.location);
|
||||
const locationStore = useLocationStore();
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
const user = userStore.state.user;
|
||||
const location = locationStore.state;
|
||||
const [state, setState] = useState<State>({
|
||||
memos: [],
|
||||
});
|
||||
@ -26,7 +28,7 @@ const Explore = () => {
|
||||
const loadingState = useLoading();
|
||||
|
||||
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) {
|
||||
setIsComplete(true);
|
||||
}
|
||||
@ -39,7 +41,7 @@ const Explore = () => {
|
||||
|
||||
const handleFetchMoreClick = async () => {
|
||||
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) {
|
||||
setIsComplete(true);
|
||||
} else {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { globalService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useGlobalStore, useUserStore } from "../store/module";
|
||||
import toastHelper from "../components/Toast";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import MemosHeader from "../components/MemosHeader";
|
||||
@ -15,12 +14,14 @@ import "../less/home.less";
|
||||
function Home() {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
|
||||
useEffect(() => {
|
||||
const { owner } = userService.getState();
|
||||
const { owner } = userStore.getState();
|
||||
|
||||
if (userService.isVisitorMode()) {
|
||||
if (userStore.isVisitorMode()) {
|
||||
if (!owner) {
|
||||
toastHelper.error(t("message.user-not-found"));
|
||||
}
|
||||
@ -29,7 +30,7 @@ function Home() {
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.setting.locale) {
|
||||
globalService.setLocale(user.setting.locale);
|
||||
globalStore.setLocale(user.setting.locale);
|
||||
}
|
||||
}, [user?.setting.locale]);
|
||||
|
||||
@ -43,11 +44,11 @@ function Home() {
|
||||
<main className="memos-wrapper">
|
||||
<MemosHeader />
|
||||
<div className="memos-editor-wrapper">
|
||||
{!userService.isVisitorMode() && <MemoEditor />}
|
||||
{!userStore.isVisitorMode() && <MemoEditor />}
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
{userService.isVisitorMode() && (
|
||||
{userStore.isVisitorMode() && (
|
||||
<div className="addition-btn-container">
|
||||
{user ? (
|
||||
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||
|
@ -2,9 +2,8 @@ import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { memoService } from "../services";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import { useAppSelector } from "../store";
|
||||
import { useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import toastHelper from "../components/Toast";
|
||||
import MemoContent from "../components/MemoContent";
|
||||
@ -18,8 +17,11 @@ interface State {
|
||||
const MemoDetail = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const params = useParams();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const location = useAppSelector((state) => state.location);
|
||||
const locationStore = useLocationStore();
|
||||
const memoStore = useMemoStore();
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
const location = locationStore.state;
|
||||
const [state, setState] = useState<State>({
|
||||
memo: {
|
||||
id: UNKNOWN_ID,
|
||||
@ -30,7 +32,7 @@ const MemoDetail = () => {
|
||||
useEffect(() => {
|
||||
const memoId = Number(params.memoId);
|
||||
if (memoId && !isNaN(memoId)) {
|
||||
memoService
|
||||
memoStore
|
||||
.fetchMemoById(memoId)
|
||||
.then((memo) => {
|
||||
setState({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { createBrowserRouter, redirect } from "react-router-dom";
|
||||
import { lazy } from "react";
|
||||
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 Explore = lazy(() => import("../pages/Explore"));
|
||||
@ -14,7 +15,7 @@ const router = createBrowserRouter([
|
||||
element: <Auth />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await globalService.initialState();
|
||||
await initialGlobalState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
@ -26,13 +27,13 @@ const router = createBrowserRouter([
|
||||
element: <Home />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await globalService.initialState();
|
||||
await userService.initialState();
|
||||
await initialGlobalState();
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
const { host, user } = userService.getState();
|
||||
const { host, user } = store.getState().user;
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
} else if (isNullorUndefined(user)) {
|
||||
@ -46,13 +47,13 @@ const router = createBrowserRouter([
|
||||
element: <Home />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await globalService.initialState();
|
||||
await userService.initialState();
|
||||
await initialGlobalState();
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
const { host } = userService.getState();
|
||||
const { host } = store.getState().user;
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
@ -64,13 +65,13 @@ const router = createBrowserRouter([
|
||||
element: <Explore />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await globalService.initialState();
|
||||
await userService.initialState();
|
||||
await initialGlobalState();
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
const { host } = userService.getState();
|
||||
const { host } = store.getState().user;
|
||||
if (isNullorUndefined(host)) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
@ -82,13 +83,13 @@ const router = createBrowserRouter([
|
||||
element: <MemoDetail />,
|
||||
loader: async () => {
|
||||
try {
|
||||
await globalService.initialState();
|
||||
await userService.initialState();
|
||||
await initialGlobalState();
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
const { host } = userService.getState();
|
||||
const { host } = store.getState().user;
|
||||
if (isNullorUndefined(host)) {
|
||||
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 { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
import globalReducer from "./modules/global";
|
||||
import userReducer from "./modules/user";
|
||||
import memoReducer from "./modules/memo";
|
||||
import editorReducer from "./modules/editor";
|
||||
import shortcutReducer from "./modules/shortcut";
|
||||
import locationReducer from "./modules/location";
|
||||
import resourceReducer from "./modules/resource";
|
||||
import globalReducer from "./reducer/global";
|
||||
import userReducer from "./reducer/user";
|
||||
import memoReducer from "./reducer/memo";
|
||||
import editorReducer from "./reducer/editor";
|
||||
import shortcutReducer from "./reducer/shortcut";
|
||||
import locationReducer from "./reducer/location";
|
||||
import resourceReducer from "./reducer/resource";
|
||||
|
||||
const store = configureStore({
|
||||
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";
|
||||
|
||||
interface State {
|
||||
editMemoId?: MemoId;
|
||||
memoVisibility: Visibility;
|
||||
resourceList: Resource[];
|
||||
editMemoId?: MemoId;
|
||||
}
|
||||
|
||||
const editorSlice = createSlice({
|
Loading…
x
Reference in New Issue
Block a user