mirror of
https://github.com/usememos/memos.git
synced 2024-12-25 04:13:07 +03:00
refactor: use redux
This commit is contained in:
parent
2e9152e223
commit
c2e5a1a524
@ -8,9 +8,11 @@
|
||||
"lint": "eslint --ext .js,.ts,.tsx, src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0"
|
||||
"react-dom": "^18.1.0",
|
||||
"react-redux": "^8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { useContext } from "react";
|
||||
import appContext from "./stores/appContext";
|
||||
import { appRouterSwitch } from "./routers";
|
||||
import { useAppSelector } from "./store";
|
||||
import "./less/app.less";
|
||||
|
||||
function App() {
|
||||
const {
|
||||
locationState: { pathname },
|
||||
} = useContext(appContext);
|
||||
const pathname = useAppSelector((state) => state.location.pathname);
|
||||
|
||||
return <>{appRouterSwitch(pathname)}</>;
|
||||
}
|
||||
|
@ -48,11 +48,9 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||
|
||||
try {
|
||||
if (shortcutId) {
|
||||
const editedShortcut = await shortcutService.updateShortcut(shortcutId, title, JSON.stringify(filters));
|
||||
shortcutService.editShortcut(shortcutService.convertResponseModelShortcut(editedShortcut));
|
||||
await shortcutService.updateShortcut(shortcutId, title, JSON.stringify(filters));
|
||||
} else {
|
||||
const shortcut = await shortcutService.createShortcut(title, JSON.stringify(filters));
|
||||
shortcutService.pushShortcut(shortcutService.convertResponseModelShortcut(shortcut));
|
||||
await shortcutService.createShortcut(title, JSON.stringify(filters));
|
||||
}
|
||||
} catch (error: any) {
|
||||
toastHelper.error(error.message);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import appContext from "../stores/appContext";
|
||||
import Provider from "../labs/Provider";
|
||||
import appStore from "../stores/appStore";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "../store";
|
||||
import { ANIMATION_DURATION } from "../helpers/consts";
|
||||
import "../less/dialog.less";
|
||||
|
||||
@ -69,11 +68,7 @@ export function showDialog<T extends DialogProps>(
|
||||
);
|
||||
|
||||
if (config.useAppContext) {
|
||||
Fragment = (
|
||||
<Provider store={appStore} context={appContext}>
|
||||
{Fragment}
|
||||
</Provider>
|
||||
);
|
||||
Fragment = <Provider store={store}>{Fragment}</Provider>;
|
||||
}
|
||||
|
||||
dialog.render(Fragment);
|
||||
|
@ -4,7 +4,7 @@ import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../
|
||||
import { parseMarkedToHtml, parseRawTextToHtml } from "../helpers/marked";
|
||||
import utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { globalStateService, memoService } from "../services";
|
||||
import { editorStateService, memoService } from "../services";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import Image from "./Image";
|
||||
import showMemoCardDialog from "./MemoCardDialog";
|
||||
@ -50,23 +50,23 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleMarkMemoClick = () => {
|
||||
globalStateService.setMarkMemoId(memo.id);
|
||||
editorStateService.setMarkMemo(memo.id);
|
||||
};
|
||||
|
||||
const handleEditMemoClick = () => {
|
||||
globalStateService.setEditMemoId(memo.id);
|
||||
editorStateService.setEditMemo(memo.id);
|
||||
};
|
||||
|
||||
const handleDeleteMemoClick = async () => {
|
||||
if (showConfirmDeleteBtn) {
|
||||
try {
|
||||
await memoService.hideMemoById(memo.id);
|
||||
await memoService.archiveMemoById(memo.id);
|
||||
} catch (error: any) {
|
||||
toastHelper.error(error.message);
|
||||
}
|
||||
|
||||
if (globalStateService.getState().editMemoId === memo.id) {
|
||||
globalStateService.setEditMemoId(UNKNOWN_ID);
|
||||
if (editorStateService.getState().editMemoId === memo.id) {
|
||||
editorStateService.setEditMemo(UNKNOWN_ID);
|
||||
}
|
||||
} else {
|
||||
toggleConfirmDeleteBtn();
|
||||
@ -163,15 +163,9 @@ export function formatMemoContent(content: string) {
|
||||
})
|
||||
.join("");
|
||||
|
||||
const { shouldUseMarkdownParser, shouldSplitMemoWord, shouldHideImageUrl } = globalStateService.getState();
|
||||
content = parseMarkedToHtml(content);
|
||||
|
||||
if (shouldUseMarkdownParser) {
|
||||
content = parseMarkedToHtml(content);
|
||||
}
|
||||
|
||||
if (shouldHideImageUrl) {
|
||||
content = content.replace(IMAGE_URL_REG, "");
|
||||
}
|
||||
content = content.replace(IMAGE_URL_REG, "");
|
||||
|
||||
content = content
|
||||
.replace(TAG_REG, "<span class='tag-span'>#$1</span>")
|
||||
@ -179,11 +173,7 @@ export function formatMemoContent(content: string) {
|
||||
.replace(MEMO_LINK_REG, "<span class='memo-link-text' data-value='$2'>$1</span>");
|
||||
|
||||
// Add space in english and chinese
|
||||
if (shouldSplitMemoWord) {
|
||||
content = content
|
||||
.replace(/([\u4e00-\u9fa5])([A-Za-z0-9?.,;[\]]+)/g, "$1 $2")
|
||||
.replace(/([A-Za-z0-9?.,;[\]]+)([\u4e00-\u9fa5])/g, "$1 $2");
|
||||
}
|
||||
content = content.replace(/([\u4e00-\u9fa5])([A-Za-z0-9?.,;[\]]+)/g, "$1 $2").replace(/([A-Za-z0-9?.,;[\]]+)([\u4e00-\u9fa5])/g, "$1 $2");
|
||||
|
||||
const tempDivContainer = document.createElement("div");
|
||||
tempDivContainer.innerHTML = content;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { IMAGE_URL_REG, MEMO_LINK_REG, UNKNOWN_ID } from "../helpers/consts";
|
||||
import utils from "../helpers/utils";
|
||||
import { globalStateService, memoService } from "../services";
|
||||
import { editorStateService, memoService } from "../services";
|
||||
import { parseHtmlToRawText } from "../helpers/marked";
|
||||
import { formatMemoContent } from "./Memo";
|
||||
import toastHelper from "./Toast";
|
||||
@ -96,7 +96,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
||||
|
||||
const handleEditMemoBtnClick = useCallback(() => {
|
||||
props.destroy();
|
||||
globalStateService.setEditMemoId(memo.id);
|
||||
editorStateService.setEditMemo(memo.id);
|
||||
}, [memo.id]);
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { globalStateService, locationService, memoService, resourceService } from "../services";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import { storage } from "../helpers/storage";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
@ -44,32 +44,36 @@ interface Props {}
|
||||
|
||||
const MemoEditor: React.FC<Props> = () => {
|
||||
const {
|
||||
globalState,
|
||||
memoState: { tags },
|
||||
} = useContext(appContext);
|
||||
editor: editorState,
|
||||
memo: { tags },
|
||||
} = useAppSelector((state) => state);
|
||||
const [isTagSeletorShown, toggleTagSeletor] = useToggle(false);
|
||||
const editorRef = useRef<EditorRefActions>(null);
|
||||
const prevGlobalStateRef = useRef(globalState);
|
||||
const prevGlobalStateRef = useRef(editorState);
|
||||
const tagSeletorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState.markMemoId !== UNKNOWN_ID) {
|
||||
if (editorState.markMemoId && editorState.markMemoId !== UNKNOWN_ID) {
|
||||
const editorCurrentValue = editorRef.current?.getContent();
|
||||
const memoLinkText = `${editorCurrentValue ? "\n" : ""}Mark: [@MEMO](${globalState.markMemoId})`;
|
||||
const memoLinkText = `${editorCurrentValue ? "\n" : ""}Mark: [@MEMO](${editorState.markMemoId})`;
|
||||
editorRef.current?.insertText(memoLinkText);
|
||||
globalStateService.setMarkMemoId(UNKNOWN_ID);
|
||||
editorStateService.setMarkMemo(UNKNOWN_ID);
|
||||
}
|
||||
|
||||
if (globalState.editMemoId !== UNKNOWN_ID && globalState.editMemoId !== prevGlobalStateRef.current.editMemoId) {
|
||||
const editMemo = memoService.getMemoById(globalState.editMemoId);
|
||||
if (
|
||||
editorState.editMemoId &&
|
||||
editorState.editMemoId !== UNKNOWN_ID &&
|
||||
editorState.editMemoId !== prevGlobalStateRef.current.editMemoId
|
||||
) {
|
||||
const editMemo = memoService.getMemoById(editorState.editMemoId ?? UNKNOWN_ID);
|
||||
if (editMemo) {
|
||||
editorRef.current?.setContent(editMemo.content ?? "");
|
||||
editorRef.current?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
prevGlobalStateRef.current = globalState;
|
||||
}, [globalState.markMemoId, globalState.editMemoId]);
|
||||
prevGlobalStateRef.current = editorState;
|
||||
}, [editorState.markMemoId, editorState.editMemoId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorRef.current) {
|
||||
@ -144,18 +148,18 @@ const MemoEditor: React.FC<Props> = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { editMemoId } = globalStateService.getState();
|
||||
const { editMemoId } = editorStateService.getState();
|
||||
|
||||
try {
|
||||
if (editMemoId !== UNKNOWN_ID) {
|
||||
const prevMemo = memoService.getMemoById(editMemoId);
|
||||
if (editMemoId && editMemoId !== UNKNOWN_ID) {
|
||||
const prevMemo = memoService.getMemoById(editMemoId ?? UNKNOWN_ID);
|
||||
|
||||
if (prevMemo && prevMemo.content !== content) {
|
||||
const editedMemo = await memoService.updateMemo(prevMemo.id, content);
|
||||
editedMemo.createdTs = Date.now();
|
||||
memoService.editMemo(editedMemo);
|
||||
}
|
||||
globalStateService.setEditMemoId(UNKNOWN_ID);
|
||||
editorStateService.setEditMemo(UNKNOWN_ID);
|
||||
} else {
|
||||
const newMemo = await memoService.createMemo(content);
|
||||
memoService.pushMemo(newMemo);
|
||||
@ -169,7 +173,7 @@ const MemoEditor: React.FC<Props> = () => {
|
||||
}, []);
|
||||
|
||||
const handleCancelBtnClick = useCallback(() => {
|
||||
globalStateService.setEditMemoId(UNKNOWN_ID);
|
||||
editorStateService.setEditMemo(UNKNOWN_ID);
|
||||
editorRef.current?.setContent("");
|
||||
setEditorContentCache("");
|
||||
}, []);
|
||||
@ -259,7 +263,7 @@ const MemoEditor: React.FC<Props> = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isEditing = globalState.editMemoId !== UNKNOWN_ID;
|
||||
const isEditing = Boolean(editorState.editMemoId && editorState.editMemoId !== UNKNOWN_ID);
|
||||
|
||||
const editorConfig = useMemo(
|
||||
() => ({
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useContext } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import utils from "../helpers/utils";
|
||||
import { getTextWithMemoType } from "../helpers/filter";
|
||||
@ -9,10 +8,9 @@ interface FilterProps {}
|
||||
|
||||
const MemoFilter: React.FC<FilterProps> = () => {
|
||||
const {
|
||||
locationState: { query },
|
||||
} = useContext(appContext);
|
||||
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
|
||||
location: { query },
|
||||
} = useAppSelector((state) => state);
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query ?? {};
|
||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
||||
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut);
|
||||
|
||||
@ -38,7 +36,7 @@ const MemoFilter: React.FC<FilterProps> = () => {
|
||||
<div
|
||||
className={"filter-item-container " + (memoType ? "" : "hidden")}
|
||||
onClick={() => {
|
||||
locationService.setMemoTypeQuery("");
|
||||
locationService.setMemoTypeQuery(undefined);
|
||||
}}
|
||||
>
|
||||
<span className="icon-text">📦</span> {getTextWithMemoType(memoType as MemoSpecType)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { locationService, memoService, shortcutService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
|
||||
import utils from "../helpers/utils";
|
||||
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
|
||||
@ -12,13 +12,13 @@ interface Props {}
|
||||
|
||||
const MemoList: React.FC<Props> = () => {
|
||||
const {
|
||||
locationState: { query },
|
||||
memoState: { memos },
|
||||
} = useContext(appContext);
|
||||
location: { query },
|
||||
memo: { memos },
|
||||
} = useAppSelector((state) => state);
|
||||
const [isFetching, setFetchStatus] = useState(true);
|
||||
const wrapperElement = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query ?? {};
|
||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
||||
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut);
|
||||
|
||||
@ -78,7 +78,7 @@ const MemoList: React.FC<Props> = () => {
|
||||
|
||||
const pinnedMemos = shownMemos.filter((m) => m.pinned);
|
||||
const unpinnedMemos = shownMemos.filter((m) => !m.pinned);
|
||||
const sortedMemos = pinnedMemos.concat(unpinnedMemos);
|
||||
const sortedMemos = pinnedMemos.concat(unpinnedMemos).filter((m) => m.rowStatus === "NORMAL");
|
||||
|
||||
useEffect(() => {
|
||||
memoService
|
||||
@ -100,7 +100,7 @@ const MemoList: React.FC<Props> = () => {
|
||||
const targetEl = event.target as HTMLElement;
|
||||
if (targetEl.tagName === "SPAN" && targetEl.className === "tag-span") {
|
||||
const tagName = targetEl.innerText.slice(1);
|
||||
const currTagQuery = locationService.getState().query.tag;
|
||||
const currTagQuery = locationService.getState().query?.tag;
|
||||
if (currTagQuery === tagName) {
|
||||
locationService.setTagQuery("");
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import SearchBar from "./SearchBar";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import "../less/memos-header.less";
|
||||
@ -10,25 +10,23 @@ interface Props {}
|
||||
|
||||
const MemosHeader: React.FC<Props> = () => {
|
||||
const {
|
||||
locationState: {
|
||||
query: { shortcutId },
|
||||
},
|
||||
shortcutState: { shortcuts },
|
||||
} = useContext(appContext);
|
||||
location: { query },
|
||||
shortcut: { shortcuts },
|
||||
} = useAppSelector((state) => state);
|
||||
|
||||
const [titleText, setTitleText] = useState("MEMOS");
|
||||
|
||||
useEffect(() => {
|
||||
if (!shortcutId) {
|
||||
if (!query?.shortcutId) {
|
||||
setTitleText("MEMOS");
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = shortcutService.getShortcutById(shortcutId);
|
||||
const shortcut = shortcutService.getShortcutById(query?.shortcutId);
|
||||
if (shortcut) {
|
||||
setTitleText(shortcut.title);
|
||||
}
|
||||
}, [shortcutId, shortcuts]);
|
||||
}, [query, shortcuts]);
|
||||
|
||||
const handleMemoTextClick = useCallback(() => {
|
||||
const now = Date.now();
|
||||
|
@ -1,22 +1,17 @@
|
||||
import { useContext } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { memoSpecialTypes } from "../helpers/filter";
|
||||
import "../less/search-bar.less";
|
||||
|
||||
interface Props {}
|
||||
|
||||
const SearchBar: React.FC<Props> = () => {
|
||||
const {
|
||||
locationState: {
|
||||
query: { type: memoType },
|
||||
},
|
||||
} = useContext(appContext);
|
||||
const memoType = useAppSelector((state) => state.location.query?.type);
|
||||
|
||||
const handleMemoTypeItemClick = (type: MemoSpecType | "") => {
|
||||
const { type: prevType } = locationService.getState().query;
|
||||
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
||||
const { type: prevType } = locationService.getState().query ?? {};
|
||||
if (type === prevType) {
|
||||
type = "";
|
||||
type = undefined;
|
||||
}
|
||||
locationService.setMemoTypeQuery(type);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { showDialog } from "./Dialog";
|
||||
import MyAccountSection from "./Settings/MyAccountSection";
|
||||
import PreferencesSection from "./Settings/PreferencesSection";
|
||||
@ -16,8 +16,8 @@ interface State {
|
||||
|
||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
const {
|
||||
userState: { user },
|
||||
} = useContext(appContext);
|
||||
user: { user },
|
||||
} = useAppSelector((state) => state);
|
||||
const { destroy } = props;
|
||||
const [state, setState] = useState<State>({
|
||||
selectedSection: "my-account",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext, useState } from "react";
|
||||
import appContext from "../../stores/appContext";
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { userService } from "../../services";
|
||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||
import toastHelper from "../Toast";
|
||||
@ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = {
|
||||
interface Props {}
|
||||
|
||||
const MyAccountSection: React.FC<Props> = () => {
|
||||
const { userState } = useContext(appContext);
|
||||
const { user: userState } = useAppSelector((state) => state);
|
||||
const user = userState.user as User;
|
||||
const [username, setUsername] = useState<string>(user.name);
|
||||
const openAPIRoute = `${window.location.origin}/h/${user.openId}/memo`;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useAppSelector } from "../store";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
@ -13,11 +13,9 @@ interface Props {}
|
||||
|
||||
const ShortcutList: React.FC<Props> = () => {
|
||||
const {
|
||||
shortcutState: { shortcuts },
|
||||
locationState: {
|
||||
query: { shortcutId },
|
||||
},
|
||||
} = useContext(appContext);
|
||||
location: { query },
|
||||
shortcut: { shortcuts },
|
||||
} = useAppSelector((state) => state);
|
||||
const loadingState = useLoading();
|
||||
const pinnedShortcuts = shortcuts
|
||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
||||
@ -48,7 +46,7 @@ const ShortcutList: React.FC<Props> = () => {
|
||||
</p>
|
||||
<div className="shortcuts-container">
|
||||
{sortedShortcuts.map((s) => {
|
||||
return <ShortcutContainer key={s.id} shortcut={s} isActive={s.id === Number(shortcutId)} />;
|
||||
return <ShortcutContainer key={s.id} shortcut={s} isActive={s.id === Number(query?.shortcutId)} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@ -80,7 +78,7 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
|
||||
if (showConfirmDeleteBtn) {
|
||||
try {
|
||||
await shortcutService.deleteShortcut(shortcut.id);
|
||||
await shortcutService.deleteShortcutById(shortcut.id);
|
||||
} catch (error: any) {
|
||||
toastHelper.error(error.message);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useContext } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useAppSelector } from "../store";
|
||||
import utils from "../helpers/utils";
|
||||
import showDailyMemoDiaryDialog from "./DailyMemoDiaryDialog";
|
||||
import showSettingDialog from "./SettingDialog";
|
||||
@ -14,9 +13,9 @@ interface Props {}
|
||||
|
||||
const Sidebar: React.FC<Props> = () => {
|
||||
const {
|
||||
memoState: { memos, tags },
|
||||
userState: { user },
|
||||
} = useContext(appContext);
|
||||
memo: { memos, tags },
|
||||
user: { user },
|
||||
} = useAppSelector((state) => state);
|
||||
const createdDays = user ? Math.ceil((Date.now() - utils.getTimeStampByDate(user.createdTs)) / 1000 / 3600 / 24) : 0;
|
||||
|
||||
const handleMyAccountBtnClick = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, memoService } from "../services";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import Only from "./common/OnlyWhen";
|
||||
@ -16,11 +16,9 @@ interface Props {}
|
||||
|
||||
const TagList: React.FC<Props> = () => {
|
||||
const {
|
||||
locationState: {
|
||||
query: { tag: tagQuery },
|
||||
},
|
||||
memoState: { tags: tagsText, memos },
|
||||
} = useContext(appContext);
|
||||
location: { query },
|
||||
memo: { memos, tags: tagsText },
|
||||
} = useAppSelector((state) => state);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -73,9 +71,9 @@ const TagList: React.FC<Props> = () => {
|
||||
<p className="title-text">Tags</p>
|
||||
<div className="tags-container">
|
||||
{tags.map((t, idx) => (
|
||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={tagQuery} />
|
||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||
))}
|
||||
<Only when={tags.length < 5 && memoService.initialized}>
|
||||
<Only when={tags.length < 5}>
|
||||
<p className="tag-tip-container">
|
||||
Enter <span className="code-text">#tag </span> to create a tag
|
||||
</p>
|
||||
@ -87,7 +85,7 @@ const TagList: React.FC<Props> = () => {
|
||||
|
||||
interface TagItemContainerProps {
|
||||
tag: Tag;
|
||||
tagQuery: string;
|
||||
tagQuery?: string;
|
||||
}
|
||||
|
||||
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService } from "../services";
|
||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||
import utils from "../helpers/utils";
|
||||
@ -36,8 +36,8 @@ const UsageHeatMap: React.FC<Props> = () => {
|
||||
const beginDayTimestemp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP;
|
||||
|
||||
const {
|
||||
memoState: { memos },
|
||||
} = useContext(appContext);
|
||||
memo: { memos },
|
||||
} = useAppSelector((state) => state);
|
||||
const [allStat, setAllStat] = useState<DailyUsageStat[]>(getInitialUsageStat(usedDaysAmount, beginDayTimestemp));
|
||||
const [popupStat, setPopupStat] = useState<DailyUsageStat | null>(null);
|
||||
const [currentStat, setCurrentStat] = useState<DailyUsageStat | null>(null);
|
||||
@ -71,7 +71,7 @@ const UsageHeatMap: React.FC<Props> = () => {
|
||||
}, []);
|
||||
|
||||
const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => {
|
||||
if (locationService.getState().query.duration?.from === item.timestamp) {
|
||||
if (locationService.getState().query?.duration?.from === item.timestamp) {
|
||||
locationService.setFromAndToQuery(0, 0);
|
||||
setCurrentStat(null);
|
||||
} else if (item.count > 0) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext, useState } from "react";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService } from "../services";
|
||||
import MenuBtnsPopup from "./MenuBtnsPopup";
|
||||
import "../less/user-banner.less";
|
||||
@ -8,8 +8,8 @@ interface Props {}
|
||||
|
||||
const UserBanner: React.FC<Props> = () => {
|
||||
const {
|
||||
userState: { user },
|
||||
} = useContext(appContext);
|
||||
user: { user },
|
||||
} = useAppSelector((state) => state);
|
||||
const username = user ? user.name : "Memos";
|
||||
|
||||
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
> .shortcut-container {
|
||||
.flex(row, space-between, center);
|
||||
@apply w-full h-10 py-0 px-4 mt-2 rounded-lg text-base cursor-pointer select-none shrink-0;
|
||||
@apply w-full h-10 py-0 px-4 mt-px first:mt-2 rounded-lg text-base cursor-pointer select-none shrink-0;
|
||||
|
||||
&:hover {
|
||||
background-color: @bg-gray;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import Provider from "./labs/Provider";
|
||||
import appContext from "./stores/appContext";
|
||||
import appStore from "./stores/appStore";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "./store";
|
||||
import { updateStateWithLocation } from "./store/modules/location";
|
||||
import App from "./App";
|
||||
import "./helpers/polyfill";
|
||||
import "./less/global.less";
|
||||
@ -12,8 +12,15 @@ const container = document.getElementById("root");
|
||||
const root = createRoot(container as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={appStore} context={appContext}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
window.onload = () => {
|
||||
store.dispatch(updateStateWithLocation());
|
||||
window.onpopstate = () => {
|
||||
store.dispatch(updateStateWithLocation());
|
||||
};
|
||||
};
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { useContext, useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { locationService, userService } from "../services";
|
||||
import { homeRouterSwitch } from "../routers";
|
||||
import appContext from "../stores/appContext";
|
||||
import { useAppSelector } from "../store";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import "../less/home.less";
|
||||
|
||||
function Home() {
|
||||
const {
|
||||
locationState: { pathname },
|
||||
} = useContext(appContext);
|
||||
location: { pathname },
|
||||
} = useAppSelector((state) => state);
|
||||
const loadingState = useLoading();
|
||||
|
||||
useEffect(() => {
|
||||
|
18
web/src/services/editorStateService.ts
Normal file
18
web/src/services/editorStateService.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import store from "../store";
|
||||
import { setEditMemoId, setMarkMemoId } from "../store/modules/editor";
|
||||
|
||||
const editorStateService = {
|
||||
getState: () => {
|
||||
return store.getState().editor;
|
||||
},
|
||||
|
||||
setEditMemo: (editMemoId: MemoId) => {
|
||||
store.dispatch(setEditMemoId(editMemoId));
|
||||
},
|
||||
|
||||
setMarkMemo: (markMemoId: MemoId) => {
|
||||
store.dispatch(setMarkMemoId(markMemoId));
|
||||
},
|
||||
};
|
||||
|
||||
export default editorStateService;
|
@ -1,50 +0,0 @@
|
||||
import { storage } from "../helpers/storage";
|
||||
import appStore from "../stores/appStore";
|
||||
import { AppSetting } from "../stores/globalStateStore";
|
||||
|
||||
class GlobalStateService {
|
||||
constructor() {
|
||||
const cachedSetting = storage.get(["shouldSplitMemoWord", "shouldHideImageUrl", "shouldUseMarkdownParser"]);
|
||||
const defaultAppSetting = {
|
||||
shouldSplitMemoWord: cachedSetting.shouldSplitMemoWord ?? true,
|
||||
shouldHideImageUrl: cachedSetting.shouldHideImageUrl ?? true,
|
||||
shouldUseMarkdownParser: cachedSetting.shouldUseMarkdownParser ?? true,
|
||||
};
|
||||
|
||||
this.setAppSetting(defaultAppSetting);
|
||||
}
|
||||
|
||||
public getState = () => {
|
||||
return appStore.getState().globalState;
|
||||
};
|
||||
|
||||
public setEditMemoId = (editMemoId: MemoId) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_EDIT_MEMO_ID",
|
||||
payload: {
|
||||
editMemoId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
public setMarkMemoId = (markMemoId: MemoId) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_MARK_MEMO_ID",
|
||||
payload: {
|
||||
markMemoId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
public setAppSetting = (appSetting: Partial<AppSetting>) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_APP_SETTING",
|
||||
payload: appSetting,
|
||||
});
|
||||
storage.set(appSetting);
|
||||
};
|
||||
}
|
||||
|
||||
const globalStateService = new GlobalStateService();
|
||||
|
||||
export default globalStateService;
|
@ -1,8 +1,8 @@
|
||||
import globalStateService from "./globalStateService";
|
||||
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 { globalStateService, locationService, memoService, shortcutService, userService, resourceService };
|
||||
export { editorStateService, locationService, memoService, shortcutService, userService, resourceService };
|
||||
|
@ -1,9 +1,10 @@
|
||||
import utils from "../helpers/utils";
|
||||
import appStore from "../stores/appStore";
|
||||
import store from "../store";
|
||||
import { setQuery, setPathname } from "../store/modules/location";
|
||||
|
||||
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
||||
const { query, pathname, hash } = appStore.getState().locationState;
|
||||
let queryString = utils.transformObjectToParamsString(query);
|
||||
const { query, pathname, hash } = store.getState().location;
|
||||
let queryString = utils.transformObjectToParamsString(query ?? {});
|
||||
if (queryString) {
|
||||
queryString = "?" + queryString;
|
||||
} else {
|
||||
@ -17,180 +18,98 @@ const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
||||
}
|
||||
};
|
||||
|
||||
class LocationService {
|
||||
constructor() {
|
||||
this.updateStateWithLocation();
|
||||
window.onpopstate = () => {
|
||||
this.updateStateWithLocation();
|
||||
};
|
||||
}
|
||||
|
||||
public updateStateWithLocation = () => {
|
||||
const { pathname, search, hash } = window.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const state: AppLocation = {
|
||||
pathname: "/",
|
||||
hash: "",
|
||||
query: {
|
||||
tag: "",
|
||||
duration: null,
|
||||
text: "",
|
||||
type: "",
|
||||
},
|
||||
};
|
||||
state.query.tag = urlParams.get("tag") ?? "";
|
||||
state.query.type = (urlParams.get("type") ?? "") as MemoSpecType;
|
||||
state.query.text = urlParams.get("text") ?? "";
|
||||
state.query.shortcutId = Number(urlParams.get("shortcutId")) ?? undefined;
|
||||
const from = parseInt(urlParams.get("from") ?? "0");
|
||||
const to = parseInt(urlParams.get("to") ?? "0");
|
||||
if (to > from && to !== 0) {
|
||||
state.query.duration = {
|
||||
from,
|
||||
to,
|
||||
};
|
||||
}
|
||||
state.hash = hash;
|
||||
state.pathname = this.getValidPathname(pathname);
|
||||
appStore.dispatch({
|
||||
type: "SET_LOCATION",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
|
||||
public getState = () => {
|
||||
return appStore.getState().locationState;
|
||||
};
|
||||
|
||||
public clearQuery = () => {
|
||||
appStore.dispatch({
|
||||
type: "SET_QUERY",
|
||||
payload: {
|
||||
tag: "",
|
||||
duration: null,
|
||||
text: "",
|
||||
type: "",
|
||||
},
|
||||
});
|
||||
const locationService = {
|
||||
getState: () => {
|
||||
return store.getState().location;
|
||||
},
|
||||
|
||||
clearQuery: () => {
|
||||
store.dispatch(setQuery({}));
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setQuery = (query: Query) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_QUERY",
|
||||
payload: query,
|
||||
});
|
||||
},
|
||||
|
||||
setQuery: (query: Query) => {
|
||||
store.dispatch(setQuery(query));
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setHash = (hash: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_HASH",
|
||||
payload: {
|
||||
hash,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
setPathname: (pathname: AppRouter) => {
|
||||
store.dispatch(setPathname(pathname));
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setPathname = (pathname: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_PATHNAME",
|
||||
payload: {
|
||||
pathname,
|
||||
},
|
||||
});
|
||||
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public pushHistory = (pathname: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_PATHNAME",
|
||||
payload: {
|
||||
pathname,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
pushHistory: (pathname: AppRouter) => {
|
||||
store.dispatch(setPathname(pathname));
|
||||
updateLocationUrl("push");
|
||||
};
|
||||
|
||||
public replaceHistory = (pathname: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_PATHNAME",
|
||||
payload: {
|
||||
pathname,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
replaceHistory: (pathname: AppRouter) => {
|
||||
store.dispatch(setPathname(pathname));
|
||||
updateLocationUrl("replace");
|
||||
};
|
||||
|
||||
public setMemoTypeQuery = (type: MemoSpecType | "" = "") => {
|
||||
appStore.dispatch({
|
||||
type: "SET_TYPE",
|
||||
payload: {
|
||||
type,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
setMemoTypeQuery: (type?: MemoSpecType) => {
|
||||
const { query } = store.getState().location;
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
...query,
|
||||
type: type,
|
||||
})
|
||||
);
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setMemoShortcut = (shortcutId?: ShortcutId) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_SHORTCUT_ID",
|
||||
payload: shortcutId,
|
||||
});
|
||||
},
|
||||
|
||||
setMemoShortcut: (shortcutId?: ShortcutId) => {
|
||||
const { query } = store.getState().location;
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
...query,
|
||||
shortcutId: shortcutId,
|
||||
})
|
||||
);
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setTextQuery = (text: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_TEXT",
|
||||
payload: {
|
||||
text,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
setTextQuery: (text?: string) => {
|
||||
const { query } = store.getState().location;
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
...query,
|
||||
text: text,
|
||||
})
|
||||
);
|
||||
updateLocationUrl();
|
||||
};
|
||||
|
||||
public setTagQuery = (tag: string) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_TAG_QUERY",
|
||||
payload: {
|
||||
tag,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
setTagQuery: (tag?: string) => {
|
||||
const { query } = store.getState().location;
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
...query,
|
||||
tag: tag,
|
||||
})
|
||||
);
|
||||
updateLocationUrl();
|
||||
};
|
||||
},
|
||||
|
||||
public setFromAndToQuery = (from: number, to: number) => {
|
||||
appStore.dispatch({
|
||||
type: "SET_DURATION_QUERY",
|
||||
payload: {
|
||||
setFromAndToQuery: (from: number, to: number) => {
|
||||
const { query } = store.getState().location;
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
...query,
|
||||
duration: { from, to },
|
||||
},
|
||||
});
|
||||
|
||||
})
|
||||
);
|
||||
updateLocationUrl();
|
||||
};
|
||||
},
|
||||
|
||||
public getValidPathname = (pathname: string): AppRouter => {
|
||||
getValidPathname: (pathname: string): AppRouter => {
|
||||
if (["/", "/signin"].includes(pathname)) {
|
||||
return pathname as AppRouter;
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const locationService = new LocationService();
|
||||
},
|
||||
};
|
||||
|
||||
export default locationService;
|
||||
|
@ -1,99 +1,92 @@
|
||||
import api from "../helpers/api";
|
||||
import { TAG_REG } from "../helpers/consts";
|
||||
import utils from "../helpers/utils";
|
||||
import appStore from "../stores/appStore";
|
||||
import { patchMemo, setMemos, setTags } from "../store/modules/memo";
|
||||
import store from "../store";
|
||||
import userService from "./userService";
|
||||
|
||||
class MemoService {
|
||||
public initialized = false;
|
||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||
return {
|
||||
...memo,
|
||||
createdTs: memo.createdTs * 1000,
|
||||
updatedTs: memo.updatedTs * 1000,
|
||||
};
|
||||
};
|
||||
|
||||
public getState() {
|
||||
return appStore.getState().memoState;
|
||||
}
|
||||
const memoService = {
|
||||
getState: () => {
|
||||
return store.getState().memo;
|
||||
},
|
||||
|
||||
public async fetchAllMemos() {
|
||||
fetchAllMemos: async () => {
|
||||
if (!userService.getState().user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await api.getMyMemos();
|
||||
const memos: Memo[] = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => this.convertResponseModelMemo(m));
|
||||
appStore.dispatch({
|
||||
type: "SET_MEMOS",
|
||||
payload: {
|
||||
memos,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.initialized) {
|
||||
this.initialized = true;
|
||||
}
|
||||
const memos: Memo[] = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m));
|
||||
store.dispatch(setMemos(memos));
|
||||
|
||||
return memos;
|
||||
}
|
||||
},
|
||||
|
||||
public async fetchDeletedMemos() {
|
||||
fetchDeletedMemos: async () => {
|
||||
if (!userService.getState().user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await api.getMyArchivedMemos();
|
||||
const deletedMemos: Memo[] = data.map((m) => {
|
||||
return this.convertResponseModelMemo(m);
|
||||
return convertResponseModelMemo(m);
|
||||
});
|
||||
return deletedMemos;
|
||||
}
|
||||
},
|
||||
|
||||
public pushMemo(memo: Memo) {
|
||||
appStore.dispatch({
|
||||
type: "INSERT_MEMO",
|
||||
payload: {
|
||||
memo: {
|
||||
...memo,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
pushMemo: (memo: Memo) => {
|
||||
store.dispatch(setMemos(memoService.getState().memos.concat(memo)));
|
||||
},
|
||||
|
||||
public getMemoById(id: MemoId) {
|
||||
for (const m of this.getState().memos) {
|
||||
getMemoById: (id: MemoId) => {
|
||||
for (const m of memoService.getState().memos) {
|
||||
if (m.id === id) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
archiveMemoById: async (id: MemoId) => {
|
||||
const memo = memoService.getMemoById(id);
|
||||
if (!memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
public async hideMemoById(id: MemoId) {
|
||||
await api.archiveMemo(id);
|
||||
appStore.dispatch({
|
||||
type: "DELETE_MEMO_BY_ID",
|
||||
payload: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
store.dispatch(
|
||||
patchMemo({
|
||||
...memo,
|
||||
rowStatus: "ARCHIVED",
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
public async restoreMemoById(id: MemoId) {
|
||||
restoreMemoById: async (id: MemoId) => {
|
||||
await api.restoreMemo(id);
|
||||
memoService.clearMemos();
|
||||
memoService.fetchAllMemos();
|
||||
}
|
||||
},
|
||||
|
||||
public async deleteMemoById(id: MemoId) {
|
||||
deleteMemoById: async (id: MemoId) => {
|
||||
await api.deleteMemo(id);
|
||||
}
|
||||
},
|
||||
|
||||
public editMemo(memo: Memo) {
|
||||
appStore.dispatch({
|
||||
type: "EDIT_MEMO",
|
||||
payload: memo,
|
||||
});
|
||||
}
|
||||
editMemo: (memo: Memo) => {
|
||||
store.dispatch(patchMemo(memo));
|
||||
},
|
||||
|
||||
public updateTagsState() {
|
||||
const { memos } = this.getState();
|
||||
updateTagsState: () => {
|
||||
const { memos } = memoService.getState();
|
||||
const tagsSet = new Set<string>();
|
||||
for (const m of memos) {
|
||||
for (const t of Array.from(m.content.match(TAG_REG) ?? [])) {
|
||||
@ -101,69 +94,49 @@ class MemoService {
|
||||
}
|
||||
}
|
||||
|
||||
appStore.dispatch({
|
||||
type: "SET_TAGS",
|
||||
payload: {
|
||||
tags: Array.from(tagsSet).filter((t) => Boolean(t)),
|
||||
},
|
||||
});
|
||||
}
|
||||
store.dispatch(setTags(Array.from(tagsSet).filter((t) => Boolean(t))));
|
||||
},
|
||||
|
||||
public clearMemos() {
|
||||
appStore.dispatch({
|
||||
type: "SET_MEMOS",
|
||||
payload: {
|
||||
memos: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
clearMemos: () => {
|
||||
store.dispatch(setMemos([]));
|
||||
},
|
||||
|
||||
public async getLinkedMemos(memoId: MemoId): Promise<Memo[]> {
|
||||
const { memos } = this.getState();
|
||||
getLinkedMemos: async (memoId: MemoId): Promise<Memo[]> => {
|
||||
const { memos } = memoService.getState();
|
||||
return memos.filter((m) => m.content.includes(`${memoId}`));
|
||||
}
|
||||
},
|
||||
|
||||
public async createMemo(content: string): Promise<Memo> {
|
||||
createMemo: async (content: string): Promise<Memo> => {
|
||||
const memo = await api.createMemo({
|
||||
content,
|
||||
});
|
||||
return this.convertResponseModelMemo(memo);
|
||||
}
|
||||
return convertResponseModelMemo(memo);
|
||||
},
|
||||
|
||||
public async updateMemo(memoId: MemoId, content: string): Promise<Memo> {
|
||||
updateMemo: async (memoId: MemoId, content: string): Promise<Memo> => {
|
||||
const memo = await api.patchMemo({
|
||||
id: memoId,
|
||||
content,
|
||||
});
|
||||
return this.convertResponseModelMemo(memo);
|
||||
}
|
||||
return convertResponseModelMemo(memo);
|
||||
},
|
||||
|
||||
public async pinMemo(memoId: MemoId) {
|
||||
pinMemo: async (memoId: MemoId) => {
|
||||
await api.pinMemo(memoId);
|
||||
}
|
||||
},
|
||||
|
||||
public async unpinMemo(memoId: MemoId) {
|
||||
unpinMemo: async (memoId: MemoId) => {
|
||||
await api.unpinMemo(memoId);
|
||||
}
|
||||
},
|
||||
|
||||
public async importMemo(content: string, createdAt: string) {
|
||||
importMemo: async (content: string, createdAt: string) => {
|
||||
const createdTs = Math.floor(utils.getTimeStampByDate(createdAt) / 1000);
|
||||
|
||||
await api.createMemo({
|
||||
content,
|
||||
createdTs,
|
||||
});
|
||||
}
|
||||
|
||||
private convertResponseModelMemo(memo: Memo): Memo {
|
||||
return {
|
||||
...memo,
|
||||
createdTs: memo.createdTs * 1000,
|
||||
updatedTs: memo.updatedTs * 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const memoService = new MemoService();
|
||||
},
|
||||
};
|
||||
|
||||
export default memoService;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import api from "../helpers/api";
|
||||
|
||||
class ResourceService {
|
||||
const resourceService = {
|
||||
/**
|
||||
* Upload resource file to server,
|
||||
* @param file file
|
||||
* @returns resource: id, filename
|
||||
*/
|
||||
public async upload(file: File) {
|
||||
async upload(file: File) {
|
||||
const { name: filename, size } = file;
|
||||
|
||||
if (size > 64 << 20) {
|
||||
@ -18,9 +18,7 @@ class ResourceService {
|
||||
const data = await api.uploadFile(formData);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
const resourceService = new ResourceService();
|
||||
},
|
||||
};
|
||||
|
||||
export default resourceService;
|
||||
|
@ -1,97 +1,77 @@
|
||||
import userService from "./userService";
|
||||
import api from "../helpers/api";
|
||||
import appStore from "../stores/appStore";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import store from "../store/";
|
||||
import { deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
||||
|
||||
class ShortcutService {
|
||||
public getState() {
|
||||
return appStore.getState().shortcutState;
|
||||
}
|
||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
||||
return {
|
||||
...shortcut,
|
||||
createdTs: shortcut.createdTs * 1000,
|
||||
updatedTs: shortcut.updatedTs * 1000,
|
||||
};
|
||||
};
|
||||
|
||||
public async getMyAllShortcuts() {
|
||||
const shortcutService = {
|
||||
getState: () => {
|
||||
return store.getState().shortcut;
|
||||
},
|
||||
|
||||
getMyAllShortcuts: async () => {
|
||||
if (!userService.getState().user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await api.getMyShortcuts();
|
||||
appStore.dispatch({
|
||||
type: "SET_SHORTCUTS",
|
||||
payload: {
|
||||
shortcuts: data.map((s) => this.convertResponseModelShortcut(s)),
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||
store.dispatch(setShortcuts(shortcuts));
|
||||
return shortcuts;
|
||||
},
|
||||
|
||||
public getShortcutById(id: ShortcutId) {
|
||||
getShortcutById: (id: ShortcutId) => {
|
||||
if (id === UNKNOWN_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const s of this.getState().shortcuts) {
|
||||
for (const s of shortcutService.getState().shortcuts) {
|
||||
if (s.id === id) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
public pushShortcut(shortcut: Shortcut) {
|
||||
appStore.dispatch({
|
||||
type: "INSERT_SHORTCUT",
|
||||
payload: {
|
||||
shortcut: {
|
||||
...shortcut,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
pushShortcut: (shortcut: Shortcut) => {
|
||||
store.dispatch(setShortcuts(shortcutService.getState().shortcuts.concat(shortcut)));
|
||||
},
|
||||
|
||||
public editShortcut(shortcut: Shortcut) {
|
||||
appStore.dispatch({
|
||||
type: "UPDATE_SHORTCUT",
|
||||
payload: shortcut,
|
||||
});
|
||||
}
|
||||
editShortcut: (shortcut: Shortcut) => {
|
||||
store.dispatch(patchShortcut(shortcut));
|
||||
},
|
||||
|
||||
public async deleteShortcut(shortcutId: ShortcutId) {
|
||||
deleteShortcutById: async (shortcutId: ShortcutId) => {
|
||||
await api.deleteShortcutById(shortcutId);
|
||||
appStore.dispatch({
|
||||
type: "DELETE_SHORTCUT_BY_ID",
|
||||
payload: {
|
||||
id: shortcutId,
|
||||
},
|
||||
});
|
||||
}
|
||||
store.dispatch(deleteShortcut(shortcutId));
|
||||
},
|
||||
|
||||
public async createShortcut(title: string, payload: string) {
|
||||
createShortcut: async (title: string, payload: string) => {
|
||||
const data = await api.createShortcut(title, payload);
|
||||
return data;
|
||||
}
|
||||
shortcutService.pushShortcut(convertResponseModelShortcut(data));
|
||||
},
|
||||
|
||||
public async updateShortcut(shortcutId: ShortcutId, title: string, payload: string) {
|
||||
updateShortcut: async (shortcutId: ShortcutId, title: string, payload: string) => {
|
||||
const data = await api.updateShortcut(shortcutId, title, payload);
|
||||
return data;
|
||||
}
|
||||
store.dispatch(patchShortcut(convertResponseModelShortcut(data)));
|
||||
},
|
||||
|
||||
public async pinShortcut(shortcutId: ShortcutId) {
|
||||
pinShortcut: async (shortcutId: ShortcutId) => {
|
||||
await api.pinShortcut(shortcutId);
|
||||
}
|
||||
},
|
||||
|
||||
public async unpinShortcut(shortcutId: ShortcutId) {
|
||||
unpinShortcut: async (shortcutId: ShortcutId) => {
|
||||
await api.unpinShortcut(shortcutId);
|
||||
}
|
||||
|
||||
public convertResponseModelShortcut(shortcut: Shortcut): Shortcut {
|
||||
return {
|
||||
...shortcut,
|
||||
createdTs: shortcut.createdTs * 1000,
|
||||
updatedTs: shortcut.updatedTs * 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const shortcutService = new ShortcutService();
|
||||
},
|
||||
};
|
||||
|
||||
export default shortcutService;
|
||||
|
@ -1,68 +1,55 @@
|
||||
import api from "../helpers/api";
|
||||
import appStore from "../stores/appStore";
|
||||
import { signin, signout } from "../store/modules/user";
|
||||
import store from "../store";
|
||||
|
||||
class UserService {
|
||||
public getState() {
|
||||
return appStore.getState().userState;
|
||||
}
|
||||
const convertResponseModelUser = (user: User): User => {
|
||||
return {
|
||||
...user,
|
||||
createdTs: user.createdTs * 1000,
|
||||
updatedTs: user.updatedTs * 1000,
|
||||
};
|
||||
};
|
||||
|
||||
public async doSignIn() {
|
||||
const userService = {
|
||||
getState: () => {
|
||||
return store.getState().user;
|
||||
},
|
||||
|
||||
doSignIn: async () => {
|
||||
const user = await api.getUser();
|
||||
if (user) {
|
||||
appStore.dispatch({
|
||||
type: "LOGIN",
|
||||
payload: {
|
||||
user: this.convertResponseModelUser(user),
|
||||
},
|
||||
});
|
||||
store.dispatch(signin(convertResponseModelUser(user)));
|
||||
} else {
|
||||
userService.doSignOut();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
},
|
||||
|
||||
public async doSignOut() {
|
||||
appStore.dispatch({
|
||||
type: "SIGN_OUT",
|
||||
payload: null,
|
||||
});
|
||||
doSignOut: async () => {
|
||||
store.dispatch(signout);
|
||||
api.signout().catch(() => {
|
||||
// do nth
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
public async updateUsername(name: string): Promise<void> {
|
||||
updateUsername: async (name: string): Promise<void> => {
|
||||
await api.patchUser({
|
||||
name,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
public async updatePassword(password: string): Promise<void> {
|
||||
updatePassword: async (password: string): Promise<void> => {
|
||||
await api.patchUser({
|
||||
password,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
public async resetOpenId(): Promise<string> {
|
||||
resetOpenId: async (): Promise<string> => {
|
||||
const user = await api.patchUser({
|
||||
resetOpenId: true,
|
||||
});
|
||||
appStore.dispatch({
|
||||
type: "RESET_OPENID",
|
||||
payload: user.openId,
|
||||
});
|
||||
return user.openId;
|
||||
}
|
||||
|
||||
private convertResponseModelUser(user: User): User {
|
||||
return {
|
||||
...user,
|
||||
createdTs: user.createdTs * 1000,
|
||||
updatedTs: user.updatedTs * 1000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const userService = new UserService();
|
||||
},
|
||||
};
|
||||
|
||||
export default userService;
|
||||
|
25
web/src/store/index.ts
Normal file
25
web/src/store/index.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
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";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
user: userReducer,
|
||||
memo: memoReducer,
|
||||
editor: editorReducer,
|
||||
shortcut: shortcutReducer,
|
||||
location: locationReducer,
|
||||
},
|
||||
});
|
||||
|
||||
type AppState = ReturnType<typeof store.getState>;
|
||||
type AppDispatch = typeof store.dispatch;
|
||||
|
||||
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
|
||||
export default store;
|
23
web/src/store/modules/editor.ts
Normal file
23
web/src/store/modules/editor.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
markMemoId?: MemoId;
|
||||
editMemoId?: MemoId;
|
||||
}
|
||||
|
||||
const editorSlice = createSlice({
|
||||
name: "editor",
|
||||
initialState: {} as State,
|
||||
reducers: {
|
||||
setMarkMemoId: (state, action: PayloadAction<Option<MemoId>>) => {
|
||||
state.markMemoId = action.payload;
|
||||
},
|
||||
setEditMemoId: (state, action: PayloadAction<Option<MemoId>>) => {
|
||||
state.editMemoId = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setEditMemoId, setMarkMemoId } = editorSlice.actions;
|
||||
|
||||
export default editorSlice.reducer;
|
62
web/src/store/modules/location.ts
Normal file
62
web/src/store/modules/location.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
pathname: AppRouter;
|
||||
hash: string;
|
||||
query?: Query;
|
||||
}
|
||||
|
||||
const getValidPathname = (pathname: string): AppRouter => {
|
||||
if (["/", "/signin"].includes(pathname)) {
|
||||
return pathname as AppRouter;
|
||||
} else {
|
||||
return "/";
|
||||
}
|
||||
};
|
||||
|
||||
const getStateFromLocation = () => {
|
||||
const { pathname, search, hash } = window.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const state: State = {
|
||||
pathname: getValidPathname(pathname),
|
||||
hash: hash,
|
||||
};
|
||||
|
||||
if (search !== "") {
|
||||
state.query = {};
|
||||
state.query.tag = urlParams.get("tag") ?? undefined;
|
||||
state.query.type = (urlParams.get("type") as MemoSpecType) ?? undefined;
|
||||
state.query.text = urlParams.get("text") ?? undefined;
|
||||
state.query.shortcutId = Number(urlParams.get("shortcutId")) ?? undefined;
|
||||
const from = parseInt(urlParams.get("from") ?? "0");
|
||||
const to = parseInt(urlParams.get("to") ?? "0");
|
||||
if (to > from && to !== 0) {
|
||||
state.query.duration = {
|
||||
from,
|
||||
to,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const locationSlice = createSlice({
|
||||
name: "location",
|
||||
initialState: getStateFromLocation(),
|
||||
reducers: {
|
||||
updateStateWithLocation: () => {
|
||||
return getStateFromLocation();
|
||||
},
|
||||
setPathname: (state, action: PayloadAction<AppRouter>) => {
|
||||
state.pathname = action.payload;
|
||||
},
|
||||
setQuery: (state, action: PayloadAction<Partial<Query>>) => {
|
||||
state.query = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setPathname, setQuery, updateStateWithLocation } = locationSlice.actions;
|
||||
|
||||
export default locationSlice.reducer;
|
44
web/src/store/modules/memo.ts
Normal file
44
web/src/store/modules/memo.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
memos: Memo[];
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const memoSlice = createSlice({
|
||||
name: "memo",
|
||||
initialState: {
|
||||
memos: [],
|
||||
tags: [],
|
||||
} as State,
|
||||
reducers: {
|
||||
setMemos: (state, action: PayloadAction<Memo[]>) => {
|
||||
state.memos = action.payload;
|
||||
},
|
||||
setTags: (state, action: PayloadAction<string[]>) => {
|
||||
state.tags = action.payload;
|
||||
},
|
||||
createMemo: (state, action: PayloadAction<Memo>) => {
|
||||
state.memos = state.memos.concat(action.payload);
|
||||
},
|
||||
patchMemo: (state, action: PayloadAction<Partial<Memo>>) => {
|
||||
state.memos = state.memos.map((m) => {
|
||||
if (m.id === action.payload.id) {
|
||||
return {
|
||||
...m,
|
||||
...action.payload,
|
||||
};
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteMemo: (state, action: PayloadAction<MemoId>) => {
|
||||
state.memos = [...state.memos].filter((memo) => memo.id !== action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setMemos, setTags, createMemo, patchMemo, deleteMemo } = memoSlice.actions;
|
||||
|
||||
export default memoSlice.reducer;
|
39
web/src/store/modules/shortcut.ts
Normal file
39
web/src/store/modules/shortcut.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
shortcuts: Shortcut[];
|
||||
}
|
||||
|
||||
const shortcutSlice = createSlice({
|
||||
name: "memo",
|
||||
initialState: {
|
||||
shortcuts: [],
|
||||
} as State,
|
||||
reducers: {
|
||||
setShortcuts: (state, action: PayloadAction<Shortcut[]>) => {
|
||||
state.shortcuts = action.payload;
|
||||
},
|
||||
createShortcut: (state, action: PayloadAction<Shortcut>) => {
|
||||
state.shortcuts = state.shortcuts.concat(action.payload);
|
||||
},
|
||||
patchShortcut: (state, action: PayloadAction<Partial<Shortcut>>) => {
|
||||
state.shortcuts = state.shortcuts.map((s) => {
|
||||
if (s.id === action.payload.id) {
|
||||
return {
|
||||
...s,
|
||||
...action.payload,
|
||||
};
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteShortcut: (state, action: PayloadAction<ShortcutId>) => {
|
||||
state.shortcuts = [...state.shortcuts].filter((shortcut) => shortcut.id !== action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setShortcuts, createShortcut, patchShortcut, deleteShortcut } = shortcutSlice.actions;
|
||||
|
||||
export default shortcutSlice.reducer;
|
34
web/src/store/modules/user.ts
Normal file
34
web/src/store/modules/user.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface State {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: "user",
|
||||
initialState: {} as State,
|
||||
reducers: {
|
||||
signin: (state, action: PayloadAction<User>) => {
|
||||
return {
|
||||
...state,
|
||||
user: action.payload,
|
||||
};
|
||||
},
|
||||
signout: (state) => {
|
||||
return {
|
||||
...state,
|
||||
user: undefined,
|
||||
};
|
||||
},
|
||||
patchUser: (state, action: PayloadAction<Partial<User>>) => {
|
||||
state.user = {
|
||||
...state.user,
|
||||
...action.payload,
|
||||
} as User;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { signin, signout, patchUser } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
@ -1,6 +0,0 @@
|
||||
import { createContext } from "react";
|
||||
import appStore from "./appStore";
|
||||
|
||||
const appContext = createContext(appStore.getState());
|
||||
|
||||
export default appContext;
|
@ -1,36 +0,0 @@
|
||||
import combineReducers from "../labs/combineReducers";
|
||||
import createStore from "../labs/createStore";
|
||||
import * as globalStore from "./globalStateStore";
|
||||
import * as locationStore from "./locationStore";
|
||||
import * as memoStore from "./memoStore";
|
||||
import * as userStore from "./userStore";
|
||||
import * as shortcutStore from "./shortcutStore";
|
||||
|
||||
interface AppState {
|
||||
globalState: globalStore.State;
|
||||
locationState: locationStore.State;
|
||||
memoState: memoStore.State;
|
||||
userState: userStore.State;
|
||||
shortcutState: shortcutStore.State;
|
||||
}
|
||||
|
||||
type AppStateActions = globalStore.Actions | locationStore.Actions | memoStore.Actions | userStore.Actions | shortcutStore.Actions;
|
||||
|
||||
const appStore = createStore<AppState, AppStateActions>(
|
||||
{
|
||||
globalState: globalStore.defaultState,
|
||||
locationState: locationStore.defaultState,
|
||||
memoState: memoStore.defaultState,
|
||||
userState: userStore.defaultState,
|
||||
shortcutState: shortcutStore.defaultState,
|
||||
},
|
||||
combineReducers<AppState, AppStateActions>({
|
||||
globalState: globalStore.reducer,
|
||||
locationState: locationStore.reducer,
|
||||
memoState: memoStore.reducer,
|
||||
userState: userStore.reducer,
|
||||
shortcutState: shortcutStore.reducer,
|
||||
})
|
||||
);
|
||||
|
||||
export default appStore;
|
@ -1,75 +0,0 @@
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
|
||||
export interface AppSetting {
|
||||
shouldSplitMemoWord: boolean;
|
||||
shouldHideImageUrl: boolean;
|
||||
shouldUseMarkdownParser: boolean;
|
||||
}
|
||||
|
||||
export interface State extends AppSetting {
|
||||
markMemoId: MemoId;
|
||||
editMemoId: MemoId;
|
||||
}
|
||||
|
||||
interface SetMarkMemoIdAction {
|
||||
type: "SET_MARK_MEMO_ID";
|
||||
payload: {
|
||||
markMemoId: MemoId;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetEditMemoIdAction {
|
||||
type: "SET_EDIT_MEMO_ID";
|
||||
payload: {
|
||||
editMemoId: MemoId;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetAppSettingAction {
|
||||
type: "SET_APP_SETTING";
|
||||
payload: Partial<AppSetting>;
|
||||
}
|
||||
|
||||
export type Actions = SetEditMemoIdAction | SetMarkMemoIdAction | SetAppSettingAction;
|
||||
|
||||
export function reducer(state: State, action: Actions) {
|
||||
switch (action.type) {
|
||||
case "SET_MARK_MEMO_ID": {
|
||||
if (action.payload.markMemoId === state.markMemoId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
markMemoId: action.payload.markMemoId,
|
||||
};
|
||||
}
|
||||
case "SET_EDIT_MEMO_ID": {
|
||||
if (action.payload.editMemoId === state.editMemoId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
editMemoId: action.payload.editMemoId,
|
||||
};
|
||||
}
|
||||
case "SET_APP_SETTING": {
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState: State = {
|
||||
markMemoId: UNKNOWN_ID,
|
||||
editMemoId: UNKNOWN_ID,
|
||||
shouldSplitMemoWord: true,
|
||||
shouldHideImageUrl: true,
|
||||
shouldUseMarkdownParser: true,
|
||||
};
|
@ -1,187 +0,0 @@
|
||||
export type State = AppLocation;
|
||||
|
||||
interface SetLocationAction {
|
||||
type: "SET_LOCATION";
|
||||
payload: State;
|
||||
}
|
||||
|
||||
interface SetPathnameAction {
|
||||
type: "SET_PATHNAME";
|
||||
payload: {
|
||||
pathname: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetQueryAction {
|
||||
type: "SET_QUERY";
|
||||
payload: Query;
|
||||
}
|
||||
|
||||
interface SetShortcutIdAction {
|
||||
type: "SET_SHORTCUT_ID";
|
||||
payload: ShortcutId | undefined;
|
||||
}
|
||||
|
||||
interface SetTagQueryAction {
|
||||
type: "SET_TAG_QUERY";
|
||||
payload: {
|
||||
tag: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetFromAndToQueryAction {
|
||||
type: "SET_DURATION_QUERY";
|
||||
payload: {
|
||||
duration: Duration | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetTypeAction {
|
||||
type: "SET_TYPE";
|
||||
payload: {
|
||||
type: MemoSpecType | "";
|
||||
};
|
||||
}
|
||||
|
||||
interface SetTextAction {
|
||||
type: "SET_TEXT";
|
||||
payload: {
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetHashAction {
|
||||
type: "SET_HASH";
|
||||
payload: {
|
||||
hash: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Actions =
|
||||
| SetLocationAction
|
||||
| SetPathnameAction
|
||||
| SetQueryAction
|
||||
| SetTagQueryAction
|
||||
| SetFromAndToQueryAction
|
||||
| SetTypeAction
|
||||
| SetTextAction
|
||||
| SetShortcutIdAction
|
||||
| SetHashAction;
|
||||
|
||||
export function reducer(state: State, action: Actions) {
|
||||
switch (action.type) {
|
||||
case "SET_LOCATION": {
|
||||
return action.payload;
|
||||
}
|
||||
case "SET_PATHNAME": {
|
||||
if (action.payload.pathname === state.pathname) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
pathname: action.payload.pathname,
|
||||
};
|
||||
}
|
||||
case "SET_HASH": {
|
||||
if (action.payload.hash === state.hash) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
hash: action.payload.hash,
|
||||
};
|
||||
}
|
||||
case "SET_QUERY": {
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_TAG_QUERY": {
|
||||
if (action.payload.tag === state.query.tag) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...state.query,
|
||||
tag: action.payload.tag,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_DURATION_QUERY": {
|
||||
if (action.payload.duration === state.query.duration) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...state.query,
|
||||
duration: {
|
||||
...state.query.duration,
|
||||
...action.payload.duration,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_TYPE": {
|
||||
if (action.payload.type === state.query.type) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...state.query,
|
||||
type: action.payload.type,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_TEXT": {
|
||||
if (action.payload.text === state.query.text) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...state.query,
|
||||
text: action.payload.text,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_SHORTCUT_ID": {
|
||||
if (action.payload === state.query.shortcutId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
query: {
|
||||
...state.query,
|
||||
shortcutId: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState: State = {
|
||||
pathname: "/",
|
||||
hash: "",
|
||||
query: {
|
||||
tag: "",
|
||||
duration: null,
|
||||
type: "",
|
||||
text: "",
|
||||
},
|
||||
};
|
@ -1,98 +0,0 @@
|
||||
import utils from "../helpers/utils";
|
||||
|
||||
export interface State {
|
||||
memos: Memo[];
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface SetMemosAction {
|
||||
type: "SET_MEMOS";
|
||||
payload: {
|
||||
memos: Memo[];
|
||||
};
|
||||
}
|
||||
|
||||
interface SetTagsAction {
|
||||
type: "SET_TAGS";
|
||||
payload: {
|
||||
tags: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface InsertMemoAction {
|
||||
type: "INSERT_MEMO";
|
||||
payload: {
|
||||
memo: Memo;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteMemoByIdAction {
|
||||
type: "DELETE_MEMO_BY_ID";
|
||||
payload: {
|
||||
id: MemoId;
|
||||
};
|
||||
}
|
||||
|
||||
interface EditMemoByIdAction {
|
||||
type: "EDIT_MEMO";
|
||||
payload: Memo;
|
||||
}
|
||||
|
||||
export type Actions = SetMemosAction | SetTagsAction | InsertMemoAction | DeleteMemoByIdAction | EditMemoByIdAction;
|
||||
|
||||
export function reducer(state: State, action: Actions): State {
|
||||
switch (action.type) {
|
||||
case "SET_MEMOS": {
|
||||
const memos = utils.dedupeObjectWithId(action.payload.memos.sort((a, b) => b.createdTs - a.createdTs));
|
||||
|
||||
return {
|
||||
...state,
|
||||
memos: [...memos],
|
||||
};
|
||||
}
|
||||
case "SET_TAGS": {
|
||||
return {
|
||||
...state,
|
||||
tags: action.payload.tags,
|
||||
};
|
||||
}
|
||||
case "INSERT_MEMO": {
|
||||
const memos = utils.dedupeObjectWithId([action.payload.memo, ...state.memos].sort((a, b) => b.createdTs - a.createdTs));
|
||||
return {
|
||||
...state,
|
||||
memos,
|
||||
};
|
||||
}
|
||||
case "DELETE_MEMO_BY_ID": {
|
||||
return {
|
||||
...state,
|
||||
memos: [...state.memos].filter((memo) => memo.id !== action.payload.id),
|
||||
};
|
||||
}
|
||||
case "EDIT_MEMO": {
|
||||
const memos = state.memos.map((m) => {
|
||||
if (m.id === action.payload.id) {
|
||||
return {
|
||||
...m,
|
||||
...action.payload,
|
||||
};
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
memos,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState: State = {
|
||||
memos: [],
|
||||
tags: [],
|
||||
};
|
@ -1,92 +0,0 @@
|
||||
import utils from "../helpers/utils";
|
||||
|
||||
export interface State {
|
||||
shortcuts: Shortcut[];
|
||||
}
|
||||
|
||||
interface SetShortcutsAction {
|
||||
type: "SET_SHORTCUTS";
|
||||
payload: {
|
||||
shortcuts: Shortcut[];
|
||||
};
|
||||
}
|
||||
|
||||
interface InsertShortcutAction {
|
||||
type: "INSERT_SHORTCUT";
|
||||
payload: {
|
||||
shortcut: Shortcut;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteShortcutByIdAction {
|
||||
type: "DELETE_SHORTCUT_BY_ID";
|
||||
payload: {
|
||||
id: ShortcutId;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateShortcutAction {
|
||||
type: "UPDATE_SHORTCUT";
|
||||
payload: Shortcut;
|
||||
}
|
||||
|
||||
export type Actions = SetShortcutsAction | InsertShortcutAction | DeleteShortcutByIdAction | UpdateShortcutAction;
|
||||
|
||||
export function reducer(state: State, action: Actions): State {
|
||||
switch (action.type) {
|
||||
case "SET_SHORTCUTS": {
|
||||
const shortcuts = utils.dedupeObjectWithId(
|
||||
action.payload.shortcuts
|
||||
.sort((a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs))
|
||||
.sort((a, b) => utils.getTimeStampByDate(b.updatedTs) - utils.getTimeStampByDate(a.updatedTs))
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts,
|
||||
};
|
||||
}
|
||||
case "INSERT_SHORTCUT": {
|
||||
const shortcuts = utils.dedupeObjectWithId(
|
||||
[action.payload.shortcut, ...state.shortcuts].sort(
|
||||
(a, b) => utils.getTimeStampByDate(b.createdTs) - utils.getTimeStampByDate(a.createdTs)
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts,
|
||||
};
|
||||
}
|
||||
case "DELETE_SHORTCUT_BY_ID": {
|
||||
return {
|
||||
...state,
|
||||
shortcuts: [...state.shortcuts].filter((shortcut) => shortcut.id !== action.payload.id),
|
||||
};
|
||||
}
|
||||
case "UPDATE_SHORTCUT": {
|
||||
const shortcuts = state.shortcuts.map((m) => {
|
||||
if (m.id === action.payload.id) {
|
||||
return {
|
||||
...m,
|
||||
...action.payload,
|
||||
};
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState: State = {
|
||||
shortcuts: [],
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
export interface State {
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
interface SignInAction {
|
||||
type: "LOGIN";
|
||||
payload: State;
|
||||
}
|
||||
|
||||
interface SignOutAction {
|
||||
type: "SIGN_OUT";
|
||||
payload: null;
|
||||
}
|
||||
|
||||
interface ResetOpenIdAction {
|
||||
type: "RESET_OPENID";
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export type Actions = SignInAction | SignOutAction | ResetOpenIdAction;
|
||||
|
||||
export function reducer(state: State, action: Actions): State {
|
||||
switch (action.type) {
|
||||
case "LOGIN": {
|
||||
return {
|
||||
user: action.payload.user,
|
||||
};
|
||||
}
|
||||
case "SIGN_OUT": {
|
||||
return {
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
case "RESET_OPENID": {
|
||||
if (!state.user) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
...state.user,
|
||||
openId: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState: State = { user: null };
|
2
web/src/types/common.d.ts
vendored
2
web/src/types/common.d.ts
vendored
@ -9,3 +9,5 @@ type FunctionType = (...args: unknown[]) => unknown;
|
||||
interface KVObject<T = any> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
type Option<T> = T | undefined;
|
||||
|
8
web/src/types/location.d.ts
vendored
8
web/src/types/location.d.ts
vendored
@ -4,10 +4,10 @@ interface Duration {
|
||||
}
|
||||
|
||||
interface Query {
|
||||
tag: string;
|
||||
duration: Duration | null;
|
||||
type: MemoSpecType | "";
|
||||
text: string;
|
||||
tag?: string;
|
||||
duration?: Duration;
|
||||
type?: MemoSpecType;
|
||||
text?: string;
|
||||
shortcutId?: ShortcutId;
|
||||
}
|
||||
|
||||
|
@ -202,6 +202,13 @@
|
||||
"@babel/plugin-syntax-jsx" "^7.16.7"
|
||||
"@babel/types" "^7.17.0"
|
||||
|
||||
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
|
||||
version "7.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.0.tgz#6d77142a19cb6088f0af662af1ada37a604d34ae"
|
||||
integrity sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
|
||||
@ -303,6 +310,16 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@reduxjs/toolkit@^1.8.1":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.1.tgz#94ee1981b8cf9227cda40163a04704a9544c9a9f"
|
||||
integrity sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==
|
||||
dependencies:
|
||||
immer "^9.0.7"
|
||||
redux "^4.1.2"
|
||||
redux-thunk "^2.4.1"
|
||||
reselect "^4.1.5"
|
||||
|
||||
"@rollup/pluginutils@^4.2.0":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||
@ -311,6 +328,14 @@
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/json-schema@^7.0.9":
|
||||
version "7.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
@ -363,6 +388,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
|
||||
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
|
||||
|
||||
"@types/use-sync-external-store@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
|
||||
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.6.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz#9608a4b6d0427104bccf132f058cba629a6553c0"
|
||||
@ -1340,6 +1370,13 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
iconv-lite@^0.4.4:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@ -1357,6 +1394,11 @@ image-size@~0.5.0:
|
||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
|
||||
integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
|
||||
|
||||
immer@^9.0.7:
|
||||
version "9.0.14"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.14.tgz#e05b83b63999d26382bb71676c9d827831248a48"
|
||||
integrity sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw==
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
@ -1913,11 +1955,28 @@ react-dom@^18.1.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.22.0"
|
||||
|
||||
react-is@^16.13.1:
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67"
|
||||
integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==
|
||||
|
||||
react-redux@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.1.tgz#2bc029f5ada9b443107914c373a2750f6bc0f40c"
|
||||
integrity sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
"@types/hoist-non-react-statics" "^3.3.1"
|
||||
"@types/use-sync-external-store" "^0.0.3"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
react-is "^18.0.0"
|
||||
use-sync-external-store "^1.0.0"
|
||||
|
||||
react-refresh@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.12.0.tgz#28ac0a2c30ef2bb3433d5fd0621e69a6d774c3a4"
|
||||
@ -1937,6 +1996,23 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redux-thunk@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
|
||||
integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
|
||||
|
||||
redux@^4.1.2:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
|
||||
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
regexp.prototype.flags@^1.4.1:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.2.tgz#bf635117a2f4b755595ebb0c0ee2d2a49b2084db"
|
||||
@ -1950,6 +2026,11 @@ regexpp@^3.2.0:
|
||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
|
||||
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
|
||||
|
||||
reselect@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6"
|
||||
integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
@ -2234,6 +2315,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-sync-external-store@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82"
|
||||
integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
Loading…
Reference in New Issue
Block a user