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

@ -18,7 +18,7 @@ const DeletedMemo: React.FC<Props> = (props: Props) => {
const memo: FormattedMemo = {
...propsMemo,
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 imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []);

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

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

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

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

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

@ -97,7 +97,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
</Only>
<div className="watermark-container">
<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>
</div>
</div>

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

@ -13,7 +13,7 @@ const UserBanner: React.FC<Props> = () => {
memoState: { memos, tags },
userState: { user },
} = 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 [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);

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

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

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

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

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

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

@ -1,8 +1,8 @@
import globalStateService from "./globalStateService";
import locationService from "./locationService";
import memoService from "./memoService";
import queryService from "./queryService";
import shortcutService from "./shortcutService";
import userService from "./userService";
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,
text: "",
type: "",
filter: "",
shortcutId: "",
},
};
state.query.tag = urlParams.get("tag") ?? "";
state.query.type = (urlParams.get("type") ?? "") as MemoSpecType;
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 to = parseInt(urlParams.get("to") ?? "0");
if (to > from && to !== 0) {
@ -71,7 +71,7 @@ class LocationService {
duration: null,
text: "",
type: "",
filter: "",
shortcutId: "",
},
});
@ -142,10 +142,10 @@ class LocationService {
updateLocationUrl();
};
public setMemoFilter = (filterId: string) => {
public setMemoShortcut = (shortcutId: string) => {
appStore.dispatch({
type: "SET_QUERY_FILTER",
payload: filterId,
type: "SET_SHORTCUT_ID",
payload: shortcutId,
});
updateLocationUrl();

@ -16,11 +16,10 @@ class MemoService {
return false;
}
const { data } = await api.getMyMemos();
const memos = [];
for (const m of data) {
memos.push(m);
}
const data = await api.getMyMemos();
const memos: Model.Memo[] = data.map((m) => {
return this.convertResponseModelMemo(m);
});
appStore.dispatch({
type: "SET_MEMOS",
payload: {
@ -40,9 +39,11 @@ class MemoService {
return false;
}
const { data } = await api.getMyDeletedMemos();
data.sort((a, b) => utils.getTimeStampByDate(b.deletedAt) - utils.getTimeStampByDate(a.deletedAt));
return data;
const data = await api.getMyDeletedMemos();
const deletedMemos: Model.Memo[] = data.map((m) => {
return this.convertResponseModelMemo(m);
});
return deletedMemos;
}
public pushMemo(memo: Model.Memo) {
@ -125,13 +126,21 @@ class MemoService {
}
public async createMemo(text: string): Promise<Model.Memo> {
const { data: memo } = await api.createMemo(text);
return memo;
const memo = await api.createMemo(text);
return this.convertResponseModelMemo(memo);
}
public async updateMemo(memoId: string, text: string): Promise<Model.Memo> {
const { data: memo } = await api.updateMemo(memoId, text);
return memo;
const memo = await api.updateMemo(memoId, text);
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);
const { data } = await api.uploadFile(formData);
const data = await api.uploadFile(formData);
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 utils from "../helpers/utils";
import appStore from "../stores/appStore";
class UserService {
@ -7,11 +8,13 @@ class UserService {
}
public async doSignIn() {
const { data: user } = await api.getUserInfo();
const user = await api.getUserInfo();
if (user) {
appStore.dispatch({
type: "SIGN_IN",
payload: { user },
type: "LOGIN",
payload: {
user: this.convertResponseModelUser(user),
},
});
} else {
userService.doSignOut();
@ -30,18 +33,18 @@ class UserService {
}
public async checkUsernameUsable(username: string): Promise<boolean> {
const { data: isUsable } = await api.checkUsernameUsable(username);
const isUsable = await api.checkUsernameUsable(username);
return isUsable;
}
public async updateUsername(username: string): Promise<void> {
public async updateUsername(name: string): Promise<void> {
await api.updateUserinfo({
username,
name,
});
}
public async checkPasswordValid(password: string): Promise<boolean> {
const { data: isValid } = await api.checkPasswordValid(password);
const isValid = await api.checkPasswordValid(password);
return isValid;
}
@ -52,13 +55,21 @@ class UserService {
}
public async resetOpenId(): Promise<string> {
const { data: openId } = await api.resetOpenId();
const openId = await api.resetOpenId();
appStore.dispatch({
type: "RESET_OPENID",
payload: 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();

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

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

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

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

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

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

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

Loading…
Cancel
Save