@aglyn/aglyn 1.0.0-beta.146 → 1.0.0-beta.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/aglyn",
3
- "version": "1.0.0-beta.146",
3
+ "version": "1.0.0-beta.147",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -33,15 +33,15 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "dependencies": {
36
- "@aglyn/shared-data-enums": "1.0.0-beta.146",
37
- "@aglyn/shared-data-mdi": "1.0.0-beta.146",
38
- "@aglyn/shared-data-types": "1.0.0-beta.146",
39
- "@aglyn/shared-util-email": "1.0.0-beta.146",
40
- "@aglyn/shared-util-http": "1.0.0-beta.146",
41
- "@aglyn/shared-util-logger": "1.0.0-beta.146",
42
- "@aglyn/shared-util-timestamp": "1.0.0-beta.146",
43
- "@aglyn/shared-util-tools": "1.0.0-beta.146",
44
- "@aglyn/shared-util-vendor": "1.0.0-beta.146",
36
+ "@aglyn/shared-data-enums": "1.0.0-beta.147",
37
+ "@aglyn/shared-data-mdi": "1.0.0-beta.147",
38
+ "@aglyn/shared-data-types": "1.0.0-beta.147",
39
+ "@aglyn/shared-util-email": "1.0.0-beta.147",
40
+ "@aglyn/shared-util-http": "1.0.0-beta.147",
41
+ "@aglyn/shared-util-logger": "1.0.0-beta.147",
42
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.147",
43
+ "@aglyn/shared-util-tools": "1.0.0-beta.147",
44
+ "@aglyn/shared-util-vendor": "1.0.0-beta.147",
45
45
  "@data-driven-forms/react-form-renderer": "^4.2.0",
46
46
  "@msgpack/msgpack": "^3.1.3",
47
47
  "@types/unist": "^3.0.3",
@@ -140,6 +140,38 @@ export declare const COLLECTION_LIST_PAGE_SIZE = 10;
140
140
  * empty collection still has a page 1 (AGL-620).
141
141
  */
142
142
  export declare function collectionTotalPages(total: number, perPage: number): number;
143
+ /**
144
+ * The slice of a listing's entries that the page named by `pagination`
145
+ * actually shows (AGL-3213).
146
+ *
147
+ * The loader hands the WHOLE narrowed set down to compose, because
148
+ * {@link expandCollectionEntries} does its own windowing off the block's
149
+ * props — a Collection entries block may carry its own `perPage`, its own
150
+ * pinned `page`, a `firstPageOnly` switch and its own filters, none of which
151
+ * this function can see. Slicing before compose would double-window and empty
152
+ * every page after the first.
153
+ *
154
+ * Everything the loader serializes into page PROPS is downstream of compose,
155
+ * and there the full set is not merely wasted bytes — it is a false
156
+ * statement. The `ItemList` JSON-LD numbers its items
157
+ * `(page - 1) * perPage + index + 1` on the premise that `entries` is the
158
+ * window; handed all one hundred, `/changelog/page/7` published
159
+ * `numberOfItems: 100` and positions 61 through 160, most of them past the
160
+ * end of the collection. The legacy fallback renderer read it the same way
161
+ * and rendered the whole list on every page.
162
+ *
163
+ * So: the loader keeps the full set until compose has had it, and narrows
164
+ * HERE on the way into props, where `pagination` is the route's own answer to
165
+ * which page this is.
166
+ *
167
+ * An unpaginated listing (an entry route, the RSS feed) passes no pagination
168
+ * and gets its entries back untouched.
169
+ */
170
+ export declare function collectionEntriesPageWindow<T>(entries: readonly T[], pagination: {
171
+ page?: number;
172
+ perPage?: number;
173
+ windowStart?: number;
174
+ } | null | undefined): T[];
143
175
  /** Default/most related posts a Related posts block renders (AGL-582). */
144
176
  export declare const COLLECTION_RELATED_DEFAULT_LIMIT = 3;
145
177
  export declare const COLLECTION_RELATED_MAX = 12;
@@ -334,6 +366,17 @@ export interface CollectionEntriesSource {
334
366
  * wins: that is a deliberately pinned window, not a paginated list.
335
367
  */
336
368
  page?: number;
369
+ /**
370
+ * The absolute index `entries[0]` holds in the collection's own order
371
+ * (AGL-3213). Zero everywhere except a listing page past the cached read's
372
+ * bound, which is served by a window read for that page alone.
373
+ *
374
+ * Without it a windowed source is silently empty: every consumer here
375
+ * slices `[(page - 1) * perPage, …)` on the premise that `entries` starts
376
+ * at the beginning of the collection, so page 11 of a windowed ten-entry
377
+ * source slices from 100 and renders nothing.
378
+ */
379
+ windowStart?: number;
337
380
  }
338
381
  /**
339
382
  * Display name of an entry's category (AGL-582): the `categoryId` lookup
@@ -133,6 +133,48 @@ export const COLLECTION_CATEGORY_ROUTE_SEGMENT = 'category';
133
133
  if (perPage <= 0) return 1;
134
134
  return Math.max(1, Math.ceil(total / perPage));
135
135
  }
136
+ /**
137
+ * The slice of a listing's entries that the page named by `pagination`
138
+ * actually shows (AGL-3213).
139
+ *
140
+ * The loader hands the WHOLE narrowed set down to compose, because
141
+ * {@link expandCollectionEntries} does its own windowing off the block's
142
+ * props — a Collection entries block may carry its own `perPage`, its own
143
+ * pinned `page`, a `firstPageOnly` switch and its own filters, none of which
144
+ * this function can see. Slicing before compose would double-window and empty
145
+ * every page after the first.
146
+ *
147
+ * Everything the loader serializes into page PROPS is downstream of compose,
148
+ * and there the full set is not merely wasted bytes — it is a false
149
+ * statement. The `ItemList` JSON-LD numbers its items
150
+ * `(page - 1) * perPage + index + 1` on the premise that `entries` is the
151
+ * window; handed all one hundred, `/changelog/page/7` published
152
+ * `numberOfItems: 100` and positions 61 through 160, most of them past the
153
+ * end of the collection. The legacy fallback renderer read it the same way
154
+ * and rendered the whole list on every page.
155
+ *
156
+ * So: the loader keeps the full set until compose has had it, and narrows
157
+ * HERE on the way into props, where `pagination` is the route's own answer to
158
+ * which page this is.
159
+ *
160
+ * An unpaginated listing (an entry route, the RSS feed) passes no pagination
161
+ * and gets its entries back untouched.
162
+ */ export function collectionEntriesPageWindow(entries, pagination) {
163
+ const perPage = Number(pagination == null ? void 0 : pagination.perPage);
164
+ if (!Number.isFinite(perPage) || perPage <= 0) return [
165
+ ...entries
166
+ ];
167
+ const pageRaw = Number(pagination == null ? void 0 : pagination.page);
168
+ const page = Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1;
169
+ // `windowStart` is where `entries` begins in the collection's own order
170
+ // (AGL-3213). A listing past the cached read's bound is served by a window
171
+ // read for that page alone, and slicing it from the page's absolute offset
172
+ // would return nothing.
173
+ const startRaw = Number(pagination == null ? void 0 : pagination.windowStart);
174
+ const windowStart = Number.isFinite(startRaw) && startRaw > 0 ? Math.floor(startRaw) : 0;
175
+ const start = Math.max(0, (page - 1) * Math.floor(perPage) - windowStart);
176
+ return entries.slice(start, start + Math.floor(perPage));
177
+ }
136
178
  /** Default/most related posts a Related posts block renders (AGL-582). */ export const COLLECTION_RELATED_DEFAULT_LIMIT = 3;
137
179
  export const COLLECTION_RELATED_MAX = 12;
138
180
  /** Most categories a collection's taxonomy holds (AGL-582). */ export const COLLECTION_CATEGORIES_MAX = 50;
@@ -779,7 +821,12 @@ const POSITIVE_INTEGER = /^[1-9]\d*$/;
779
821
  // counting it, because the count cannot see a read that came back full
780
822
  // and was then thinned by the liveness gate.
781
823
  const sourceCapped = collectionSourceIsBounded(source);
782
- const windowed = suppressedBeyondFirstPage ? [] : perPage ? filtered.slice((page - 1) * perPage, (page - 1) * perPage + perPage) : filtered.slice(0, limit);
824
+ // Where this source's first entry sits in the collection (AGL-3213):
825
+ // zero for a whole read, and the page's own offset for a windowed one.
826
+ const windowStartRaw = Number(source.windowStart);
827
+ const windowStart = Number.isFinite(windowStartRaw) && windowStartRaw > 0 ? Math.floor(windowStartRaw) : 0;
828
+ const pageStart = perPage ? Math.max(0, (page - 1) * perPage - windowStart) : 0;
829
+ const windowed = suppressedBeyondFirstPage ? [] : perPage ? filtered.slice(pageStart, pageStart + perPage) : filtered.slice(0, limit);
783
830
  const childIds = [];
784
831
  windowed.forEach((entry, index)=>{
785
832
  const prefix = `${COLLECTION_ENTRIES_NODE_ID_PREFIX}${containerId}__${index}__`;