refactor: sync frontend

pull/26/head
email 3 years ago
parent 4535e0ce6d
commit 3fa918169e

@ -1,18 +1,18 @@
import { memo, useCallback, useEffect, useState } from "react"; import { memo, useCallback, useEffect, useState } from "react";
import { memoService, queryService } from "../services"; import { memoService, shortcutService } from "../services";
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter"; import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import { showDialog } from "./Dialog"; import { showDialog } from "./Dialog";
import toastHelper from "./Toast"; import toastHelper from "./Toast";
import Selector from "./common/Selector"; import Selector from "./common/Selector";
import "../less/create-query-dialog.less"; import "../less/create-shortcut-dialog.less";
interface Props extends DialogProps { interface Props extends DialogProps {
queryId?: string; shortcutId?: string;
} }
const CreateQueryDialog: React.FC<Props> = (props: Props) => { const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
const { destroy, queryId } = props; const { destroy, shortcutId } = props;
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [filters, setFilters] = useState<Filter[]>([]); const [filters, setFilters] = useState<Filter[]>([]);
@ -23,15 +23,15 @@ const CreateQueryDialog: React.FC<Props> = (props: Props) => {
}).length; }).length;
useEffect(() => { useEffect(() => {
const queryTemp = queryService.getQueryById(queryId ?? ""); const shortcutTemp = shortcutService.getShortcutById(shortcutId ?? "");
if (queryTemp) { if (shortcutTemp) {
setTitle(queryTemp.title); setTitle(shortcutTemp.title);
const temp = JSON.parse(queryTemp.querystring); const temp = JSON.parse(shortcutTemp.payload);
if (Array.isArray(temp)) { if (Array.isArray(temp)) {
setFilters(temp); setFilters(temp);
} }
} }
}, [queryId]); }, [shortcutId]);
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string; const text = e.target.value as string;
@ -45,12 +45,12 @@ const CreateQueryDialog: React.FC<Props> = (props: Props) => {
} }
try { try {
if (queryId) { if (shortcutId) {
const editedQuery = await queryService.updateQuery(queryId, title, JSON.stringify(filters)); const editedShortcut = await shortcutService.updateShortcut(shortcutId, title, JSON.stringify(filters));
queryService.editQuery(editedQuery); shortcutService.editShortcut(shortcutService.convertResponseModelShortcut(editedShortcut));
} else { } else {
const query = await queryService.createQuery(title, JSON.stringify(filters)); const shortcut = await shortcutService.createShortcut(title, JSON.stringify(filters));
queryService.pushQuery(query); shortcutService.pushShortcut(shortcutService.convertResponseModelShortcut(shortcut));
} }
} catch (error: any) { } catch (error: any) {
toastHelper.error(error.message); toastHelper.error(error.message);
@ -90,7 +90,7 @@ const CreateQueryDialog: React.FC<Props> = (props: Props) => {
<div className="dialog-header-container"> <div className="dialog-header-container">
<p className="title-text"> <p className="title-text">
<span className="icon-text">🔖</span> <span className="icon-text">🔖</span>
{queryId ? "编辑检索" : "创建检索"} {shortcutId ? "编辑检索" : "创建检索"}
</p> </p>
<button className="btn close-btn" onClick={destroy}> <button className="btn close-btn" onClick={destroy}>
<img className="icon-img" src="/icons/close.svg" /> <img className="icon-img" src="/icons/close.svg" />
@ -298,12 +298,12 @@ const FilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInpute
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer); const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer);
export default function showCreateQueryDialog(queryId?: string): void { export default function showCreateShortcutDialog(shortcutId?: string): void {
showDialog( showDialog(
{ {
className: "create-query-dialog", className: "create-shortcut-dialog",
}, },
CreateQueryDialog, CreateShortcutDialog,
{ queryId } { shortcutId }
); );
} }

@ -18,7 +18,7 @@ const DeletedMemo: React.FC<Props> = (props: Props) => {
const memo: FormattedMemo = { const memo: FormattedMemo = {
...propsMemo, ...propsMemo,
createdAtStr: utils.getDateTimeString(propsMemo.createdAt), createdAtStr: utils.getDateTimeString(propsMemo.createdAt),
deletedAtStr: utils.getDateTimeString(propsMemo.deletedAt ?? Date.now()), deletedAtStr: utils.getDateTimeString(propsMemo.updatedAt ?? Date.now()),
}; };
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []); const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);

@ -130,7 +130,7 @@ const MemoEditor: React.FC<Props> = () => {
try { try {
const image = await resourceService.upload(file); const image = await resourceService.upload(file);
const url = `/r/${image.id}/${image.filename}`; const url = `/h/r/${image.id}/${image.filename}`;
return url; return url;
} catch (error: any) { } catch (error: any) {

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import { locationService, queryService } from "../services"; import { locationService, shortcutService } from "../services";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import { getTextWithMemoType } from "../helpers/filter"; import { getTextWithMemoType } from "../helpers/filter";
import "../less/memo-filter.less"; import "../less/memo-filter.less";
@ -12,8 +12,8 @@ const MemoFilter: React.FC<FilterProps> = () => {
locationState: { query }, locationState: { query },
} = useContext(appContext); } = useContext(appContext);
const { tag: tagQuery, duration, type: memoType, text: textQuery, filter } = query; const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
const queryFilter = queryService.getQueryById(filter); const queryFilter = shortcutService.getShortcutById(shortcutId);
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter); const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
return ( return (
@ -22,7 +22,7 @@ const MemoFilter: React.FC<FilterProps> = () => {
<div <div
className={"filter-item-container " + (queryFilter ? "" : "hidden")} className={"filter-item-container " + (queryFilter ? "" : "hidden")}
onClick={() => { onClick={() => {
locationService.setMemoFilter(""); locationService.setMemoShortcut("");
}} }}
> >
<span className="icon-text">🔖</span> {queryFilter?.title} <span className="icon-text">🔖</span> {queryFilter?.title}

@ -1,6 +1,6 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { useCallback, useContext, useEffect, useRef, useState } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import { locationService, memoService, queryService } from "../services"; import { locationService, memoService, shortcutService } from "../services";
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import { checkShouldShowMemoWithFilters } from "../helpers/filter"; import { checkShouldShowMemoWithFilters } from "../helpers/filter";
@ -18,8 +18,8 @@ const MemoList: React.FC<Props> = () => {
const [isFetching, setFetchStatus] = useState(true); const [isFetching, setFetchStatus] = useState(true);
const wrapperElement = useRef<HTMLDivElement>(null); const wrapperElement = useRef<HTMLDivElement>(null);
const { tag: tagQuery, duration, type: memoType, text: textQuery, filter: queryId } = query; const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
const queryFilter = queryService.getQueryById(queryId); const queryFilter = shortcutService.getShortcutById(shortcutId);
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter); const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
const shownMemos = const shownMemos =
@ -28,7 +28,7 @@ const MemoList: React.FC<Props> = () => {
let shouldShow = true; let shouldShow = true;
if (queryFilter) { if (queryFilter) {
const filters = JSON.parse(queryFilter.querystring) as Filter[]; const filters = JSON.parse(queryFilter.payload) as Filter[];
if (Array.isArray(filters)) { if (Array.isArray(filters)) {
shouldShow = checkShouldShowMemoWithFilters(memo, filters); shouldShow = checkShouldShowMemoWithFilters(memo, filters);
} }

@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useState } from "react"; import { useCallback, useContext, useEffect, useState } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import SearchBar from "./SearchBar"; import SearchBar from "./SearchBar";
import { globalStateService, memoService, queryService } from "../services"; import { globalStateService, memoService, shortcutService } from "../services";
import Only from "./common/OnlyWhen"; import Only from "./common/OnlyWhen";
import "../less/memos-header.less"; import "../less/memos-header.less";
@ -12,22 +12,22 @@ interface Props {}
const MemosHeader: React.FC<Props> = () => { const MemosHeader: React.FC<Props> = () => {
const { const {
locationState: { locationState: {
query: { filter }, query: { shortcutId },
}, },
globalState: { isMobileView }, globalState: { isMobileView },
queryState: { queries }, shortcutState: { shortcuts },
} = useContext(appContext); } = useContext(appContext);
const [titleText, setTitleText] = useState("MEMOS"); const [titleText, setTitleText] = useState("MEMOS");
useEffect(() => { useEffect(() => {
const query = queryService.getQueryById(filter); const query = shortcutService.getShortcutById(shortcutId);
if (query) { if (query) {
setTitleText(query.title); setTitleText(query.title);
} else { } else {
setTitleText("MEMOS"); setTitleText("MEMOS");
} }
}, [filter, queries]); }, [shortcutId, shortcuts]);
const handleMemoTextClick = useCallback(() => { const handleMemoTextClick = useCallback(() => {
const now = Date.now(); const now = Date.now();

@ -20,8 +20,8 @@ interface Props {}
const MyAccountSection: React.FC<Props> = () => { const MyAccountSection: React.FC<Props> = () => {
const { userState } = useContext(appContext); const { userState } = useContext(appContext);
const user = userState.user as Model.User; const user = userState.user as Model.User;
const [username, setUsername] = useState<string>(user.username); const [username, setUsername] = useState<string>(user.name);
const openAPIRoute = `${window.location.origin}/api/whs/memo/${user.openId}`; const openAPIRoute = `${window.location.origin}/h/${user.openId}/memo`;
const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const nextUsername = e.target.value as string; const nextUsername = e.target.value as string;
@ -29,12 +29,12 @@ const MyAccountSection: React.FC<Props> = () => {
}; };
const handleConfirmEditUsernameBtnClick = async () => { const handleConfirmEditUsernameBtnClick = async () => {
if (user.username === "guest") { if (user.name === "guest") {
toastHelper.info("🈲 不要修改我的用户名"); toastHelper.info("🈲 不要修改我的用户名");
return; return;
} }
if (username === user.username) { if (username === user.name) {
return; return;
} }
@ -61,7 +61,7 @@ const MyAccountSection: React.FC<Props> = () => {
}; };
const handleChangePasswordBtnClick = () => { const handleChangePasswordBtnClick = () => {
if (user.username === "guest") { if (user.name === "guest") {
toastHelper.info("🈲 不要修改我的密码"); toastHelper.info("🈲 不要修改我的密码");
return; return;
} }
@ -93,14 +93,14 @@ const MyAccountSection: React.FC<Props> = () => {
<label className="form-label input-form-label username-label"> <label className="form-label input-form-label username-label">
<span className="normal-text"></span> <span className="normal-text"></span>
<input type="text" value={username} onChange={handleUsernameChanged} /> <input type="text" value={username} onChange={handleUsernameChanged} />
<div className={`btns-container ${username === user.username ? "hidden" : ""}`} onClick={handlePreventDefault}> <div className={`btns-container ${username === user.name ? "hidden" : ""}`} onClick={handlePreventDefault}>
<span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}> <span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}>
</span> </span>
<span <span
className="btn cancel-btn" className="btn cancel-btn"
onClick={() => { onClick={() => {
setUsername(user.username); setUsername(user.name);
}} }}
> >

@ -97,7 +97,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
</Only> </Only>
<div className="watermark-container"> <div className="watermark-container">
<span className="normal-text"> <span className="normal-text">
<span className="icon-text"></span> by <span className="name-text">{userinfo?.username}</span> <span className="icon-text"></span> by <span className="name-text">{userinfo?.name}</span>
</span> </span>
</div> </div>
</div> </div>

@ -5,27 +5,27 @@ import useLoading from "../hooks/useLoading";
import Only from "./common/OnlyWhen"; import Only from "./common/OnlyWhen";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import toastHelper from "./Toast"; import toastHelper from "./Toast";
import { locationService, queryService } from "../services"; import { locationService, shortcutService } from "../services";
import showCreateQueryDialog from "./CreateQueryDialog"; import showCreateQueryDialog from "./CreateShortcutDialog";
import "../less/query-list.less"; import "../less/shortcut-list.less";
interface Props {} interface Props {}
const QueryList: React.FC<Props> = () => { const ShortcutList: React.FC<Props> = () => {
const { const {
queryState: { queries }, shortcutState: { shortcuts },
locationState: { locationState: {
query: { filter }, query: { shortcutId },
}, },
} = useContext(appContext); } = useContext(appContext);
const loadingState = useLoading(); const loadingState = useLoading();
const sortedQueries = queries const sortedShortcuts = shortcuts
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)) .sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt))
.sort((a, b) => utils.getTimeStampByDate(b.pinnedAt ?? 0) - utils.getTimeStampByDate(a.pinnedAt ?? 0)); .sort((a, b) => utils.getTimeStampByDate(b.updatedAt) - utils.getTimeStampByDate(a.updatedAt));
useEffect(() => { useEffect(() => {
queryService shortcutService
.getMyAllQueries() .getMyAllShortcuts()
.catch(() => { .catch(() => {
// do nth // do nth
}) })
@ -35,47 +35,47 @@ const QueryList: React.FC<Props> = () => {
}, []); }, []);
return ( return (
<div className="queries-wrapper"> <div className="shortcuts-wrapper">
<p className="title-text"> <p className="title-text">
<span className="normal-text"></span> <span className="normal-text"></span>
<span className="btn" onClick={() => showCreateQueryDialog()}> <span className="btn" onClick={() => showCreateQueryDialog()}>
+ +
</span> </span>
</p> </p>
<Only when={loadingState.isSucceed && sortedQueries.length === 0}> <Only when={loadingState.isSucceed && sortedShortcuts.length === 0}>
<div className="create-query-btn-container"> <div className="create-shortcut-btn-container">
<span className="btn" onClick={() => showCreateQueryDialog()}> <span className="btn" onClick={() => showCreateQueryDialog()}>
</span> </span>
</div> </div>
</Only> </Only>
<div className="queries-container"> <div className="shortcuts-container">
{sortedQueries.map((q) => { {sortedShortcuts.map((s) => {
return <QueryItemContainer key={q.id} query={q} isActive={q.id === filter} />; return <ShortcutContainer key={s.id} shortcut={s} isActive={s.id === shortcutId} />;
})} })}
</div> </div>
</div> </div>
); );
}; };
interface QueryItemContainerProps { interface ShortcutContainerProps {
query: Model.Query; shortcut: Model.Shortcut;
isActive: boolean; isActive: boolean;
} }
const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemContainerProps) => { const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
const { query, isActive } = props; const { shortcut, isActive } = props;
const [showActionBtns, toggleShowActionBtns] = useToggle(false); const [showActionBtns, toggleShowActionBtns] = useToggle(false);
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const handleQueryClick = () => { const handleQueryClick = () => {
if (isActive) { if (isActive) {
locationService.setMemoFilter(""); locationService.setMemoShortcut("");
} else { } else {
if (!["/", "/recycle"].includes(locationService.getState().pathname)) { if (!["/", "/recycle"].includes(locationService.getState().pathname)) {
locationService.setPathname("/"); locationService.setPathname("/");
} }
locationService.setMemoFilter(query.id); locationService.setMemoShortcut(shortcut.id);
} }
}; };
@ -93,7 +93,7 @@ const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemC
if (showConfirmDeleteBtn) { if (showConfirmDeleteBtn) {
try { try {
await queryService.deleteQuery(query.id); await shortcutService.deleteShortcut(shortcut.id);
} catch (error: any) { } catch (error: any) {
toastHelper.error(error.message); toastHelper.error(error.message);
} }
@ -104,24 +104,24 @@ const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemC
const handleEditQueryBtnClick = (event: React.MouseEvent) => { const handleEditQueryBtnClick = (event: React.MouseEvent) => {
event.stopPropagation(); event.stopPropagation();
showCreateQueryDialog(query.id); showCreateQueryDialog(shortcut.id);
}; };
const handlePinQueryBtnClick = async (event: React.MouseEvent) => { const handlePinQueryBtnClick = async (event: React.MouseEvent) => {
event.stopPropagation(); event.stopPropagation();
try { try {
if (query.pinnedAt) { if (shortcut.rowStatus === "ARCHIVED") {
await queryService.unpinQuery(query.id); await shortcutService.unpinShortcut(shortcut.id);
queryService.editQuery({ shortcutService.editShortcut({
...query, ...shortcut,
pinnedAt: "", rowStatus: "NORMAL",
}); });
} else { } else {
await queryService.pinQuery(query.id); await shortcutService.pinShortcut(shortcut.id);
queryService.editQuery({ shortcutService.editShortcut({
...query, ...shortcut,
pinnedAt: utils.getDateTimeString(Date.now()), rowStatus: "NORMAL",
}); });
} }
} catch (error) { } catch (error) {
@ -135,10 +135,10 @@ const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemC
return ( return (
<> <>
<div className={`query-item-container ${isActive ? "active" : ""}`} onClick={handleQueryClick}> <div className={`shortcut-container ${isActive ? "active" : ""}`} onClick={handleQueryClick}>
<div className="query-text-container"> <div className="shortcut-text-container">
<span className="icon-text">#</span> <span className="icon-text">#</span>
<span className="query-text">{query.title}</span> <span className="shortcut-text">{shortcut.title}</span>
</div> </div>
<div className="btns-container"> <div className="btns-container">
<span className="action-btn toggle-btn" onClick={handleShowActionBtnClick}> <span className="action-btn toggle-btn" onClick={handleShowActionBtnClick}>
@ -147,7 +147,7 @@ const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemC
<div className={`action-btns-wrapper ${showActionBtns ? "" : "hidden"}`} onMouseLeave={handleActionBtnContainerMouseLeave}> <div className={`action-btns-wrapper ${showActionBtns ? "" : "hidden"}`} onMouseLeave={handleActionBtnContainerMouseLeave}>
<div className="action-btns-container"> <div className="action-btns-container">
<span className="btn" onClick={handlePinQueryBtnClick}> <span className="btn" onClick={handlePinQueryBtnClick}>
{query.pinnedAt ? "取消置顶" : "置顶"} {shortcut.rowStatus === "ARCHIVED" ? "取消置顶" : "置顶"}
</span> </span>
<span className="btn" onClick={handleEditQueryBtnClick}> <span className="btn" onClick={handleEditQueryBtnClick}>
@ -167,4 +167,4 @@ const QueryItemContainer: React.FC<QueryItemContainerProps> = (props: QueryItemC
); );
}; };
export default QueryList; export default ShortcutList;

@ -3,7 +3,7 @@ import appContext from "../stores/appContext";
import { SHOW_SIDERBAR_MOBILE_CLASSNAME } from "../helpers/consts"; import { SHOW_SIDERBAR_MOBILE_CLASSNAME } from "../helpers/consts";
import { globalStateService } from "../services"; import { globalStateService } from "../services";
import UserBanner from "./UserBanner"; import UserBanner from "./UserBanner";
import QueryList from "./QueryList"; import ShortcutList from "./ShortcutList";
import TagList from "./TagList"; import TagList from "./TagList";
import UsageHeatMap from "./UsageHeatMap"; import UsageHeatMap from "./UsageHeatMap";
import "../less/siderbar.less"; import "../less/siderbar.less";
@ -66,7 +66,7 @@ const Sidebar: React.FC<Props> = () => {
<aside className="sidebar-wrapper" ref={wrapperElRef}> <aside className="sidebar-wrapper" ref={wrapperElRef}>
<UserBanner /> <UserBanner />
<UsageHeatMap /> <UsageHeatMap />
<QueryList /> <ShortcutList />
<TagList /> <TagList />
</aside> </aside>
); );

@ -13,7 +13,7 @@ const UserBanner: React.FC<Props> = () => {
memoState: { memos, tags }, memoState: { memos, tags },
userState: { user }, userState: { user },
} = useContext(appContext); } = useContext(appContext);
const username = user ? user.username : "Memos"; const username = user ? user.name : "Memos";
const createdDays = user ? Math.ceil((Date.now() - utils.getTimeStampByDate(user.createdAt)) / 1000 / 3600 / 24) : 0; const createdDays = user ? Math.ceil((Date.now() - utils.getTimeStampByDate(user.createdAt)) / 1000 / 3600 / 24) : 0;
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false); const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);

@ -1,9 +1,7 @@
import utils from "./utils"; type ResponseObject<T> = {
type ResponseType<T = unknown> = {
succeed: boolean;
message: string;
data: T; data: T;
error?: string;
message?: string;
}; };
type RequestConfig = { type RequestConfig = {
@ -13,7 +11,7 @@ type RequestConfig = {
dataType?: "json" | "file"; dataType?: "json" | "file";
}; };
async function request<T>(config: RequestConfig): Promise<ResponseType<T>> { async function request<T>(config: RequestConfig): Promise<T> {
const { method, url, data, dataType } = config; const { method, url, data, dataType } = config;
const requestConfig: RequestInit = { const requestConfig: RequestInit = {
method, method,
@ -31,13 +29,13 @@ async function request<T>(config: RequestConfig): Promise<ResponseType<T>> {
} }
const response = await fetch(url, requestConfig); const response = await fetch(url, requestConfig);
const responseData = (await response.json()) as ResponseType<T>; const responseData = (await response.json()) as ResponseObject<T>;
if (!responseData.succeed) { if (responseData.error || responseData.message) {
throw responseData; throw new Error(responseData.error || responseData.message);
} }
return responseData; return responseData.data;
} }
namespace api { namespace api {
@ -48,34 +46,42 @@ namespace api {
}); });
} }
export function signin(username: string, password: string) { export function login(name: string, password: string) {
return request({ return request<Model.User>({
method: "POST", method: "POST",
url: "/api/auth/signin", url: "/api/auth/login",
data: { username, password }, data: {
name,
password,
},
}); });
} }
export function signup(username: string, password: string) { export function signup(name: string, password: string) {
return request({ return request<Model.User>({
method: "POST", method: "POST",
url: "/api/auth/signup", url: "/api/auth/signup",
data: { username, password }, data: {
name,
password,
},
}); });
} }
export function signout() { export function signout() {
return request({ return request({
method: "POST", method: "POST",
url: "/api/auth/signout", url: "/api/auth/logout",
}); });
} }
export function checkUsernameUsable(username: string) { export function checkUsernameUsable(name: string) {
return request<boolean>({ return request<boolean>({
method: "POST", method: "POST",
url: "/api/user/checkusername", url: "/api/user/checkusername",
data: { username }, data: {
name,
},
}); });
} }
@ -83,11 +89,13 @@ namespace api {
return request<boolean>({ return request<boolean>({
method: "POST", method: "POST",
url: "/api/user/validpassword", url: "/api/user/validpassword",
data: { password }, data: {
password,
},
}); });
} }
export function updateUserinfo(userinfo: Partial<{ username: string; password: string }>) { export function updateUserinfo(userinfo: Partial<{ name: string; password: string }>) {
return request({ return request({
method: "PATCH", method: "PATCH",
url: "/api/user/me", url: "/api/user/me",
@ -105,22 +113,24 @@ namespace api {
export function getMyMemos() { export function getMyMemos() {
return request<Model.Memo[]>({ return request<Model.Memo[]>({
method: "GET", method: "GET",
url: "/api/memo/all", url: "/api/memo",
}); });
} }
export function getMyDeletedMemos() { export function getMyDeletedMemos() {
return request<Model.Memo[]>({ return request<Model.Memo[]>({
method: "GET", method: "GET",
url: "/api/memo/all?deleted=true", url: "/api/memo/?hidden=true",
}); });
} }
export function createMemo(content: string) { export function createMemo(content: string) {
return request<Model.Memo>({ return request<Model.Memo>({
method: "PUT", method: "POST",
url: "/api/memo/", url: "/api/memo",
data: { content }, data: {
content,
},
}); });
} }
@ -128,7 +138,9 @@ namespace api {
return request<Model.Memo>({ return request<Model.Memo>({
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
data: { content }, data: {
content,
},
}); });
} }
@ -137,7 +149,7 @@ namespace api {
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
data: { data: {
deletedAt: utils.getDateTimeString(Date.now()), rowStatus: "HIDDEN",
}, },
}); });
} }
@ -147,7 +159,7 @@ namespace api {
method: "PATCH", method: "PATCH",
url: `/api/memo/${memoId}`, url: `/api/memo/${memoId}`,
data: { data: {
deletedAt: "", rowStatus: "NORMAL",
}, },
}); });
} }
@ -159,56 +171,66 @@ namespace api {
}); });
} }
export function getMyQueries() { export function getMyShortcuts() {
return request<Model.Query[]>({ return request<Model.Shortcut[]>({
method: "GET", method: "GET",
url: "/api/query/all", url: "/api/shortcut",
}); });
} }
export function createQuery(title: string, querystring: string) { export function createShortcut(title: string, payload: string) {
return request<Model.Query>({ return request<Model.Shortcut>({
method: "PUT", method: "POST",
url: "/api/query/", url: "/api/shortcut",
data: { title, querystring }, data: {
title,
payload,
},
}); });
} }
export function updateQuery(queryId: string, title: string, querystring: string) { export function updateShortcut(shortcutId: string, title: string, payload: string) {
return request<Model.Query>({ return request<Model.Shortcut>({
method: "PATCH", method: "PATCH",
url: `/api/query/${queryId}`, url: `/api/shortcut/${shortcutId}`,
data: { title, querystring }, data: {
title,
payload,
},
}); });
} }
export function deleteQueryById(queryId: string) { export function deleteShortcutById(shortcutId: string) {
return request({ return request({
method: "DELETE", method: "DELETE",
url: `/api/query/${queryId}`, url: `/api/shortcut/${shortcutId}`,
}); });
} }
export function pinQuery(queryId: string) { export function pinShortcut(shortcutId: string) {
return request({ return request({
method: "PATCH", method: "PATCH",
url: `/api/query/${queryId}`, url: `/api/shortcut/${shortcutId}`,
data: { pinnedAt: utils.getDateTimeString(Date.now()) }, data: {
rowStatus: "ARCHIVED",
},
}); });
} }
export function unpinQuery(queryId: string) { export function unpinShortcut(shortcutId: string) {
return request({ return request({
method: "PATCH", method: "PATCH",
url: `/api/query/${queryId}`, url: `/api/shortcut/${shortcutId}`,
data: { pinnedAt: "" }, data: {
rowStatus: "NORMAL",
},
}); });
} }
export function uploadFile(formData: FormData) { export function uploadFile(formData: FormData) {
return request<Model.Resource>({ return request<Model.Resource>({
method: "PUT", method: "POST",
url: "/api/resource/", url: "/api/resource",
data: formData, data: formData,
dataType: "file", dataType: "file",
}); });

@ -43,6 +43,10 @@ namespace utils {
return `${year}/${month}/${date}`; return `${year}/${month}/${date}`;
} }
export function getDataStringWithTs(ts: number): string {
return getDateTimeString(ts * 1000);
}
export function getTimeString(t: Date | number | string): string { export function getTimeString(t: Date | number | string): string {
const d = new Date(getTimeStampByDate(t)); const d = new Date(getTimeStampByDate(t));

@ -1,6 +1,6 @@
@import "./mixin.less"; @import "./mixin.less";
.create-query-dialog { .create-shortcut-dialog {
> .dialog-container { > .dialog-container {
width: 420px; width: 420px;
@ -155,7 +155,7 @@
} }
@media only screen and (max-width: 875px) { @media only screen and (max-width: 875px) {
.dialog-wrapper.create-query-dialog { .dialog-wrapper.create-shortcut-dialog {
padding: 24px 16px; padding: 24px 16px;
padding-top: 64px; padding-top: 64px;
justify-content: unset; justify-content: unset;

@ -1,6 +1,6 @@
@import "./mixin.less"; @import "./mixin.less";
.queries-wrapper { .shortcuts-wrapper {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
width: 100%; width: 100%;
padding: 0 8px; padding: 0 8px;
@ -35,7 +35,7 @@
} }
} }
> .create-query-btn-container { > .create-shortcut-btn-container {
.flex(row, center, center); .flex(row, center, center);
width: 100%; width: 100%;
margin-top: 8px; margin-top: 8px;
@ -55,7 +55,7 @@
} }
} }
> .queries-container { > .shortcuts-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
position: relative; position: relative;
width: 100%; width: 100%;
@ -63,7 +63,7 @@
flex-wrap: nowrap; flex-wrap: nowrap;
margin-bottom: 8px; margin-bottom: 8px;
> .query-item-container { > .shortcut-container {
.flex(row, space-between, center); .flex(row, space-between, center);
width: 100%; width: 100%;
height: 40px; height: 40px;
@ -86,7 +86,7 @@
&.active { &.active {
background-color: @text-green !important; background-color: @text-green !important;
> .query-text-container { > .shortcut-text-container {
font-weight: bold; font-weight: bold;
> * { > * {
@ -95,7 +95,7 @@
} }
} }
> .query-text-container { > .shortcut-text-container {
.flex(row, flex-start, center); .flex(row, flex-start, center);
max-width: calc(100% - 24px); max-width: calc(100% - 24px);
color: @text-black; color: @text-black;
@ -110,7 +110,7 @@
flex-shrink: 0; flex-shrink: 0;
} }
> .query-text { > .shortcut-text {
flex-shrink: 0; flex-shrink: 0;
} }
} }
@ -181,7 +181,7 @@
} }
@media only screen and (max-width: 875px) { @media only screen and (max-width: 875px) {
.queries-container { .shortcuts-container {
height: auto; height: auto;
&:last-child { &:last-child {
@ -192,7 +192,7 @@
font-size: 13px; font-size: 13px;
} }
> .query-item-container { > .shortcut-container {
font-size: 15px; font-size: 15px;
} }
} }

@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useState } from "react"; import { useCallback, useContext, useEffect, useState } from "react";
import appContext from "../stores/appContext"; import appContext from "../stores/appContext";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import { globalStateService, locationService, memoService, queryService } from "../services"; import { globalStateService, locationService, memoService, shortcutService } from "../services";
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
import utils from "../helpers/utils"; import utils from "../helpers/utils";
import { checkShouldShowMemoWithFilters } from "../helpers/filter"; import { checkShouldShowMemoWithFilters } from "../helpers/filter";
@ -21,8 +21,8 @@ const MemoTrash: React.FC<Props> = () => {
const loadingState = useLoading(); const loadingState = useLoading();
const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]); const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]);
const { tag: tagQuery, duration, type: memoType, text: textQuery, filter: queryId } = query; const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
const queryFilter = queryService.getQueryById(queryId); const queryFilter = shortcutService.getShortcutById(shortcutId);
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter); const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
const shownMemos = const shownMemos =
@ -31,7 +31,7 @@ const MemoTrash: React.FC<Props> = () => {
let shouldShow = true; let shouldShow = true;
if (queryFilter) { if (queryFilter) {
const filters = JSON.parse(queryFilter.querystring) as Filter[]; const filters = JSON.parse(queryFilter.payload) as Filter[];
if (Array.isArray(filters)) { if (Array.isArray(filters)) {
shouldShow = checkShouldShowMemoWithFilters(memo, filters); shouldShow = checkShouldShowMemoWithFilters(memo, filters);
} }

@ -67,16 +67,11 @@ const Signin: React.FC<Props> = () => {
try { try {
signinBtnsClickLoadingState.setLoading(); signinBtnsClickLoadingState.setLoading();
let actionFunc = api.signin; let actionFunc = api.login;
if (action === "signup") { if (action === "signup") {
actionFunc = api.signup; actionFunc = api.signup;
} }
const { succeed, message } = await actionFunc(username, password); await actionFunc(username, password);
if (!succeed && message) {
toastHelper.error("😟 " + message);
return;
}
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {
@ -106,12 +101,7 @@ const Signin: React.FC<Props> = () => {
try { try {
signinBtnsClickLoadingState.setLoading(); signinBtnsClickLoadingState.setLoading();
const { succeed, message } = await api.signin("guest", "123456"); await api.login("guest", "123456");
if (!succeed && message) {
toastHelper.error("😟 " + message);
return;
}
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {

@ -1,8 +1,8 @@
import globalStateService from "./globalStateService"; import globalStateService from "./globalStateService";
import locationService from "./locationService"; import locationService from "./locationService";
import memoService from "./memoService"; import memoService from "./memoService";
import queryService from "./queryService"; import shortcutService from "./shortcutService";
import userService from "./userService"; import userService from "./userService";
import resourceService from "./resourceService"; import resourceService from "./resourceService";
export { globalStateService, locationService, memoService, queryService, userService, resourceService }; export { globalStateService, locationService, memoService, shortcutService, userService, resourceService };

@ -36,13 +36,13 @@ class LocationService {
duration: null, duration: null,
text: "", text: "",
type: "", type: "",
filter: "", shortcutId: "",
}, },
}; };
state.query.tag = urlParams.get("tag") ?? ""; state.query.tag = urlParams.get("tag") ?? "";
state.query.type = (urlParams.get("type") ?? "") as MemoSpecType; state.query.type = (urlParams.get("type") ?? "") as MemoSpecType;
state.query.text = urlParams.get("text") ?? ""; state.query.text = urlParams.get("text") ?? "";
state.query.filter = urlParams.get("filter") ?? ""; state.query.shortcutId = urlParams.get("filter") ?? "";
const from = parseInt(urlParams.get("from") ?? "0"); const from = parseInt(urlParams.get("from") ?? "0");
const to = parseInt(urlParams.get("to") ?? "0"); const to = parseInt(urlParams.get("to") ?? "0");
if (to > from && to !== 0) { if (to > from && to !== 0) {
@ -71,7 +71,7 @@ class LocationService {
duration: null, duration: null,
text: "", text: "",
type: "", type: "",
filter: "", shortcutId: "",
}, },
}); });
@ -142,10 +142,10 @@ class LocationService {
updateLocationUrl(); updateLocationUrl();
}; };
public setMemoFilter = (filterId: string) => { public setMemoShortcut = (shortcutId: string) => {
appStore.dispatch({ appStore.dispatch({
type: "SET_QUERY_FILTER", type: "SET_SHORTCUT_ID",
payload: filterId, payload: shortcutId,
}); });
updateLocationUrl(); updateLocationUrl();

@ -16,11 +16,10 @@ class MemoService {
return false; return false;
} }
const { data } = await api.getMyMemos(); const data = await api.getMyMemos();
const memos = []; const memos: Model.Memo[] = data.map((m) => {
for (const m of data) { return this.convertResponseModelMemo(m);
memos.push(m); });
}
appStore.dispatch({ appStore.dispatch({
type: "SET_MEMOS", type: "SET_MEMOS",
payload: { payload: {
@ -40,9 +39,11 @@ class MemoService {
return false; return false;
} }
const { data } = await api.getMyDeletedMemos(); const data = await api.getMyDeletedMemos();
data.sort((a, b) => utils.getTimeStampByDate(b.deletedAt) - utils.getTimeStampByDate(a.deletedAt)); const deletedMemos: Model.Memo[] = data.map((m) => {
return data; return this.convertResponseModelMemo(m);
});
return deletedMemos;
} }
public pushMemo(memo: Model.Memo) { public pushMemo(memo: Model.Memo) {
@ -125,13 +126,21 @@ class MemoService {
} }
public async createMemo(text: string): Promise<Model.Memo> { public async createMemo(text: string): Promise<Model.Memo> {
const { data: memo } = await api.createMemo(text); const memo = await api.createMemo(text);
return memo; return this.convertResponseModelMemo(memo);
} }
public async updateMemo(memoId: string, text: string): Promise<Model.Memo> { public async updateMemo(memoId: string, text: string): Promise<Model.Memo> {
const { data: memo } = await api.updateMemo(memoId, text); const memo = await api.updateMemo(memoId, text);
return memo; return this.convertResponseModelMemo(memo);
}
private convertResponseModelMemo(memo: Model.Memo): Model.Memo {
return {
...memo,
createdAt: utils.getDataStringWithTs(memo.createdTs),
updatedAt: utils.getDataStringWithTs(memo.updatedTs),
};
} }
} }

@ -1,82 +0,0 @@
import userService from "./userService";
import api from "../helpers/api";
import appStore from "../stores/appStore";
class QueryService {
public getState() {
return appStore.getState().queryState;
}
public async getMyAllQueries() {
if (!userService.getState().user) {
return false;
}
const { data } = await api.getMyQueries();
appStore.dispatch({
type: "SET_QUERIES",
payload: {
queries: data,
},
});
return data;
}
public getQueryById(id: string) {
for (const q of this.getState().queries) {
if (q.id === id) {
return q;
}
}
}
public pushQuery(query: Model.Query) {
appStore.dispatch({
type: "INSERT_QUERY",
payload: {
query: {
...query,
},
},
});
}
public editQuery(query: Model.Query) {
appStore.dispatch({
type: "UPDATE_QUERY",
payload: query,
});
}
public async deleteQuery(queryId: string) {
await api.deleteQueryById(queryId);
appStore.dispatch({
type: "DELETE_QUERY_BY_ID",
payload: {
id: queryId,
},
});
}
public async createQuery(title: string, querystring: string) {
const { data } = await api.createQuery(title, querystring);
return data;
}
public async updateQuery(queryId: string, title: string, querystring: string) {
const { data } = await api.updateQuery(queryId, title, querystring);
return data;
}
public async pinQuery(queryId: string) {
await api.pinQuery(queryId);
}
public async unpinQuery(queryId: string) {
await api.unpinQuery(queryId);
}
}
const queryService = new QueryService();
export default queryService;

@ -17,7 +17,7 @@ class ResourceService {
formData.append("file", file, filename); formData.append("file", file, filename);
const { data } = await api.uploadFile(formData); const data = await api.uploadFile(formData);
return data; return data;
} }

@ -0,0 +1,93 @@
import userService from "./userService";
import api from "../helpers/api";
import appStore from "../stores/appStore";
import utils from "../helpers/utils";
class ShortcutService {
public getState() {
return appStore.getState().shortcutState;
}
public async getMyAllShortcuts() {
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;
}
public getShortcutById(id: string) {
for (const q of this.getState().shortcuts) {
if (q.id === id) {
return q;
}
}
return null;
}
public pushShortcut(shortcut: Model.Shortcut) {
appStore.dispatch({
type: "INSERT_SHORTCUT",
payload: {
shortcut: {
...shortcut,
},
},
});
}
public editShortcut(shortcut: Model.Shortcut) {
appStore.dispatch({
type: "UPDATE_SHORTCUT",
payload: shortcut,
});
}
public async deleteShortcut(shortcutId: string) {
await api.deleteShortcutById(shortcutId);
appStore.dispatch({
type: "DELETE_SHORTCUT_BY_ID",
payload: {
id: shortcutId,
},
});
}
public async createShortcut(title: string, shortcutstring: string) {
const data = await api.createShortcut(title, shortcutstring);
return data;
}
public async updateShortcut(shortcutId: string, title: string, shortcutstring: string) {
const data = await api.updateShortcut(shortcutId, title, shortcutstring);
return data;
}
public async pinShortcut(shortcutId: string) {
await api.pinShortcut(shortcutId);
}
public async unpinShortcut(shortcutId: string) {
await api.unpinShortcut(shortcutId);
}
public convertResponseModelShortcut(shortcut: Model.Shortcut): Model.Shortcut {
return {
...shortcut,
createdAt: utils.getDataStringWithTs(shortcut.createdTs),
updatedAt: utils.getDataStringWithTs(shortcut.updatedTs),
};
}
}
const shortcutService = new ShortcutService();
export default shortcutService;

@ -1,4 +1,5 @@
import api from "../helpers/api"; import api from "../helpers/api";
import utils from "../helpers/utils";
import appStore from "../stores/appStore"; import appStore from "../stores/appStore";
class UserService { class UserService {
@ -7,11 +8,13 @@ class UserService {
} }
public async doSignIn() { public async doSignIn() {
const { data: user } = await api.getUserInfo(); const user = await api.getUserInfo();
if (user) { if (user) {
appStore.dispatch({ appStore.dispatch({
type: "SIGN_IN", type: "LOGIN",
payload: { user }, payload: {
user: this.convertResponseModelUser(user),
},
}); });
} else { } else {
userService.doSignOut(); userService.doSignOut();
@ -30,18 +33,18 @@ class UserService {
} }
public async checkUsernameUsable(username: string): Promise<boolean> { public async checkUsernameUsable(username: string): Promise<boolean> {
const { data: isUsable } = await api.checkUsernameUsable(username); const isUsable = await api.checkUsernameUsable(username);
return isUsable; return isUsable;
} }
public async updateUsername(username: string): Promise<void> { public async updateUsername(name: string): Promise<void> {
await api.updateUserinfo({ await api.updateUserinfo({
username, name,
}); });
} }
public async checkPasswordValid(password: string): Promise<boolean> { public async checkPasswordValid(password: string): Promise<boolean> {
const { data: isValid } = await api.checkPasswordValid(password); const isValid = await api.checkPasswordValid(password);
return isValid; return isValid;
} }
@ -52,13 +55,21 @@ class UserService {
} }
public async resetOpenId(): Promise<string> { public async resetOpenId(): Promise<string> {
const { data: openId } = await api.resetOpenId(); const openId = await api.resetOpenId();
appStore.dispatch({ appStore.dispatch({
type: "RESET_OPENID", type: "RESET_OPENID",
payload: openId, payload: openId,
}); });
return openId; return openId;
} }
private convertResponseModelUser(user: Model.User): Model.User {
return {
...user,
createdAt: utils.getDataStringWithTs(user.createdTs),
updatedAt: utils.getDataStringWithTs(user.updatedTs),
};
}
} }
const userService = new UserService(); const userService = new UserService();

@ -4,17 +4,17 @@ import * as globalStore from "./globalStateStore";
import * as locationStore from "./locationStore"; import * as locationStore from "./locationStore";
import * as memoStore from "./memoStore"; import * as memoStore from "./memoStore";
import * as userStore from "./userStore"; import * as userStore from "./userStore";
import * as queryStore from "./queryStore"; import * as shortcutStore from "./shortcutStore";
interface AppState { interface AppState {
globalState: globalStore.State; globalState: globalStore.State;
locationState: locationStore.State; locationState: locationStore.State;
memoState: memoStore.State; memoState: memoStore.State;
userState: userStore.State; userState: userStore.State;
queryState: queryStore.State; shortcutState: shortcutStore.State;
} }
type AppStateActions = globalStore.Actions | locationStore.Actions | memoStore.Actions | userStore.Actions | queryStore.Actions; type AppStateActions = globalStore.Actions | locationStore.Actions | memoStore.Actions | userStore.Actions | shortcutStore.Actions;
const appStore = createStore<AppState, AppStateActions>( const appStore = createStore<AppState, AppStateActions>(
{ {
@ -22,14 +22,14 @@ const appStore = createStore<AppState, AppStateActions>(
locationState: locationStore.defaultState, locationState: locationStore.defaultState,
memoState: memoStore.defaultState, memoState: memoStore.defaultState,
userState: userStore.defaultState, userState: userStore.defaultState,
queryState: queryStore.defaultState, shortcutState: shortcutStore.defaultState,
}, },
combineReducers<AppState, AppStateActions>({ combineReducers<AppState, AppStateActions>({
globalState: globalStore.reducer, globalState: globalStore.reducer,
locationState: locationStore.reducer, locationState: locationStore.reducer,
memoState: memoStore.reducer, memoState: memoStore.reducer,
userState: userStore.reducer, userState: userStore.reducer,
queryState: queryStore.reducer, shortcutState: shortcutStore.reducer,
}) })
); );

@ -6,10 +6,10 @@ export interface AppSetting {
} }
export interface State extends AppSetting { export interface State extends AppSetting {
markMemoId: string;
editMemoId: string;
isMobileView: boolean; isMobileView: boolean;
showSiderbarInMobileView: boolean; showSiderbarInMobileView: boolean;
markMemoId: string;
editMemoId: string;
} }
interface SetMarkMemoIdAction { interface SetMarkMemoIdAction {

@ -1,6 +1,6 @@
export type State = AppLocation; export type State = AppLocation;
interface SetLocation { interface SetLocationAction {
type: "SET_LOCATION"; type: "SET_LOCATION";
payload: State; payload: State;
} }
@ -12,13 +12,13 @@ interface SetPathnameAction {
}; };
} }
interface SetQuery { interface SetQueryAction {
type: "SET_QUERY"; type: "SET_QUERY";
payload: Query; payload: Query;
} }
interface SetQueryFilterAction { interface SetShortcutIdAction {
type: "SET_QUERY_FILTER"; type: "SET_SHORTCUT_ID";
payload: string; payload: string;
} }
@ -58,14 +58,14 @@ interface SetHashAction {
} }
export type Actions = export type Actions =
| SetLocation | SetLocationAction
| SetPathnameAction | SetPathnameAction
| SetQuery | SetQueryAction
| SetTagQueryAction | SetTagQueryAction
| SetFromAndToQueryAction | SetFromAndToQueryAction
| SetTypeAction | SetTypeAction
| SetTextAction | SetTextAction
| SetQueryFilterAction | SetShortcutIdAction
| SetHashAction; | SetHashAction;
export function reducer(state: State, action: Actions) { export function reducer(state: State, action: Actions) {
@ -156,8 +156,8 @@ export function reducer(state: State, action: Actions) {
}, },
}; };
} }
case "SET_QUERY_FILTER": { case "SET_SHORTCUT_ID": {
if (action.payload === state.query.filter) { if (action.payload === state.query.shortcutId) {
return state; return state;
} }
@ -183,6 +183,6 @@ export const defaultState: State = {
duration: null, duration: null,
type: "", type: "",
text: "", text: "",
filter: "", shortcutId: "",
}, },
}; };

@ -1,92 +0,0 @@
import utils from "../helpers/utils";
export interface State {
queries: Model.Query[];
}
interface SetQueries {
type: "SET_QUERIES";
payload: {
queries: Model.Query[];
};
}
interface InsertQueryAction {
type: "INSERT_QUERY";
payload: {
query: Model.Query;
};
}
interface DeleteQueryByIdAction {
type: "DELETE_QUERY_BY_ID";
payload: {
id: string;
};
}
interface UpdateQueryAction {
type: "UPDATE_QUERY";
payload: Model.Query;
}
export type Actions = SetQueries | InsertQueryAction | DeleteQueryByIdAction | UpdateQueryAction;
export function reducer(state: State, action: Actions): State {
switch (action.type) {
case "SET_QUERIES": {
const queries = utils.dedupeObjectWithId(
action.payload.queries
.sort((a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt))
.sort((a, b) => utils.getTimeStampByDate(b.pinnedAt ?? 0) - utils.getTimeStampByDate(a.pinnedAt ?? 0))
);
return {
...state,
queries,
};
}
case "INSERT_QUERY": {
const queries = utils.dedupeObjectWithId(
[action.payload.query, ...state.queries].sort(
(a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)
)
);
return {
...state,
queries,
};
}
case "DELETE_QUERY_BY_ID": {
return {
...state,
queries: [...state.queries].filter((query) => query.id !== action.payload.id),
};
}
case "UPDATE_QUERY": {
const queries = state.queries.map((m) => {
if (m.id === action.payload.id) {
return {
...m,
...action.payload,
};
} else {
return m;
}
});
return {
...state,
queries,
};
}
default: {
return state;
}
}
}
export const defaultState: State = {
queries: [],
};

@ -0,0 +1,92 @@
import utils from "../helpers/utils";
export interface State {
shortcuts: Model.Shortcut[];
}
interface SetShortcutsAction {
type: "SET_SHORTCUTS";
payload: {
shortcuts: Model.Shortcut[];
};
}
interface InsertShortcutAction {
type: "INSERT_SHORTCUT";
payload: {
shortcut: Model.Shortcut;
};
}
interface DeleteShortcutByIdAction {
type: "DELETE_SHORTCUT_BY_ID";
payload: {
id: string;
};
}
interface UpdateShortcutAction {
type: "UPDATE_SHORTCUT";
payload: Model.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.createdAt) - utils.getTimeStampByDate(a.createdAt))
.sort((a, b) => utils.getTimeStampByDate(b.updatedAt) - utils.getTimeStampByDate(a.updatedAt))
);
return {
...state,
shortcuts,
};
}
case "INSERT_SHORTCUT": {
const shortcuts = utils.dedupeObjectWithId(
[action.payload.shortcut, ...state.shortcuts].sort(
(a, b) => utils.getTimeStampByDate(b.createdAt) - utils.getTimeStampByDate(a.createdAt)
)
);
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: [],
};

@ -3,7 +3,7 @@ export interface State {
} }
interface SignInAction { interface SignInAction {
type: "SIGN_IN"; type: "LOGIN";
payload: State; payload: State;
} }
@ -21,7 +21,7 @@ export type Actions = SignInAction | SignOutAction | ResetOpenIdAction;
export function reducer(state: State, action: Actions): State { export function reducer(state: State, action: Actions): State {
switch (action.type) { switch (action.type) {
case "SIGN_IN": { case "LOGIN": {
return { return {
user: action.payload.user, user: action.payload.user,
}; };

@ -1,6 +1 @@
declare namespace Api { declare namespace Api {}
interface MemosStat {
timestamp: string;
amount: number;
}
}

@ -8,7 +8,7 @@ interface Query {
duration: Duration | null; duration: Duration | null;
type: MemoSpecType | ""; type: MemoSpecType | "";
text: string; text: string;
filter: string; shortcutId: string;
} }
type AppRouter = "/" | "/signin" | "/recycle" | "/setting"; type AppRouter = "/" | "/signin" | "/recycle" | "/setting";

@ -1,28 +1,30 @@
declare namespace Model { declare namespace Model {
interface BaseModel { interface BaseModel {
id: string; id: string;
createdTs: number;
updatedTs: number;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
interface User extends BaseModel { interface User extends BaseModel {
username: string; name: string;
openId: string; openId: string;
} }
interface Memo extends BaseModel { interface Memo extends BaseModel {
content: string; content: string;
deletedAt: string; rowStatus: "NORMAL" | "HIDDEN";
} }
interface Query extends BaseModel { interface Shortcut extends BaseModel {
title: string; title: string;
querystring: string; payload: string;
pinnedAt: string; rowStatus: "NORMAL" | "ARCHIVED";
} }
interface Resource { interface Resource extends BaseModel {
id: string;
filename: string; filename: string;
type: string; type: string;
size: string; size: string;

@ -8,11 +8,11 @@ export default defineConfig({
cors: true, cors: true,
proxy: { proxy: {
"/api": { "/api": {
// target: "http://localhost:8080/", target: "http://localhost:8080/",
target: "https://memos.justsven.top/", // target: "https://memos.justsven.top/",
changeOrigin: true, changeOrigin: true,
}, },
"/r/": { "/h/": {
target: "https://memos.justsven.top/", target: "https://memos.justsven.top/",
changeOrigin: true, changeOrigin: true,
}, },

Loading…
Cancel
Save