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, 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
+2
View File
@@ -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 }),
}); });
@@ -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 }),
}); });