mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
support beforeCursor/prevCursor
This commit is contained in:
@@ -18,7 +18,6 @@ import {
|
|||||||
IconFileDescription,
|
IconFileDescription,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconSparkles,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
@@ -26,7 +25,7 @@ import { useGetSpacesQuery } from "@/features/space/queries/space-query";
|
|||||||
import { useLicense } from "@/ee/hooks/use-license";
|
import { useLicense } from "@/ee/hooks/use-license";
|
||||||
import classes from "./search-spotlight-filters.module.css";
|
import classes from "./search-spotlight-filters.module.css";
|
||||||
import { isCloud } from "@/lib/config.ts";
|
import { isCloud } from "@/lib/config.ts";
|
||||||
import { useAtom } from "jotai/index";
|
import { useAtom } from "jotai";
|
||||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
|
||||||
interface SearchSpotlightFiltersProps {
|
interface SearchSpotlightFiltersProps {
|
||||||
@@ -53,7 +52,6 @@ export function SearchSpotlightFilters({
|
|||||||
const [workspace] = useAtom(workspaceAtom);
|
const [workspace] = useAtom(workspaceAtom);
|
||||||
|
|
||||||
const { data: spacesData } = useGetSpacesQuery({
|
const { data: spacesData } = useGetSpacesQuery({
|
||||||
page: 1,
|
|
||||||
limit: 100,
|
limit: 100,
|
||||||
query: debouncedSpaceQuery,
|
query: debouncedSpaceQuery,
|
||||||
});
|
});
|
||||||
@@ -265,7 +263,9 @@ export function SearchSpotlightFilters({
|
|||||||
contentType !== option.value &&
|
contentType !== option.value &&
|
||||||
handleFilterChange("contentType", option.value)
|
handleFilterChange("contentType", option.value)
|
||||||
}
|
}
|
||||||
disabled={option.disabled || (isAiMode && option.value === "attachment")}
|
disabled={
|
||||||
|
option.disabled || (isAiMode && option.value === "attachment")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Group flex="1" gap="xs">
|
<Group flex="1" gap="xs">
|
||||||
<div>
|
<div>
|
||||||
@@ -275,11 +275,13 @@ export function SearchSpotlightFilters({
|
|||||||
{t("Enterprise")}
|
{t("Enterprise")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!option.disabled && isAiMode && option.value === "attachment" && (
|
{!option.disabled &&
|
||||||
<Text size="xs" mt={4}>
|
isAiMode &&
|
||||||
{t("Ask AI not available for attachments")}
|
option.value === "attachment" && (
|
||||||
</Text>
|
<Text size="xs" mt={4}>
|
||||||
)}
|
{t("Ask AI not available for attachments")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{contentType === option.value && <IconCheck size={20} />}
|
{contentType === option.value && <IconCheck size={20} />}
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts
|
|||||||
|
|
||||||
export default function SpaceGrid() {
|
export default function SpaceGrid() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, isLoading } = useGetSpacesQuery({ page: 1, limit: 10 });
|
const { data, isLoading } = useGetSpacesQuery({ limit: 10 });
|
||||||
|
|
||||||
const cards = data?.items.slice(0, 9).map((space, index) => (
|
const cards = data?.items.slice(0, 9).map((space, index) => (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface QueryParams {
|
export interface QueryParams {
|
||||||
query?: string;
|
query?: string;
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
|
beforeCursor?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
adminView?: boolean;
|
adminView?: boolean;
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,7 @@ export type IPaginationMeta = {
|
|||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
hasPrevPage: boolean;
|
hasPrevPage: boolean;
|
||||||
nextCursor: string | null;
|
nextCursor: string | null;
|
||||||
|
prevCursor: string | null;
|
||||||
};
|
};
|
||||||
export type IPagination<T> = {
|
export type IPagination<T> = {
|
||||||
items: T[];
|
items: T[];
|
||||||
|
|||||||
@@ -206,7 +206,8 @@ export class PageService {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: 250,
|
perPage: 250,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'position', direction: 'asc', orderModifier: (ob) => ob.collate('C').asc() },
|
{ expression: 'position', direction: 'asc', orderModifier: (ob) => ob.collate('C').asc() },
|
||||||
{ expression: 'id', direction: 'asc' },
|
{ expression: 'id', direction: 'asc' },
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export class WorkspaceInvitationService {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'asc' }],
|
fields: [{ expression: 'id', direction: 'asc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -110,9 +110,10 @@ type CursorPaginationResultRow<
|
|||||||
|
|
||||||
type CursorPaginationMeta = {
|
type CursorPaginationMeta = {
|
||||||
limit: number;
|
limit: number;
|
||||||
hasNextPage?: boolean;
|
hasNextPage: boolean;
|
||||||
hasPrevPage?: boolean;
|
hasPrevPage: boolean;
|
||||||
nextCursor: string | null;
|
nextCursor: string | null;
|
||||||
|
prevCursor: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CursorPaginationResult<
|
export type CursorPaginationResult<
|
||||||
@@ -133,8 +134,8 @@ export async function executeWithCursorPagination<
|
|||||||
qb: SelectQueryBuilder<DB, TB, O>,
|
qb: SelectQueryBuilder<DB, TB, O>,
|
||||||
opts: {
|
opts: {
|
||||||
perPage: number;
|
perPage: number;
|
||||||
after?: string;
|
cursor?: string;
|
||||||
before?: string;
|
beforeCursor?: string;
|
||||||
cursorPerRow?: TCursorKey;
|
cursorPerRow?: TCursorKey;
|
||||||
fields: TFields;
|
fields: TFields;
|
||||||
encodeCursor?: CursorEncoder<DB, TB, O, TFields>;
|
encodeCursor?: CursorEncoder<DB, TB, O, TFields>;
|
||||||
@@ -219,10 +220,10 @@ export async function executeWithCursorPagination<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.after) qb = applyCursor(qb, opts.after, 'asc');
|
if (opts.cursor) qb = applyCursor(qb, opts.cursor, 'asc');
|
||||||
if (opts.before) qb = applyCursor(qb, opts.before, 'desc');
|
if (opts.beforeCursor) qb = applyCursor(qb, opts.beforeCursor, 'desc');
|
||||||
|
|
||||||
const reversed = !!opts.before && !opts.after;
|
const reversed = !!opts.beforeCursor && !opts.cursor;
|
||||||
|
|
||||||
for (const { expression, direction, orderModifier } of fields) {
|
for (const { expression, direction, orderModifier } of fields) {
|
||||||
qb = qb.orderBy(
|
qb = qb.orderBy(
|
||||||
@@ -234,8 +235,7 @@ export async function executeWithCursorPagination<
|
|||||||
|
|
||||||
const rows = await qb.limit(opts.perPage + 1).execute();
|
const rows = await qb.limit(opts.perPage + 1).execute();
|
||||||
|
|
||||||
const hasNextPage = reversed ? true : rows.length > opts.perPage;
|
const hasNextPage = rows.length > opts.perPage;
|
||||||
const hasPrevPage = !reversed ? undefined : rows.length > opts.perPage;
|
|
||||||
|
|
||||||
// If we fetched an extra row to determine if we have a next page, that
|
// If we fetched an extra row to determine if we have a next page, that
|
||||||
// shouldn't be in the returned results
|
// shouldn't be in the returned results
|
||||||
@@ -243,10 +243,11 @@ export async function executeWithCursorPagination<
|
|||||||
|
|
||||||
if (reversed) rows.reverse();
|
if (reversed) rows.reverse();
|
||||||
|
|
||||||
//const startRow = rows[0];
|
const startRow = rows[0];
|
||||||
const endRow = rows[rows.length - 1];
|
const endRow = rows[rows.length - 1];
|
||||||
|
|
||||||
//const startCursor = startRow ? generateCursor(startRow) : null;
|
const hasPrevPage = !!opts.cursor;
|
||||||
|
const prevCursor = hasPrevPage && startRow ? generateCursor(startRow) : null;
|
||||||
const nextCursor = hasNextPage && endRow ? generateCursor(endRow) : null;
|
const nextCursor = hasNextPage && endRow ? generateCursor(endRow) : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -263,8 +264,9 @@ export async function executeWithCursorPagination<
|
|||||||
meta: {
|
meta: {
|
||||||
limit: opts.perPage,
|
limit: opts.perPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
hasPrevPage: hasPrevPage ?? !!opts.after,
|
hasPrevPage,
|
||||||
nextCursor,
|
nextCursor,
|
||||||
|
prevCursor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
export class PaginationOptions {
|
export class PaginationOptions {
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
page = 1;
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@@ -25,6 +20,10 @@ export class PaginationOptions {
|
|||||||
@IsString()
|
@IsString()
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
beforeCursor?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
query: string;
|
query: string;
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ export class CommentRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'asc' }],
|
fields: [{ expression: 'id', direction: 'asc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export class GroupUserRepo {
|
|||||||
|
|
||||||
const result = await executeWithCursorPagination(query, {
|
const result = await executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'users.id', direction: 'asc', key: 'id' }],
|
fields: [{ expression: 'users.id', direction: 'asc', key: 'id' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ export class GroupRepo {
|
|||||||
const query = this.db.selectFrom(baseQuery.as('sub')).selectAll('sub');
|
const query = this.db.selectFrom(baseQuery.as('sub')).selectAll('sub');
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
expression: 'sub.memberCount',
|
expression: 'sub.memberCount',
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ export class PageHistoryRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'desc' }],
|
fields: [{ expression: 'id', direction: 'desc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -285,7 +285,8 @@ export class PageRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'updatedAt', direction: 'desc' },
|
{ expression: 'updatedAt', direction: 'desc' },
|
||||||
{ expression: 'id', direction: 'desc' },
|
{ expression: 'id', direction: 'desc' },
|
||||||
@@ -307,7 +308,8 @@ export class PageRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'updatedAt', direction: 'desc' },
|
{ expression: 'updatedAt', direction: 'desc' },
|
||||||
{ expression: 'id', direction: 'desc' },
|
{ expression: 'id', direction: 'desc' },
|
||||||
@@ -347,7 +349,8 @@ export class PageRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'deletedAt', direction: 'desc' },
|
{ expression: 'deletedAt', direction: 'desc' },
|
||||||
{ expression: 'id', direction: 'desc' },
|
{ expression: 'id', direction: 'desc' },
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ export class ShareRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'updatedAt', direction: 'desc' },
|
{ expression: 'updatedAt', direction: 'desc' },
|
||||||
{ expression: 'id', direction: 'desc' },
|
{ expression: 'id', direction: 'desc' },
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ export class SpaceMemberRepo {
|
|||||||
|
|
||||||
const result = await executeWithCursorPagination(query, {
|
const result = await executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [
|
fields: [
|
||||||
{ expression: 'sub.isGroup', direction: 'desc', key: 'isGroup' },
|
{ expression: 'sub.isGroup', direction: 'desc', key: 'isGroup' },
|
||||||
{ expression: 'sub.createdAt', direction: 'asc', key: 'createdAt' },
|
{ expression: 'sub.createdAt', direction: 'asc', key: 'createdAt' },
|
||||||
@@ -262,7 +263,8 @@ export class SpaceMemberRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'asc' }],
|
fields: [{ expression: 'id', direction: 'asc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ export class SpaceRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'asc' }],
|
fields: [{ expression: 'id', direction: 'asc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -163,7 +163,8 @@ export class UserRepo {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'asc' }],
|
fields: [{ expression: 'id', direction: 'asc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: fb7257cb4c...9888832270
@@ -60,7 +60,8 @@ export class FileTaskController {
|
|||||||
|
|
||||||
return executeWithCursorPagination(query, {
|
return executeWithCursorPagination(query, {
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
after: pagination.cursor,
|
cursor: pagination.cursor,
|
||||||
|
beforeCursor: pagination.beforeCursor,
|
||||||
fields: [{ expression: 'id', direction: 'desc' }],
|
fields: [{ expression: 'id', direction: 'desc' }],
|
||||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user