support beforeCursor/prevCursor

This commit is contained in:
Philipinho
2026-01-29 20:53:18 +00:00
parent 9fc5f36bc2
commit cb10cf155f
18 changed files with 63 additions and 43 deletions
@@ -18,7 +18,6 @@ import {
IconFileDescription,
IconSearch,
IconCheck,
IconSparkles,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
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 classes from "./search-spotlight-filters.module.css";
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";
interface SearchSpotlightFiltersProps {
@@ -53,7 +52,6 @@ export function SearchSpotlightFilters({
const [workspace] = useAtom(workspaceAtom);
const { data: spacesData } = useGetSpacesQuery({
page: 1,
limit: 100,
query: debouncedSpaceQuery,
});
@@ -265,7 +263,9 @@ export function SearchSpotlightFilters({
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">
<div>
@@ -275,11 +275,13 @@ export function SearchSpotlightFilters({
{t("Enterprise")}
</Badge>
)}
{!option.disabled && isAiMode && option.value === "attachment" && (
<Text size="xs" mt={4}>
{t("Ask AI not available for attachments")}
</Text>
)}
{!option.disabled &&
isAiMode &&
option.value === "attachment" && (
<Text size="xs" mt={4}>
{t("Ask AI not available for attachments")}
</Text>
)}
</div>
{contentType === option.value && <IconCheck size={20} />}
</Group>
@@ -15,7 +15,7 @@ import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts
export default function SpaceGrid() {
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) => (
<Card
+2
View File
@@ -1,6 +1,7 @@
export interface QueryParams {
query?: string;
cursor?: string;
beforeCursor?: string;
limit?: number;
adminView?: boolean;
}
@@ -32,6 +33,7 @@ export type IPaginationMeta = {
hasNextPage: boolean;
hasPrevPage: boolean;
nextCursor: string | null;
prevCursor: string | null;
};
export type IPagination<T> = {
items: T[];
@@ -206,7 +206,8 @@ export class PageService {
return executeWithCursorPagination(query, {
perPage: 250,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'position', direction: 'asc', orderModifier: (ob) => ob.collate('C').asc() },
{ expression: 'id', direction: 'asc' },
@@ -66,7 +66,8 @@ export class WorkspaceInvitationService {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'asc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -110,9 +110,10 @@ type CursorPaginationResultRow<
type CursorPaginationMeta = {
limit: number;
hasNextPage?: boolean;
hasPrevPage?: boolean;
hasNextPage: boolean;
hasPrevPage: boolean;
nextCursor: string | null;
prevCursor: string | null;
};
export type CursorPaginationResult<
@@ -133,8 +134,8 @@ export async function executeWithCursorPagination<
qb: SelectQueryBuilder<DB, TB, O>,
opts: {
perPage: number;
after?: string;
before?: string;
cursor?: string;
beforeCursor?: string;
cursorPerRow?: TCursorKey;
fields: 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.before) qb = applyCursor(qb, opts.before, 'desc');
if (opts.cursor) qb = applyCursor(qb, opts.cursor, 'asc');
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) {
qb = qb.orderBy(
@@ -234,8 +235,7 @@ export async function executeWithCursorPagination<
const rows = await qb.limit(opts.perPage + 1).execute();
const hasNextPage = reversed ? true : rows.length > opts.perPage;
const hasPrevPage = !reversed ? undefined : rows.length > opts.perPage;
const hasNextPage = rows.length > opts.perPage;
// If we fetched an extra row to determine if we have a next page, that
// shouldn't be in the returned results
@@ -243,10 +243,11 @@ export async function executeWithCursorPagination<
if (reversed) rows.reverse();
//const startRow = rows[0];
const startRow = rows[0];
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;
return {
@@ -263,8 +264,9 @@ export async function executeWithCursorPagination<
meta: {
limit: opts.perPage,
hasNextPage,
hasPrevPage: hasPrevPage ?? !!opts.after,
hasPrevPage,
nextCursor,
prevCursor,
},
};
}
@@ -9,11 +9,6 @@ import {
} from 'class-validator';
export class PaginationOptions {
@IsOptional()
@IsNumber()
@Min(1)
page = 1;
@IsOptional()
@IsNumber()
@IsPositive()
@@ -25,6 +20,10 @@ export class PaginationOptions {
@IsString()
cursor?: string;
@IsOptional()
@IsString()
beforeCursor?: string;
@IsOptional()
@IsString()
query: string;
@@ -41,7 +41,8 @@ export class CommentRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'asc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -62,7 +62,8 @@ export class GroupUserRepo {
const result = await executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'users.id', direction: 'asc', key: 'id' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -127,7 +127,8 @@ export class GroupRepo {
const query = this.db.selectFrom(baseQuery.as('sub')).selectAll('sub');
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{
expression: 'sub.memberCount',
@@ -69,7 +69,8 @@ export class PageHistoryRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'desc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -285,7 +285,8 @@ export class PageRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'updatedAt', direction: 'desc' },
{ expression: 'id', direction: 'desc' },
@@ -307,7 +308,8 @@ export class PageRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'updatedAt', direction: 'desc' },
{ expression: 'id', direction: 'desc' },
@@ -347,7 +349,8 @@ export class PageRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'deletedAt', direction: 'desc' },
{ expression: 'id', direction: 'desc' },
@@ -147,7 +147,8 @@ export class ShareRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'updatedAt', direction: 'desc' },
{ expression: 'id', direction: 'desc' },
@@ -141,7 +141,8 @@ export class SpaceMemberRepo {
const result = await executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [
{ expression: 'sub.isGroup', direction: 'desc', key: 'isGroup' },
{ expression: 'sub.createdAt', direction: 'asc', key: 'createdAt' },
@@ -262,7 +263,8 @@ export class SpaceMemberRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'asc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -128,7 +128,8 @@ export class SpaceRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'asc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -163,7 +163,8 @@ export class UserRepo {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'asc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});
@@ -60,7 +60,8 @@ export class FileTaskController {
return executeWithCursorPagination(query, {
perPage: pagination.limit,
after: pagination.cursor,
cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor,
fields: [{ expression: 'id', direction: 'desc' }],
parseCursor: (cursor) => ({ id: cursor.id }),
});