Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c39833f
feat: pagination phase 1
martinjagodic Nov 3, 2025
319fd6c
feat: pagination phase 2
martinjagodic Nov 3, 2025
1d19cf5
feat: pagination phase 3
martinjagodic Nov 3, 2025
322b86d
feat: pagination phase 4
martinjagodic Nov 3, 2025
c97a457
feat: pagination phase 5
martinjagodic Nov 3, 2025
437d9b8
feat: pagination phase 5 fix
martinjagodic Nov 4, 2025
27b22dd
feat: pagination phase 5 fix 2
martinjagodic Nov 4, 2025
dba88f6
feat: pagination settings work
martinjagodic Nov 4, 2025
76c33d1
chore: remove user options
martinjagodic Nov 4, 2025
297a9a9
chore: refactor
martinjagodic Nov 4, 2025
d48ed1f
feat: grouping exception
martinjagodic Nov 5, 2025
3bb4124
feat: page size
martinjagodic Nov 5, 2025
a029291
feat: sorting + pagination
martinjagodic Nov 5, 2025
6f750b0
feat: filtering + pagiantion
martinjagodic Nov 5, 2025
2876d04
feat: make grouping + sorting + filtering + pagination work
martinjagodic Nov 5, 2025
8b02688
feat: add tests, refactor
martinjagodic Nov 5, 2025
52444da
chore: refactor
martinjagodic Nov 5, 2025
447d5c6
chore: remove pagination temp docs
martinjagodic Nov 5, 2025
7d01d75
Merge branch 'main' into pagination
martinjagodic Nov 5, 2025
79b17d6
chore: fix tests
martinjagodic Nov 5, 2025
e718ef6
feat: fix for i18n
martinjagodic Nov 11, 2025
c78e3d1
fix: show all sorted entries when pagination is disabled
martinjagodic Nov 11, 2025
6a6be97
Update packages/decap-cms-core/src/reducers/entries.ts
martinjagodic Nov 11, 2025
403dff9
Update packages/decap-cms-backend-test/src/implementation.ts
martinjagodic Nov 11, 2025
2354338
Update packages/decap-cms-core/src/components/Collection/Entries/Pagi…
martinjagodic Nov 11, 2025
420136b
Update packages/decap-cms-core/src/lib/immutableHelpers.ts
martinjagodic Nov 11, 2025
2c2a576
Update packages/decap-cms-backend-test/src/implementation.ts
martinjagodic Nov 11, 2025
bb7db1f
fix: format, warnings
martinjagodic Nov 11, 2025
a5a29cd
chore: refactor and document pagination
martinjagodic Nov 11, 2025
a50e5ee
feat: add pagination docs
martinjagodic Nov 11, 2025
2460ef7
feat: pagination cache
martinjagodic Nov 11, 2025
11368fc
Merge branch 'main' into pagination
martinjagodic Nov 11, 2025
75f83ce
Merge branch 'main' into pagination
martinjagodic Nov 25, 2025
e95abe3
fix: create pagination state if it doesn't exist
martinjagodic Nov 26, 2025
7055918
feat: disable pagination on nested collections
martinjagodic Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dev-test/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ collections: # A list of collections the CMS should be able to edit
slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
summary: '{{title}} -- {{year}}/{{month}}/{{day}}'
create: true # Allow users to create new documents in this collection
pagination:
enabled: true
per_page: 10
editor:
visualEditing: true
view_filters:
Expand Down
13 changes: 11 additions & 2 deletions packages/decap-cms-backend-git-gateway/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,17 @@ export default class GitGateway implements Implementation {
return this.tokenPromise!();
}

async entriesByFolder(folder: string, extension: string, depth: number) {
return this.backend!.entriesByFolder(folder, extension, depth);
async entriesByFolder(
folder: string,
extension: string,
depth: number,
options?: {
page?: number;
pageSize?: number;
pagination?: boolean;
},
) {
return this.backend!.entriesByFolder(folder, extension, depth, options);
}
allEntriesByFolder(folder: string, extension: string, depth: number, pathRegex?: RegExp) {
return this.backend!.allEntriesByFolder(folder, extension, depth, pathRegex);
Expand Down
43 changes: 32 additions & 11 deletions packages/decap-cms-backend-github/src/implementation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,7 @@ export default class GitHub implements Implementation {
return Promise.resolve(this.token);
}

getCursorAndFiles = (files: ApiFile[], page: number) => {
const pageSize = 20;
getCursorAndFiles = (files: ApiFile[], page: number, pageSize = 20) => {
const count = files.length;
const pageCount = Math.ceil(files.length / pageSize);

Expand All @@ -413,8 +412,20 @@ export default class GitHub implements Implementation {
return { cursor, files: pageFiles };
};

async entriesByFolder(folder: string, extension: string, depth: number) {
async entriesByFolder(
folder: string,
extension: string,
depth: number,
options?: {
page?: number;
pageSize?: number;
pagination?: boolean;
},
) {
const repoURL = this.api!.originRepoURL;
const page = options?.page ?? 1;
const pageSize = options?.pageSize ?? 20;
const usePagination = options?.pagination ?? true;

let cursor: Cursor;

Expand All @@ -424,9 +435,18 @@ export default class GitHub implements Implementation {
depth,
}).then(files => {
const filtered = files.filter(file => filterByExtension(file, extension));
const result = this.getCursorAndFiles(filtered, 1);
cursor = result.cursor;
return result.files;

if (usePagination) {
// Paginated: return only the requested page
const result = this.getCursorAndFiles(filtered, page, pageSize);
cursor = result.cursor;
return result.files;
} else {
// Non-paginated: return all files (no slicing)
const result = this.getCursorAndFiles(filtered, 1, pageSize);
cursor = result.cursor;
return filtered;
}
});

const readFile = (path: string, id: string | null | undefined) =>
Expand Down Expand Up @@ -562,27 +582,28 @@ export default class GitHub implements Implementation {
async traverseCursor(cursor: Cursor, action: string) {
const meta = cursor.meta!;
const files = cursor.data!.get('files')!.toJS() as ApiFile[];
const pageSize = meta.get('pageSize') || 20;

let result: { cursor: Cursor; files: ApiFile[] };
switch (action) {
case 'first': {
result = this.getCursorAndFiles(files, 1);
result = this.getCursorAndFiles(files, 1, pageSize);
break;
}
case 'last': {
result = this.getCursorAndFiles(files, meta.get('pageCount'));
result = this.getCursorAndFiles(files, meta.get('pageCount'), pageSize);
break;
}
case 'next': {
result = this.getCursorAndFiles(files, meta.get('page') + 1);
result = this.getCursorAndFiles(files, meta.get('page') + 1, pageSize);
break;
}
case 'prev': {
result = this.getCursorAndFiles(files, meta.get('page') - 1);
result = this.getCursorAndFiles(files, meta.get('page') - 1, pageSize);
break;
}
default: {
result = this.getCursorAndFiles(files, 1);
result = this.getCursorAndFiles(files, 1, pageSize);
break;
}
}
Expand Down
13 changes: 11 additions & 2 deletions packages/decap-cms-backend-proxy/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,19 @@ export default class ProxyBackend implements Implementation {
}
}

entriesByFolder(folder: string, extension: string, depth: number) {
entriesByFolder(
folder: string,
extension: string,
depth: number,
options?: {
page?: number;
pageSize?: number;
pagination?: boolean;
},
) {
return this.request({
action: 'entriesByFolder',
params: { branch: this.branch, folder, extension, depth },
params: { branch: this.branch, folder, extension, depth, options },
});
}

Expand Down
39 changes: 31 additions & 8 deletions packages/decap-cms-backend-test/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,24 @@ function deleteFile(path: string, tree: RepoTree) {
unset(tree, path.split('/'));
}

const pageSize = 10;
const DEFAULT_PAGE_SIZE = 10;

function getCursor(
folder: string,
extension: string,
entries: ImplementationEntry[],
index: number,
depth: number,
pageSize = DEFAULT_PAGE_SIZE,
) {
Comment thread
martinjagodic marked this conversation as resolved.
const count = entries.length;
const pageCount = Math.floor(count / pageSize);
const pageCount = Math.ceil(count / pageSize);
return Cursor.create({
actions: [
...(index < pageCount ? ['next', 'last'] : []),
...(index > 0 ? ['prev', 'first'] : []),
],
meta: { index, count, pageSize, pageCount },
meta: { index, page: index + 1, count, pageSize, pageCount },
data: { folder, extension, index, pageCount, depth },
});
}
Expand Down Expand Up @@ -173,6 +174,9 @@ export default class TestBackend implements Implementation {
pageCount: number;
depth: number;
};
const meta = cursor.meta!;
const currentPageSize = meta.get('pageSize', DEFAULT_PAGE_SIZE);

const newIndex = (() => {
if (action === 'next') {
return (index as number) + 1;
Expand All @@ -194,19 +198,38 @@ export default class TestBackend implements Implementation {
data: f.content as string,
file: { path: f.path, id: f.path },
}));
const entries = allEntries.slice(newIndex * pageSize, newIndex * pageSize + pageSize);
const newCursor = getCursor(folder, extension, allEntries, newIndex, depth);
const entries = allEntries.slice(
newIndex * currentPageSize,
newIndex * currentPageSize + currentPageSize,
);
const newCursor = getCursor(folder, extension, allEntries, newIndex, depth, currentPageSize);
return Promise.resolve({ entries, cursor: newCursor });
}

entriesByFolder(folder: string, extension: string, depth: number) {
entriesByFolder(
folder: string,
extension: string,
depth: number,
options?: {
page?: number;
pageSize?: number;
pagination?: boolean;
},
) {
const files = folder ? getFolderFiles(window.repoFiles, folder, extension, depth) : [];
const entries = files.map(f => ({
data: f.content as string,
file: { path: f.path, id: f.path },
}));
const cursor = getCursor(folder, extension, entries, 0, depth);
const ret = take(entries, pageSize);
const pageSize = options?.pageSize ?? DEFAULT_PAGE_SIZE;
const page = options?.page ?? 1;
const usePagination = options?.pagination ?? true;

const cursor = getCursor(folder, extension, entries, page - 1, depth, pageSize);

// If pagination is enabled, return only the requested page
// Otherwise, return all entries (for backward compatibility)
const ret = usePagination ? take(entries.slice((page - 1) * pageSize), pageSize) : entries;
Comment thread
martinjagodic marked this conversation as resolved.
Outdated
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
ret[CURSOR_COMPATIBILITY_SYMBOL] = cursor;
Expand Down
Loading
Loading