mirror of https://github.com/usememos/memos
refactor: introducing `use{Module}Store` instead of service (#768)
* refactor: introducing `useEditorStore` * refactor: update * chore: updatepull/769/head
parent
bd00fa798d
commit
ef621a444f
@ -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;
|
|
||||||
@ -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([]));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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";
|
||||||
@ -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();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,151 @@
|
|||||||
|
import store, { useAppSelector } from "..";
|
||||||
|
import * as api from "../../helpers/api";
|
||||||
|
import * as storage from "../../helpers/storage";
|
||||||
|
import { UNKNOWN_ID } from "../../helpers/consts";
|
||||||
|
import { getSystemColorScheme } from "../../helpers/utils";
|
||||||
|
import { setAppearance, setLocale } from "../reducer/global";
|
||||||
|
import { setUser, patchUser, setHost, setOwner } from "../reducer/user";
|
||||||
|
|
||||||
|
const defaultSetting: Setting = {
|
||||||
|
locale: "en",
|
||||||
|
appearance: getSystemColorScheme(),
|
||||||
|
memoVisibility: "PRIVATE",
|
||||||
|
memoDisplayTsOption: "created_ts",
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultLocalSetting: LocalSetting = {
|
||||||
|
enableFoldMemo: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertResponseModelUser = (user: User): User => {
|
||||||
|
const setting: Setting = {
|
||||||
|
...defaultSetting,
|
||||||
|
};
|
||||||
|
const { localSetting: storageLocalSetting } = storage.get(["localSetting"]);
|
||||||
|
const localSetting: LocalSetting = {
|
||||||
|
...defaultLocalSetting,
|
||||||
|
...storageLocalSetting,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.userSettingList) {
|
||||||
|
for (const userSetting of user.userSettingList) {
|
||||||
|
(setting as any)[userSetting.key] = JSON.parse(userSetting.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
setting,
|
||||||
|
localSetting,
|
||||||
|
createdTs: user.createdTs * 1000,
|
||||||
|
updatedTs: user.updatedTs * 1000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialUserState = async () => {
|
||||||
|
const { systemStatus } = store.getState().global;
|
||||||
|
|
||||||
|
if (systemStatus.host) {
|
||||||
|
store.dispatch(setHost(convertResponseModelUser(systemStatus.host)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerUserId = getUserIdFromPath();
|
||||||
|
if (ownerUserId) {
|
||||||
|
const { data: owner } = (await api.getUserById(ownerUserId)).data;
|
||||||
|
if (owner) {
|
||||||
|
store.dispatch(setOwner(convertResponseModelUser(owner)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = (await api.getMyselfUser()).data;
|
||||||
|
if (data) {
|
||||||
|
const user = convertResponseModelUser(data);
|
||||||
|
store.dispatch(setUser(user));
|
||||||
|
if (user.setting.locale) {
|
||||||
|
store.dispatch(setLocale(user.setting.locale));
|
||||||
|
}
|
||||||
|
if (user.setting.appearance) {
|
||||||
|
store.dispatch(setAppearance(user.setting.appearance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserIdFromPath = () => {
|
||||||
|
const { pathname } = store.getState().location;
|
||||||
|
const userIdRegex = /^\/u\/(\d+).*/;
|
||||||
|
const result = pathname.match(userIdRegex);
|
||||||
|
if (result && result.length === 2) {
|
||||||
|
return Number(result[1]);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doSignIn = async () => {
|
||||||
|
const { data: user } = (await api.getMyselfUser()).data;
|
||||||
|
if (user) {
|
||||||
|
store.dispatch(setUser(convertResponseModelUser(user)));
|
||||||
|
} else {
|
||||||
|
doSignOut();
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doSignOut = async () => {
|
||||||
|
store.dispatch(setUser());
|
||||||
|
await api.signout();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserStore = () => {
|
||||||
|
const state = useAppSelector((state) => state.user);
|
||||||
|
|
||||||
|
const isVisitorMode = () => {
|
||||||
|
return !(getUserIdFromPath() === undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
getState: () => {
|
||||||
|
return store.getState().user;
|
||||||
|
},
|
||||||
|
isVisitorMode,
|
||||||
|
getUserIdFromPath,
|
||||||
|
doSignIn,
|
||||||
|
doSignOut,
|
||||||
|
getCurrentUserId: () => {
|
||||||
|
if (isVisitorMode()) {
|
||||||
|
return getUserIdFromPath() || UNKNOWN_ID;
|
||||||
|
} else {
|
||||||
|
return state.user?.id || UNKNOWN_ID;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getUserById: async (userId: UserId) => {
|
||||||
|
const { data: user } = (await api.getUserById(userId)).data;
|
||||||
|
if (user) {
|
||||||
|
return convertResponseModelUser(user);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsertUserSetting: async (key: keyof Setting, value: any) => {
|
||||||
|
await api.upsertUserSetting({
|
||||||
|
key: key as any,
|
||||||
|
value: JSON.stringify(value),
|
||||||
|
});
|
||||||
|
await doSignIn();
|
||||||
|
},
|
||||||
|
upsertLocalSetting: async (key: keyof LocalSetting, value: any) => {
|
||||||
|
storage.set({ localSetting: { [key]: value } });
|
||||||
|
store.dispatch(patchUser({ localSetting: { [key]: value } }));
|
||||||
|
},
|
||||||
|
patchUser: async (userPatch: UserPatch): Promise<void> => {
|
||||||
|
const { data } = (await api.patchUser(userPatch)).data;
|
||||||
|
if (userPatch.id === store.getState().user.user?.id) {
|
||||||
|
const user = convertResponseModelUser(data);
|
||||||
|
store.dispatch(patchUser(user));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteUser: async (userDelete: UserDelete) => {
|
||||||
|
await api.deleteUser(userDelete);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
editMemoId?: MemoId;
|
|
||||||
memoVisibility: Visibility;
|
memoVisibility: Visibility;
|
||||||
resourceList: Resource[];
|
resourceList: Resource[];
|
||||||
|
editMemoId?: MemoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorSlice = createSlice({
|
const editorSlice = createSlice({
|
||||||
Loading…
Reference in New Issue