mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: pagination for memo list (#330)
This commit is contained in:
@@ -203,6 +203,56 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.GET("/memo/stats", func(c echo.Context) error {
|
||||||
|
ctx := c.Request().Context()
|
||||||
|
normalStatus := api.Normal
|
||||||
|
memoFind := &api.MemoFind{
|
||||||
|
RowStatus: &normalStatus,
|
||||||
|
}
|
||||||
|
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||||
|
memoFind.CreatorID = &userID
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUserID, ok := c.Get(getUserIDContextKey()).(int)
|
||||||
|
if !ok {
|
||||||
|
if memoFind.CreatorID == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
|
||||||
|
}
|
||||||
|
memoFind.VisibilityList = []api.Visibility{api.Public}
|
||||||
|
} else {
|
||||||
|
if memoFind.CreatorID == nil {
|
||||||
|
memoFind.CreatorID = ¤tUserID
|
||||||
|
} else {
|
||||||
|
memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visibilitListStr := c.QueryParam("visibility")
|
||||||
|
if visibilitListStr != "" {
|
||||||
|
visibilityList := []api.Visibility{}
|
||||||
|
for _, visibility := range strings.Split(visibilitListStr, ",") {
|
||||||
|
visibilityList = append(visibilityList, api.Visibility(visibility))
|
||||||
|
}
|
||||||
|
memoFind.VisibilityList = visibilityList
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := s.Store.FindMemoList(ctx, memoFind)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayTsList := []int64{}
|
||||||
|
for _, memo := range list {
|
||||||
|
displayTsList = append(displayTsList, memo.DisplayTs)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
||||||
|
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(displayTsList)); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo stats response").SetInternal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
g.GET("/memo/all", func(c echo.Context) error {
|
g.GET("/memo/all", func(c echo.Context) error {
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
memoFind := &api.MemoFind{}
|
memoFind := &api.MemoFind{}
|
||||||
|
@@ -21,7 +21,6 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
|||||||
if (showConfirmDeleteBtn) {
|
if (showConfirmDeleteBtn) {
|
||||||
try {
|
try {
|
||||||
await memoService.deleteMemoById(memo.id);
|
await memoService.deleteMemoById(memo.id);
|
||||||
await memoService.fetchMemos();
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { memoService, shortcutService } from "../services";
|
||||||
|
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { TAG_REG, LINK_REG } from "../labs/marked/parser";
|
import { TAG_REG, LINK_REG } from "../labs/marked/parser";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
@@ -14,6 +15,7 @@ const MemoList = () => {
|
|||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
const memoDisplayTsOption = useAppSelector((state) => state.user.user?.setting.memoDisplayTsOption);
|
const memoDisplayTsOption = useAppSelector((state) => state.user.user?.setting.memoDisplayTsOption);
|
||||||
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
||||||
|
const [isComplete, setIsComplete] = useState<boolean>(false);
|
||||||
|
|
||||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query ?? {};
|
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query ?? {};
|
||||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
||||||
@@ -81,8 +83,12 @@ const MemoList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService
|
memoService
|
||||||
.fetchMemos()
|
.fetchMemos()
|
||||||
.then(() => {
|
.then((fetchedMemos) => {
|
||||||
// do nth
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
|
setIsComplete(true);
|
||||||
|
} else {
|
||||||
|
setIsComplete(false);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -97,6 +103,20 @@ const MemoList = () => {
|
|||||||
}
|
}
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
const handleFetchMoreClick = async () => {
|
||||||
|
try {
|
||||||
|
const fetchedMemos = await memoService.fetchMemos(DEFAULT_MEMO_LIMIT, memos.length);
|
||||||
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
|
setIsComplete(true);
|
||||||
|
} else {
|
||||||
|
setIsComplete(false);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
toastHelper.error(error.response.data.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="memo-list-container">
|
<div className="memo-list-container">
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
@@ -108,7 +128,21 @@ const MemoList = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="status-text-container">
|
<div className="status-text-container">
|
||||||
<p className="status-text">{sortedMemos.length === 0 ? t("message.no-memos") : showMemoFilter ? "" : t("message.memos-ready")}</p>
|
<p className="status-text">
|
||||||
|
{isComplete ? (
|
||||||
|
sortedMemos.length === 0 ? (
|
||||||
|
t("message.no-memos")
|
||||||
|
) : (
|
||||||
|
t("message.memos-ready")
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="cursor-pointer hover:text-green-600" onClick={handleFetchMoreClick}>
|
||||||
|
{t("memo-list.fetch-more")}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
|
import { getMemoStats } from "../helpers/api";
|
||||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import "../less/usage-heat-map.less";
|
import "../less/usage-heat-map.less";
|
||||||
@@ -39,15 +40,21 @@ const UsageHeatMap = () => {
|
|||||||
const containerElRef = useRef<HTMLDivElement>(null);
|
const containerElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestemp);
|
getMemoStats()
|
||||||
for (const m of memos) {
|
.then(({ data: { data } }) => {
|
||||||
const index = (utils.getDateStampByDate(m.displayTs) - beginDayTimestemp) / (1000 * 3600 * 24) - 1;
|
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestemp);
|
||||||
if (index >= 0) {
|
for (const record of data) {
|
||||||
newStat[index].count += 1;
|
const index = (utils.getDateStampByDate(record * 1000) - beginDayTimestemp) / (1000 * 3600 * 24) - 1;
|
||||||
}
|
if (index >= 0) {
|
||||||
}
|
newStat[index].count += 1;
|
||||||
setAllStat([...newStat]);
|
}
|
||||||
}, [memos]);
|
}
|
||||||
|
setAllStat([...newStat]);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}, [memos.length]);
|
||||||
|
|
||||||
const handleUsageStatItemMouseEnter = useCallback((event: React.MouseEvent, item: DailyUsageStat) => {
|
const handleUsageStatItemMouseEnter = useCallback((event: React.MouseEvent, item: DailyUsageStat) => {
|
||||||
const tempDiv = document.createElement("div");
|
const tempDiv = document.createElement("div");
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { getMemoStats } from "../helpers/api";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import userService from "../services/userService";
|
import userService from "../services/userService";
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
@@ -18,6 +19,7 @@ const UserBanner = () => {
|
|||||||
const { user, owner } = useAppSelector((state) => state.user);
|
const { user, owner } = useAppSelector((state) => state.user);
|
||||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
const { memos, tags } = useAppSelector((state) => state.memo);
|
||||||
const [username, setUsername] = useState("Memos");
|
const [username, setUsername] = useState("Memos");
|
||||||
|
const [memoAmount, setMemoAmount] = useState(0);
|
||||||
const [createdDays, setCreatedDays] = useState(0);
|
const [createdDays, setCreatedDays] = useState(0);
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userService.isVisitorMode();
|
||||||
|
|
||||||
@@ -34,6 +36,16 @@ const UserBanner = () => {
|
|||||||
}
|
}
|
||||||
}, [isVisitorMode, user, owner]);
|
}, [isVisitorMode, user, owner]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMemoStats()
|
||||||
|
.then(({ data: { data } }) => {
|
||||||
|
setMemoAmount(data.length);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}, [memos]);
|
||||||
|
|
||||||
const handleUsernameClick = useCallback(() => {
|
const handleUsernameClick = useCallback(() => {
|
||||||
locationService.clearQuery();
|
locationService.clearQuery();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -109,7 +121,7 @@ const UserBanner = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="amount-text-container">
|
<div className="amount-text-container">
|
||||||
<div className="status-text memos-text">
|
<div className="status-text memos-text">
|
||||||
<span className="amount-text">{memos.length}</span>
|
<span className="amount-text">{memoAmount}</span>
|
||||||
<span className="type-text">{t("amount-text.memo")}</span>
|
<span className="type-text">{t("amount-text.memo")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="status-text tags-text">
|
<div className="status-text tags-text">
|
||||||
|
@@ -58,8 +58,16 @@ export function deleteUser(userDelete: UserDelete) {
|
|||||||
return axios.delete(`/api/user/${userDelete.id}`);
|
return axios.delete(`/api/user/${userDelete.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllMemos() {
|
export function getAllMemos(memoFind?: MemoFind) {
|
||||||
return axios.get<ResponseObject<Memo[]>>("/api/memo/all");
|
const queryList = [];
|
||||||
|
if (memoFind?.offset) {
|
||||||
|
queryList.push(`offset=${memoFind.offset}`);
|
||||||
|
}
|
||||||
|
if (memoFind?.limit) {
|
||||||
|
queryList.push(`limit=${memoFind.limit}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.get<ResponseObject<Memo[]>>(`/api/memo/all?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMemoList(memoFind?: MemoFind) {
|
export function getMemoList(memoFind?: MemoFind) {
|
||||||
@@ -70,9 +78,19 @@ export function getMemoList(memoFind?: MemoFind) {
|
|||||||
if (memoFind?.rowStatus) {
|
if (memoFind?.rowStatus) {
|
||||||
queryList.push(`rowStatus=${memoFind.rowStatus}`);
|
queryList.push(`rowStatus=${memoFind.rowStatus}`);
|
||||||
}
|
}
|
||||||
|
if (memoFind?.offset) {
|
||||||
|
queryList.push(`offset=${memoFind.offset}`);
|
||||||
|
}
|
||||||
|
if (memoFind?.limit) {
|
||||||
|
queryList.push(`limit=${memoFind.limit}`);
|
||||||
|
}
|
||||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo?${queryList.join("&")}`);
|
return axios.get<ResponseObject<Memo[]>>(`/api/memo?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMemoStats() {
|
||||||
|
return axios.get<ResponseObject<number[]>>(`/api/memo/stats`);
|
||||||
|
}
|
||||||
|
|
||||||
export function getMemoById(id: MemoId) {
|
export function getMemoById(id: MemoId) {
|
||||||
return axios.get<ResponseObject<Memo>>(`/api/memo/${id}`);
|
return axios.get<ResponseObject<Memo>>(`/api/memo/${id}`);
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,11 @@
|
|||||||
.ol-block,
|
.ol-block,
|
||||||
.ul-block,
|
.ul-block,
|
||||||
.todo-block {
|
.todo-block {
|
||||||
@apply inline-block box-border text-center w-7 font-mono select-none;
|
@apply inline-block box-border text-right w-8 mr-px font-mono select-none whitespace-nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ul-block {
|
||||||
|
@apply text-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-block {
|
.todo-block {
|
||||||
|
@@ -88,7 +88,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memo-list": {
|
"memo-list": {
|
||||||
"fetching-data": "fetching data..."
|
"fetching-data": "fetching data...",
|
||||||
|
"fetch-more": "Click here to fetch more"
|
||||||
},
|
},
|
||||||
"shortcut-list": {
|
"shortcut-list": {
|
||||||
"shortcut-title": "shortcut title",
|
"shortcut-title": "shortcut title",
|
||||||
|
@@ -88,7 +88,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memo-list": {
|
"memo-list": {
|
||||||
"fetching-data": "đang tải dữ liệu..."
|
"fetching-data": "đang tải dữ liệu...",
|
||||||
|
"fetch-more": "Click here to fetch more"
|
||||||
},
|
},
|
||||||
"shortcut-list": {
|
"shortcut-list": {
|
||||||
"shortcut-title": "Tên lối tắt",
|
"shortcut-title": "Tên lối tắt",
|
||||||
|
@@ -88,7 +88,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memo-list": {
|
"memo-list": {
|
||||||
"fetching-data": "请求数据中..."
|
"fetching-data": "请求数据中...",
|
||||||
|
"fetch-more": "Click here to fetch more"
|
||||||
},
|
},
|
||||||
"shortcut-list": {
|
"shortcut-list": {
|
||||||
"shortcut-title": "捷径标题",
|
"shortcut-title": "捷径标题",
|
||||||
|
@@ -3,8 +3,10 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { memoService } from "../services";
|
import { memoService } from "../services";
|
||||||
|
import { DEFAULT_MEMO_LIMIT } from "../services/memoService";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import toastHelper from "../components/Toast";
|
||||||
import MemoContent from "../components/MemoContent";
|
import MemoContent from "../components/MemoContent";
|
||||||
import MemoResources from "../components/MemoResources";
|
import MemoResources from "../components/MemoResources";
|
||||||
import "../less/explore.less";
|
import "../less/explore.less";
|
||||||
@@ -20,10 +22,14 @@ const Explore = () => {
|
|||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
memos: [],
|
memos: [],
|
||||||
});
|
});
|
||||||
|
const [isComplete, setIsComplete] = useState<boolean>(false);
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoService.fetchAllMemos().then((memos) => {
|
memoService.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length).then((memos) => {
|
||||||
|
if (memos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
|
setIsComplete(true);
|
||||||
|
}
|
||||||
setState({
|
setState({
|
||||||
memos,
|
memos,
|
||||||
});
|
});
|
||||||
@@ -31,6 +37,23 @@ const Explore = () => {
|
|||||||
});
|
});
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
|
const handleFetchMoreClick = async () => {
|
||||||
|
try {
|
||||||
|
const fetchedMemos = await memoService.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length);
|
||||||
|
if (fetchedMemos.length < DEFAULT_MEMO_LIMIT) {
|
||||||
|
setIsComplete(true);
|
||||||
|
} else {
|
||||||
|
setIsComplete(false);
|
||||||
|
}
|
||||||
|
setState({
|
||||||
|
memos: state.memos.concat(fetchedMemos),
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
toastHelper.error(error.response.data.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="page-wrapper explore">
|
<section className="page-wrapper explore">
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
@@ -53,25 +76,33 @@ const Explore = () => {
|
|||||||
</div>
|
</div>
|
||||||
{!loadingState.isLoading && (
|
{!loadingState.isLoading && (
|
||||||
<main className="memos-wrapper">
|
<main className="memos-wrapper">
|
||||||
{state.memos.length > 0 ? (
|
{state.memos.map((memo) => {
|
||||||
state.memos.map((memo) => {
|
const createdAtStr = dayjs(memo.displayTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss");
|
||||||
const createdAtStr = dayjs(memo.displayTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss");
|
return (
|
||||||
return (
|
<div className="memo-container" key={memo.id}>
|
||||||
<div className="memo-container" key={memo.id}>
|
<div className="memo-header">
|
||||||
<div className="memo-header">
|
<span className="time-text">{createdAtStr}</span>
|
||||||
<span className="time-text">{createdAtStr}</span>
|
<span className="split-text">by</span>
|
||||||
<span className="split-text">by</span>
|
<a className="name-text" href={`/u/${memo.creator.id}`}>
|
||||||
<a className="name-text" href={`/u/${memo.creator.id}`}>
|
{memo.creator.name}
|
||||||
{memo.creator.name}
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<MemoContent className="memo-content" content={memo.content} onMemoContentClick={() => undefined} />
|
|
||||||
<MemoResources memo={memo} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<MemoContent className="memo-content" content={memo.content} onMemoContentClick={() => undefined} />
|
||||||
})
|
<MemoResources memo={memo} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isComplete ? (
|
||||||
|
state.memos.length === 0 ? (
|
||||||
|
<p className="w-full text-center mt-12 text-gray-600">{t("message.no-memos")}</p>
|
||||||
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<p className="w-full text-center mt-12 text-gray-600">{t("message.no-memos")}</p>
|
<p
|
||||||
|
className="m-auto text-center mt-4 italic cursor-pointer text-gray-500 hover:text-green-600"
|
||||||
|
onClick={handleFetchMoreClick}
|
||||||
|
>
|
||||||
|
{t("memo-list.fetch-more")}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
)}
|
)}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { createMemo, patchMemo, setIsFetching, setMemos, setTags } from "../store/modules/memo";
|
import { createMemo, deleteMemo, patchMemo, setIsFetching, setMemos, setTags } from "../store/modules/memo";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import userService from "./userService";
|
import userService from "./userService";
|
||||||
|
|
||||||
|
export const DEFAULT_MEMO_LIMIT = 20;
|
||||||
|
|
||||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||||
return {
|
return {
|
||||||
...memo,
|
...memo,
|
||||||
@@ -17,32 +19,37 @@ const memoService = {
|
|||||||
return store.getState().memo;
|
return store.getState().memo;
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchAllMemos: async () => {
|
fetchMemos: async (limit = DEFAULT_MEMO_LIMIT, offset = 0) => {
|
||||||
const memoFind: MemoFind = {};
|
store.dispatch(setIsFetching(true));
|
||||||
if (userService.isVisitorMode()) {
|
|
||||||
memoFind.creatorId = userService.getUserIdFromPath();
|
|
||||||
}
|
|
||||||
const { data } = (await api.getAllMemos()).data;
|
|
||||||
const memos = data.map((m) => convertResponseModelMemo(m));
|
|
||||||
return memos;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchMemos: async () => {
|
|
||||||
const timeoutIndex = setTimeout(() => {
|
|
||||||
store.dispatch(setIsFetching(true));
|
|
||||||
}, 1000);
|
|
||||||
const memoFind: MemoFind = {
|
const memoFind: MemoFind = {
|
||||||
rowStatus: "NORMAL",
|
rowStatus: "NORMAL",
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
};
|
};
|
||||||
if (userService.isVisitorMode()) {
|
if (userService.isVisitorMode()) {
|
||||||
memoFind.creatorId = userService.getUserIdFromPath();
|
memoFind.creatorId = userService.getUserIdFromPath();
|
||||||
}
|
}
|
||||||
const { data } = (await api.getMemoList(memoFind)).data;
|
const { data } = (await api.getMemoList(memoFind)).data;
|
||||||
const memos = data.map((m) => convertResponseModelMemo(m));
|
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
|
||||||
store.dispatch(setMemos(memos));
|
if (offset === 0) {
|
||||||
clearTimeout(timeoutIndex);
|
store.dispatch(setMemos([]));
|
||||||
|
}
|
||||||
|
const memos = memoService.getState().memos;
|
||||||
|
store.dispatch(setMemos(memos.concat(fetchedMemos)));
|
||||||
store.dispatch(setIsFetching(false));
|
store.dispatch(setIsFetching(false));
|
||||||
|
|
||||||
|
return fetchedMemos;
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchAllMemos: async (limit = DEFAULT_MEMO_LIMIT, offset?: number) => {
|
||||||
|
const memoFind: MemoFind = {
|
||||||
|
rowStatus: "NORMAL",
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = (await api.getAllMemos(memoFind)).data;
|
||||||
|
const memos = data.map((m) => convertResponseModelMemo(m));
|
||||||
return memos;
|
return memos;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -129,6 +136,7 @@ const memoService = {
|
|||||||
|
|
||||||
deleteMemoById: async (memoId: MemoId) => {
|
deleteMemoById: async (memoId: MemoId) => {
|
||||||
await api.deleteMemo(memoId);
|
await api.deleteMemo(memoId);
|
||||||
|
store.dispatch(deleteMemo(memoId));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -44,6 +44,14 @@ const memoSlice = createSlice({
|
|||||||
.filter((memo) => memo.rowStatus === "NORMAL"),
|
.filter((memo) => memo.rowStatus === "NORMAL"),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
deleteMemo: (state, action: PayloadAction<MemoId>) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
memos: state.memos.filter((memo) => {
|
||||||
|
return memo.id !== action.payload;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
setTags: (state, action: PayloadAction<string[]>) => {
|
setTags: (state, action: PayloadAction<string[]>) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -59,6 +67,6 @@ const memoSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setMemos, createMemo, patchMemo, setTags, setIsFetching } = memoSlice.actions;
|
export const { setMemos, createMemo, patchMemo, deleteMemo, setTags, setIsFetching } = memoSlice.actions;
|
||||||
|
|
||||||
export default memoSlice.reducer;
|
export default memoSlice.reducer;
|
||||||
|
2
web/src/types/modules/memo.d.ts
vendored
2
web/src/types/modules/memo.d.ts
vendored
@@ -38,4 +38,6 @@ interface MemoFind {
|
|||||||
creatorId?: UserId;
|
creatorId?: UserId;
|
||||||
rowStatus?: RowStatus;
|
rowStatus?: RowStatus;
|
||||||
visibility?: Visibility;
|
visibility?: Visibility;
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user