Fix memo list count limit

This commit is contained in:
yoma committed 2026-07-03 10:46:48 +08:00
1 parent 35209f6194
commit 62263e178d
5 files changed
+102 -51

No files matched your search

+84 -44
View File
@@ -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) => {
+3 -1
View File
@@ -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);
+7 -3
View File
@@ -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}
+1
View File
@@ -19,6 +19,7 @@ type ListNotebooksResponse = {
type ListMemosResponse = {
memos: MemoSummary[];
totalCount: number;
};
type ListMemoRevisionsResponse = {
+7 -3
View File
@@ -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"]
}
}
}