Fix memo list count limit
This commit is contained in:
1 parent
35209f6194
commit
62263e178d
5 files changed
+102
-51
No files matched your search
+84
-44
@@ -696,7 +696,7 @@ app.get("/api/v1/memos", async (c) => {
|
||||
const notebookId = c.req.query("notebookId");
|
||||
const q = c.req.query("q")?.trim();
|
||||
const includeTrash = c.req.query("trash") === "1";
|
||||
const limit = clampNumber(Number(c.req.query("limit") ?? 80), 1, 100);
|
||||
const limit = clampNumber(Number(c.req.query("limit") ?? 5000), 1, 5000);
|
||||
const deletedClause = includeTrash ? "m.is_deleted = 1" : "m.is_deleted = 0";
|
||||
|
||||
if (q) {
|
||||
@@ -704,57 +704,97 @@ app.get("/api/v1/memos", async (c) => {
|
||||
const likeQuery = `%${escapeLike(q)}%`;
|
||||
|
||||
if (ftsQuery) {
|
||||
const rows = await c.env.DB.prepare(
|
||||
`WITH raw_matches(memo_id, rank) AS (
|
||||
SELECT memo_id, bm25(memos_fts)
|
||||
FROM memos_fts
|
||||
WHERE memos_fts MATCH ?
|
||||
const [rows, totalRow] = await Promise.all([
|
||||
c.env.DB.prepare(
|
||||
`WITH raw_matches(memo_id, rank) AS (
|
||||
SELECT memo_id, bm25(memos_fts)
|
||||
FROM memos_fts
|
||||
WHERE memos_fts MATCH ?
|
||||
|
||||
UNION ALL
|
||||
UNION ALL
|
||||
|
||||
SELECT m.id, 100.0
|
||||
FROM memos m
|
||||
SELECT m.id, 100.0
|
||||
FROM memos m
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE m.title LIKE ? ESCAPE '\\'
|
||||
OR c.content_text LIKE ? ESCAPE '\\'
|
||||
OR m.tags_json LIKE ? ESCAPE '\\'
|
||||
),
|
||||
search_matches AS (
|
||||
SELECT memo_id, MIN(rank) AS rank
|
||||
FROM raw_matches
|
||||
GROUP BY memo_id
|
||||
)
|
||||
SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
|
||||
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, c.revision
|
||||
FROM search_matches s
|
||||
INNER JOIN memos m ON m.id = s.memo_id
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE m.title LIKE ? ESCAPE '\\'
|
||||
OR c.content_text LIKE ? ESCAPE '\\'
|
||||
OR m.tags_json LIKE ? ESCAPE '\\'
|
||||
),
|
||||
search_matches AS (
|
||||
SELECT memo_id, MIN(rank) AS rank
|
||||
FROM raw_matches
|
||||
GROUP BY memo_id
|
||||
)
|
||||
SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
|
||||
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, c.revision
|
||||
FROM search_matches s
|
||||
INNER JOIN memos m ON m.id = s.memo_id
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)
|
||||
ORDER BY s.rank ASC, m.is_pinned DESC, m.updated_at DESC
|
||||
LIMIT ?`
|
||||
)
|
||||
.bind(ftsQuery, likeQuery, likeQuery, likeQuery, notebookId ?? null, notebookId ?? null, limit)
|
||||
.all<MemoSummaryRow>();
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)
|
||||
ORDER BY s.rank ASC, m.is_pinned DESC, m.updated_at DESC
|
||||
LIMIT ?`
|
||||
)
|
||||
.bind(ftsQuery, likeQuery, likeQuery, likeQuery, notebookId ?? null, notebookId ?? null, limit)
|
||||
.all<MemoSummaryRow>(),
|
||||
c.env.DB.prepare(
|
||||
`WITH raw_matches(memo_id) AS (
|
||||
SELECT memo_id
|
||||
FROM memos_fts
|
||||
WHERE memos_fts MATCH ?
|
||||
|
||||
return c.json({ memos: rows.results.map(mapMemoSummary) });
|
||||
UNION ALL
|
||||
|
||||
SELECT m.id
|
||||
FROM memos m
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE m.title LIKE ? ESCAPE '\\'
|
||||
OR c.content_text LIKE ? ESCAPE '\\'
|
||||
OR m.tags_json LIKE ? ESCAPE '\\'
|
||||
),
|
||||
search_matches AS (
|
||||
SELECT memo_id
|
||||
FROM raw_matches
|
||||
GROUP BY memo_id
|
||||
)
|
||||
SELECT COUNT(*) AS count
|
||||
FROM search_matches s
|
||||
INNER JOIN memos m ON m.id = s.memo_id
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)`
|
||||
)
|
||||
.bind(ftsQuery, likeQuery, likeQuery, likeQuery, notebookId ?? null, notebookId ?? null)
|
||||
.first<{ count: number }>(),
|
||||
]);
|
||||
|
||||
return c.json({ memos: rows.results.map(mapMemoSummary), totalCount: totalRow?.count ?? rows.results.length });
|
||||
}
|
||||
}
|
||||
|
||||
const rows = await c.env.DB.prepare(
|
||||
`SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
|
||||
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, c.revision
|
||||
FROM memos m
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)
|
||||
ORDER BY ${includeTrash ? "m.deleted_at DESC," : "m.is_pinned DESC,"} m.updated_at DESC
|
||||
LIMIT ?`
|
||||
)
|
||||
.bind(notebookId ?? null, notebookId ?? null, limit)
|
||||
.all<MemoSummaryRow>();
|
||||
const [rows, totalRow] = await Promise.all([
|
||||
c.env.DB.prepare(
|
||||
`SELECT m.id, m.notebook_id, m.title, m.excerpt, m.tags_json, m.is_pinned,
|
||||
m.is_archived, m.is_deleted, m.created_at, m.updated_at, m.deleted_at, c.revision
|
||||
FROM memos m
|
||||
INNER JOIN memo_contents c ON c.memo_id = m.id
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)
|
||||
ORDER BY ${includeTrash ? "m.deleted_at DESC," : "m.is_pinned DESC,"} m.updated_at DESC
|
||||
LIMIT ?`
|
||||
)
|
||||
.bind(notebookId ?? null, notebookId ?? null, limit)
|
||||
.all<MemoSummaryRow>(),
|
||||
c.env.DB.prepare(
|
||||
`SELECT COUNT(*) AS count
|
||||
FROM memos m
|
||||
WHERE ${deletedClause}
|
||||
AND (? IS NULL OR m.notebook_id = ?)`
|
||||
)
|
||||
.bind(notebookId ?? null, notebookId ?? null)
|
||||
.first<{ count: number }>(),
|
||||
]);
|
||||
|
||||
return c.json({ memos: rows.results.map(mapMemoSummary) });
|
||||
return c.json({ memos: rows.results.map(mapMemoSummary), totalCount: totalRow?.count ?? rows.results.length });
|
||||
});
|
||||
|
||||
app.post("/api/v1/memos", zValidator("json", MemoCreateSchema), async (c) => {
|
||||
|
||||
@@ -304,6 +304,7 @@ export const MemoListPane = ({
|
||||
notebooks,
|
||||
notebook,
|
||||
memos,
|
||||
totalMemoCount,
|
||||
selectedMemoId,
|
||||
selectedMemoIds,
|
||||
selectionMode,
|
||||
@@ -364,6 +365,7 @@ export const MemoListPane = ({
|
||||
notebooks: Notebook[];
|
||||
notebook: Notebook | null;
|
||||
memos: MemoSummary[];
|
||||
totalMemoCount: number;
|
||||
selectedMemoId: string | null;
|
||||
selectedMemoIds: Set<string>;
|
||||
selectionMode: boolean;
|
||||
@@ -445,7 +447,7 @@ export const MemoListPane = ({
|
||||
|
||||
const listTitle = view === "trash" ? "回收站" : notebook?.name ?? "全部笔记";
|
||||
const listContextLabel = view === "trash" ? "已删除笔记" : notebook ? "当前笔记本" : "所有笔记本";
|
||||
const listCountLabel = `${filteredMemos.length}${filterMode !== "all" ? ` / ${memos.length}` : ""} ${
|
||||
const listCountLabel = `${filteredMemos.length}${filterMode !== "all" || filteredMemos.length !== totalMemoCount ? ` / ${totalMemoCount}` : ""} ${
|
||||
view === "trash" ? "条已删除" : "条笔记"
|
||||
}`;
|
||||
const selectionCountLabel = getSelectionCountLabel(selectedMemoIds.size);
|
||||
|
||||
@@ -143,8 +143,9 @@ const cacheMemoDetail = (queryClient: QueryClient, memo: MemoDetail, view: MemoV
|
||||
const prependMemoSummaryToList = (queryClient: QueryClient, queryKey: readonly unknown[], memo: MemoDetail) => {
|
||||
const summary = memoToSummary(memo);
|
||||
|
||||
queryClient.setQueryData<{ memos: MemoSummary[] }>(queryKey, (current) => ({
|
||||
queryClient.setQueryData<{ memos: MemoSummary[]; totalCount?: number }>(queryKey, (current) => ({
|
||||
memos: [summary, ...(current?.memos ?? []).filter((item) => item.id !== memo.id)],
|
||||
totalCount: (current?.totalCount ?? current?.memos.length ?? 0) + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -918,6 +919,7 @@ export const WorkspaceApp = ({
|
||||
});
|
||||
|
||||
const memos = memosQuery.data?.memos ?? [];
|
||||
const totalMemoCount = memosQuery.data?.totalCount ?? memos.length;
|
||||
const selectedMemoIndex = selectedMemoId ? memos.findIndex((memo) => memo.id === selectedMemoId) : -1;
|
||||
const previousMemoId = selectedMemoIndex > 0 ? memos[selectedMemoIndex - 1]?.id : null;
|
||||
const nextMemoId =
|
||||
@@ -1040,12 +1042,13 @@ export const WorkspaceApp = ({
|
||||
]);
|
||||
|
||||
const memoIdSet = new Set(memoIds);
|
||||
const previousMemoQueries = queryClient.getQueriesData<{ memos: MemoSummary[] }>({ queryKey: ["memos"] });
|
||||
const previousMemoQueries = queryClient.getQueriesData<{ memos: MemoSummary[]; totalCount?: number }>({ queryKey: ["memos"] });
|
||||
const previousMemoDetailQueries = queryClient.getQueriesData<{ memo: MemoDetail }>({ queryKey: ["memo"] });
|
||||
|
||||
queryClient.setQueriesData<{ memos: MemoSummary[] }>({ queryKey: ["memos"] }, (current) =>
|
||||
queryClient.setQueriesData<{ memos: MemoSummary[]; totalCount?: number }>({ queryKey: ["memos"] }, (current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
memos: current.memos.map((memo) => (memoIdSet.has(memo.id) ? { ...memo, isPinned } : memo)),
|
||||
}
|
||||
: current
|
||||
@@ -1994,6 +1997,7 @@ export const WorkspaceApp = ({
|
||||
notebooks={notebooks}
|
||||
view={memoView}
|
||||
memos={memos}
|
||||
totalMemoCount={totalMemoCount}
|
||||
selectedMemoId={selectedMemoId}
|
||||
selectedMemoIds={selectedMemoIds}
|
||||
selectionMode={memoSelectionModeActive}
|
||||
|
||||
@@ -19,6 +19,7 @@ type ListNotebooksResponse = {
|
||||
|
||||
type ListMemosResponse = {
|
||||
memos: MemoSummary[];
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
type ListMemoRevisionsResponse = {
|
||||
|
||||
+7
-3
@@ -377,8 +377,8 @@
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"default": 80
|
||||
"maximum": 5000,
|
||||
"default": 5000
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -395,9 +395,13 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MemoSummary"
|
||||
}
|
||||
},
|
||||
"totalCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["memos"]
|
||||
"required": ["memos", "totalCount"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user