@smartsoft001-mobilems/creators-shell-angular 2.123.0 → 2.125.0

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.
@@ -103,6 +103,28 @@ function creatorsFindPrefix(raw, prefixes) {
103
103
  const upper = raw.toLocaleUpperCase('pl');
104
104
  return (prefixes.find((prefix) => prefix.toLocaleUpperCase('pl') === upper) ?? null);
105
105
  }
106
+ /**
107
+ * A whole index letter split into the API groups (`prefixes`, as `getPrefixes`
108
+ * gives them), in their order; empty groups are left out. A creator whose group
109
+ * the API did not list (data drift) still shows, in a group of its own after the
110
+ * listed ones. Items keep their order (`getByIndexPrefix` sorts them).
111
+ */
112
+ function groupCreatorsByIndexPrefixes(items, prefixes) {
113
+ const groups = new Map(prefixes.map((prefix) => [prefix, []]));
114
+ for (const item of items) {
115
+ const own = creatorsIndexPrefixOf(item.name ?? '');
116
+ const prefix = creatorsFindPrefix(own, prefixes) ?? own;
117
+ const group = groups.get(prefix);
118
+ if (group)
119
+ group.push(item);
120
+ else
121
+ groups.set(prefix, [item]);
122
+ }
123
+ return Array.from(groups, ([prefix, groupItems]) => ({
124
+ prefix,
125
+ items: groupItems,
126
+ })).filter((group) => group.items.length > 0);
127
+ }
106
128
  /** `?litera=` query param to index letter; invalid or missing falls back to `A`. */
107
129
  function parseCreatorsLetter(raw) {
108
130
  const upper = (raw ?? '').toLocaleUpperCase('pl');
@@ -915,6 +937,15 @@ function createCreatorsCatalog(options) {
915
937
  const CREATORS_INDEX_LETTER_PARAM = 'litera';
916
938
  /** `?prefiks=`: the two-letter group of the letter the index shows (MOB-1959). */
917
939
  const CREATORS_INDEX_PREFIX_PARAM = 'prefiks';
940
+ /** A `#fragment` as written, decoded when it is a valid escape. */
941
+ function creatorsIndexDecode(fragment) {
942
+ try {
943
+ return decodeURIComponent(fragment);
944
+ }
945
+ catch {
946
+ return fragment;
947
+ }
948
+ }
918
949
  /** `?filter=` belongs to the catalog results view; the index stays idle while it is there. */
919
950
  const CREATORS_INDEX_FILTER_PARAM = 'filter';
920
951
  const CREATORS_INDEX_LOADING = {
@@ -1006,7 +1037,26 @@ class CreatorsIndexBaseComponent {
1006
1037
  this.groups = computed(() => {
1007
1038
  const { prefixes, prefix: shown, items } = this.state();
1008
1039
  const base = this.creatorsIndexAnchorBase;
1009
- return prefixes.map((prefix) => ({
1040
+ if (this.creatorsIndexWholeLetter() && !shown) {
1041
+ // The API groups first (also the empty ones: the jump bar lists them), then a shown group the
1042
+ // API did not list.
1043
+ const filled = new Map(groupCreatorsByIndexPrefixes(items, prefixes).map((group) => [
1044
+ group.prefix,
1045
+ group.items,
1046
+ ]));
1047
+ return [
1048
+ ...prefixes,
1049
+ ...[...filled.keys()].filter((prefix) => !prefixes.includes(prefix)),
1050
+ ].map((prefix) => ({
1051
+ prefix,
1052
+ anchor: creatorsPrefixAnchor(prefix, base),
1053
+ items: filled.get(prefix) ?? [],
1054
+ }));
1055
+ }
1056
+ const listed = this.creatorsIndexWholeLetter() && shown && !prefixes.includes(shown)
1057
+ ? [...prefixes, shown]
1058
+ : prefixes;
1059
+ return listed.map((prefix) => ({
1010
1060
  prefix,
1011
1061
  anchor: creatorsPrefixAnchor(prefix, base),
1012
1062
  items: prefix === shown ? items : [],
@@ -1061,16 +1111,12 @@ class CreatorsIndexBaseComponent {
1061
1111
  this.creatorsIndexHandledFragment = null;
1062
1112
  return;
1063
1113
  }
1064
- if (state.status === 'loading' && !state.prefixes.length)
1065
- return;
1066
1114
  if (fragment === this.creatorsIndexHandledFragment)
1067
1115
  return;
1068
1116
  const groups = this.groups();
1069
1117
  const target = resolveCreatorsFragment(fragment, this.letter(), groups);
1070
- if (!target)
1071
- return;
1072
- // A letter switch does not need this letter's list (it may have failed).
1073
- if ('letter' in target) {
1118
+ // A letter switch does not need this letter's list (it may still load, or have failed).
1119
+ if (target && 'letter' in target) {
1074
1120
  this.creatorsIndexRouter.navigate([], {
1075
1121
  relativeTo: this.creatorsIndexRoute,
1076
1122
  queryParams: { [CREATORS_INDEX_LETTER_PARAM]: target.letter },
@@ -1079,9 +1125,42 @@ class CreatorsIndexBaseComponent {
1079
1125
  });
1080
1126
  return;
1081
1127
  }
1082
- // Another group of this letter is loaded first (`?prefiks=`).
1128
+ if (state.status === 'loading' && !state.prefixes.length)
1129
+ return;
1130
+ if (!target) {
1131
+ // Narrowed, a group the API did not list is only known from the whole letter: load it first.
1132
+ if (this.creatorsIndexWholeLetter() &&
1133
+ state.prefix &&
1134
+ this.creatorsIndexUnlistedPrefix(this.letter(), this.creatorsIndexFragmentPrefix(fragment))) {
1135
+ this.creatorsIndexRouter.navigate([], {
1136
+ relativeTo: this.creatorsIndexRoute,
1137
+ queryParams: { [CREATORS_INDEX_LETTER_PARAM]: this.letter() },
1138
+ fragment,
1139
+ replaceUrl: true,
1140
+ });
1141
+ }
1142
+ return;
1143
+ }
1144
+ if ('letter' in target)
1145
+ return; // switched above; narrows the type
1083
1146
  const group = groups.find((g) => g.anchor === target.anchor);
1084
- if (group && group.prefix !== state.prefix) {
1147
+ // The whole letter shows every group: another one than the picked group drops `?prefiks=`.
1148
+ if (this.creatorsIndexWholeLetter() &&
1149
+ group &&
1150
+ state.prefix &&
1151
+ group.prefix !== state.prefix) {
1152
+ this.creatorsIndexRouter.navigate([], {
1153
+ relativeTo: this.creatorsIndexRoute,
1154
+ queryParams: { [CREATORS_INDEX_LETTER_PARAM]: this.letter() },
1155
+ fragment,
1156
+ replaceUrl: true,
1157
+ });
1158
+ return;
1159
+ }
1160
+ // Another group of this letter is loaded first (`?prefiks=`).
1161
+ if (!this.creatorsIndexWholeLetter() &&
1162
+ group &&
1163
+ group.prefix !== state.prefix) {
1085
1164
  this.creatorsIndexRouter.navigate([], {
1086
1165
  relativeTo: this.creatorsIndexRoute,
1087
1166
  queryParams: {
@@ -1184,17 +1263,73 @@ class CreatorsIndexBaseComponent {
1184
1263
  creatorsIndexApplySeo() {
1185
1264
  this.creatorsIndexSeoService.apply(creatorsListSeoMetadata(this.creatorsIndexSeo, (key) => String(this.creatorsIndexTranslate.instant(key) ?? '')));
1186
1265
  }
1266
+ /**
1267
+ * `true`: a letter shows its whole list split into the API groups, and `?prefiks=` only narrows it
1268
+ * (MobileMS, Łukasz 22.10: „kliknięcie A powinno całą listę zwrócić”). `false` (the default, kept
1269
+ * for the museums built on it) opens the first group. A method, not a field: a cached letter can
1270
+ * load while the base initialisers still run, before a subclass field would be set.
1271
+ */
1272
+ creatorsIndexWholeLetter() {
1273
+ return false;
1274
+ }
1275
+ /**
1276
+ * A `?prefiks=` of this letter the API did not list (a group shown from the whole letter's data),
1277
+ * in the index form (`cv` → `Cv`); `null` for anything else. Not for `#`, whose groups are local.
1278
+ * Two characters only: a one-character group is the letter itself (a creator named `X`), and
1279
+ * `lastNameSw` on it is the whole letter, so it cannot narrow.
1280
+ */
1281
+ /** The prefix a `#fragment` names: a group anchor (`tworcy-43-76`) read back, or the fragment itself (`#Cv`). */
1282
+ creatorsIndexFragmentPrefix(fragment) {
1283
+ const raw = creatorsIndexDecode(fragment);
1284
+ const head = `${this.creatorsIndexAnchorBase}-`;
1285
+ if (!raw.startsWith(head))
1286
+ return raw;
1287
+ const codePoints = raw
1288
+ .slice(head.length)
1289
+ .split('-')
1290
+ .map((hex) => (/^[\da-f]{1,6}$/.test(hex) ? parseInt(hex, 16) : NaN));
1291
+ return codePoints.every((point) => point <= 0x10ffff)
1292
+ ? String.fromCodePoint(...codePoints)
1293
+ : raw;
1294
+ }
1295
+ creatorsIndexUnlistedPrefix(letter, raw) {
1296
+ const chars = Array.from(raw?.trim() ?? '');
1297
+ if (letter === '#' || chars.length !== 2)
1298
+ return null;
1299
+ const prefix = creatorsDisplayPrefix(chars.join('').toLocaleUpperCase('pl'));
1300
+ return creatorsLetterOf(prefix) === letter ? prefix : null;
1301
+ }
1187
1302
  creatorsIndexLoad(letter, prefixes, raw) {
1188
- const prefix = creatorsFindPrefix(raw, prefixes) ?? prefixes[0] ?? null;
1303
+ const whole = this.creatorsIndexWholeLetter();
1304
+ const prefix = whole
1305
+ ? (creatorsFindPrefix(raw, prefixes) ??
1306
+ this.creatorsIndexUnlistedPrefix(letter, raw))
1307
+ : (creatorsFindPrefix(raw, prefixes) ?? prefixes[0] ?? null);
1189
1308
  const base = { prefixes, prefix };
1190
- if (!prefix)
1309
+ // The whole letter is still searched when the API lists no group (data drift); `#` has no search.
1310
+ const nothing = whole
1311
+ ? !prefix && letter === '#' && !prefixes.length
1312
+ : !prefix;
1313
+ if (nothing)
1191
1314
  return of({ ...base, status: 'success', items: [] });
1192
- return this.creatorsIndexService.getByIndexPrefix(letter, prefix).pipe(map((items) => ({
1315
+ const items$ = prefix
1316
+ ? this.creatorsIndexService.getByIndexPrefix(letter, prefix)
1317
+ : this.creatorsIndexLetter(letter, prefixes);
1318
+ return items$.pipe(map((items) => ({
1193
1319
  ...base,
1194
1320
  status: 'success',
1195
1321
  items,
1196
1322
  })), catchError(() => of({ ...base, status: 'error', items: [] })), startWith({ ...base, status: 'loading', items: [] }));
1197
1323
  }
1324
+ /**
1325
+ * Every creator of a letter: one `lastNameSw` search on the letter, or for `#` (no API filter)
1326
+ * its groups, read from the service's cached bucket.
1327
+ */
1328
+ creatorsIndexLetter(letter, prefixes) {
1329
+ if (letter !== '#')
1330
+ return this.creatorsIndexService.getByIndexPrefix(letter, letter);
1331
+ return forkJoin(prefixes.map((prefix) => this.creatorsIndexService.getByIndexPrefix(letter, prefix))).pipe(map((lists) => lists.flat()));
1332
+ }
1198
1333
  /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CreatorsIndexBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1199
1334
  /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.3", type: CreatorsIndexBaseComponent, isStandalone: true, viewQueries: [{ propertyName: "creatorsIndexFiltersButton", first: true, predicate: ["filtersButton"], descendants: true, isSignal: true }], ngImport: i0 }); }
1200
1335
  }
@@ -1206,7 +1341,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
1206
1341
  * Logic of the mobile "go to" panel of the creators index (museum-artists'
1207
1342
  * `index-filters-mobile`, MOB-1949/1959). The choice is a draft until `apply()`:
1208
1343
  * another letter emits `letterChange`, a prefix of the current letter emits
1209
- * `prefixSelect`. Every way out (close, backdrop, Escape, apply) emits `closed`;
1344
+ * `prefixSelect`, and — with `creatorsJumpToWholeLetter()` — unpressing the shown prefix
1345
+ * (`togglePrefix`) emits the current letter, its whole list. Every way out (close, backdrop, Escape, apply) emits `closed`;
1210
1346
  * the host removes the panel and moves the focus on.
1211
1347
  *
1212
1348
  * Escape is bound here (`host`), so a subclass must not bind it again.
@@ -1248,6 +1384,10 @@ class CreatorsJumpToBaseComponent {
1248
1384
  this.selectedLetter.set(letter);
1249
1385
  this.selectedPrefix.set(null);
1250
1386
  }
1387
+ /** Presses a prefix; the pressed one again goes back to no prefix (the whole letter). */
1388
+ togglePrefix(prefix) {
1389
+ this.selectedPrefix.update((current) => current === prefix ? null : prefix);
1390
+ }
1251
1391
  apply() {
1252
1392
  const letter = this.selectedLetter();
1253
1393
  const prefix = this.selectedPrefix();
@@ -1255,8 +1395,17 @@ class CreatorsJumpToBaseComponent {
1255
1395
  this.letterChange.emit(letter);
1256
1396
  else if (prefix)
1257
1397
  this.prefixSelect.emit(prefix);
1398
+ else if (this.creatorsJumpToWholeLetter() && this.activePrefix())
1399
+ this.letterChange.emit(letter);
1258
1400
  this.closed.emit();
1259
1401
  }
1402
+ /**
1403
+ * `true` beside an index with `creatorsIndexWholeLetter()`: unpressing the shown prefix goes back to
1404
+ * the whole letter. `false` (the default) keeps the panel's apply a no-op then.
1405
+ */
1406
+ creatorsJumpToWholeLetter() {
1407
+ return false;
1408
+ }
1260
1409
  close() {
1261
1410
  this.closed.emit();
1262
1411
  }
@@ -1956,7 +2105,7 @@ class CreatorDetailsBaseComponent {
1956
2105
  const { status, creator } = this.state();
1957
2106
  untracked(() => {
1958
2107
  if (creator) {
1959
- this.creatorDetailsSeo.apply(this.creatorDetailsSeoOf(creator));
2108
+ this.creatorDetailsSeo.apply(this.creatorDetailsSeoMetadata(creator));
1960
2109
  }
1961
2110
  else if (status === 'not-found') {
1962
2111
  const keys = this.creatorDetailsKeys;
@@ -2106,8 +2255,12 @@ class CreatorDetailsBaseComponent {
2106
2255
  }
2107
2256
  return [];
2108
2257
  }
2109
- /** `creatorDetailsSeoTitle`, the plain biography (`creatorBiography`), the first image, the canonical. */
2110
- creatorDetailsSeoOf(creator) {
2258
+ /**
2259
+ * `creatorDetailsSeoTitle`, the plain biography (`creatorBiography`), the first image, the canonical
2260
+ * with the slug (`resolveRelativeUrl`, so `MuseumSeoService` does not fall back to the request URL).
2261
+ * Override to change the metadata; used on the page and on the server.
2262
+ */
2263
+ creatorDetailsSeoMetadata(creator) {
2111
2264
  const title = this.creatorDetailsSeoTitle(creator);
2112
2265
  const url = creatorDetailsCommands(this.creatorDetailsPath, creator).join('/');
2113
2266
  const metadata = {
@@ -2116,6 +2269,7 @@ class CreatorDetailsBaseComponent {
2116
2269
  // A path override without the leading slash still gives an absolute canonical.
2117
2270
  url: url.startsWith('/') ? url : `/${url}`,
2118
2271
  type: 'article',
2272
+ resolveRelativeUrl: true,
2119
2273
  };
2120
2274
  const image = this.creatorDetailsGalleryOf(creator)[0]?.src;
2121
2275
  if (image)
@@ -2125,7 +2279,7 @@ class CreatorDetailsBaseComponent {
2125
2279
  creatorDetailsFetchSsrSeo() {
2126
2280
  const id = this.creatorDetailsRoute.snapshot.paramMap.get('id') ?? '';
2127
2281
  return firstValueFrom(this.creatorDetailsService.getById(id))
2128
- .then((creator) => (creator ? this.creatorDetailsSeoOf(creator) : null))
2282
+ .then((creator) => creator ? this.creatorDetailsSeoMetadata(creator) : null)
2129
2283
  .catch(() => null);
2130
2284
  }
2131
2285
  creatorDetailsT(key) {
@@ -2142,5 +2296,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
2142
2296
  * Generated bundle index. Do not edit.
2143
2297
  */
2144
2298
 
2145
- export { CREATORS_ALPHABET, CREATORS_CATALOG_DEFAULTS, CREATORS_HASH_PREFIXES, CREATORS_INDEX_LETTER_PARAM, CREATORS_INDEX_PREFIX_PARAM, CREATORS_SEARCH_LIMIT, CREATORS_SEARCH_PAGE_SIZES, CREATOR_DETAILS_KEYS, CREATOR_RELATED_STRIPS, CreatorCardBaseComponent, CreatorDetailsBaseComponent, CreatorsCatalogBaseComponent, CreatorsFiltersBaseComponent, CreatorsIndexBaseComponent, CreatorsJumpToBaseComponent, CreatorsListContext, CreatorsSearchListBaseComponent, CreatorsService, EMPTY_CREATORS_FILTER, createCreatorsCatalog, createCreatorsFacade, creatorBiography, creatorBiographyExcerpt, creatorDetailsCommands, creatorDictLabel, creatorDictionaryUrl, creatorDisplayName, creatorIsLongBiography, creatorLifeYears, creatorNfdLetter, creatorPlainName, creatorPlainText, creatorProfileUrl, creatorRelatedUrl, creatorRoles, creatorSlug, creatorWorksUrl, creatorYears, creatorsDisplayPrefix, creatorsFilterQueryParams, creatorsFilterToQuery, creatorsFilterValueOf, creatorsFindPrefix, creatorsIndexPrefixOf, creatorsLetterOf, creatorsListSeoMetadata, creatorsListUrl, creatorsPrefixAnchor, creatorsPrefixOf, creatorsPrefixesUrl, groupCreatorsByLetter, groupCreatorsByPrefix, hasCreatorsFilter, isCreatorsBirthRangeValid, isCreatorsBirthYear, parseCreatorsLetter, resolveCreatorsFragment };
2299
+ export { CREATORS_ALPHABET, CREATORS_CATALOG_DEFAULTS, CREATORS_HASH_PREFIXES, CREATORS_INDEX_LETTER_PARAM, CREATORS_INDEX_PREFIX_PARAM, CREATORS_SEARCH_LIMIT, CREATORS_SEARCH_PAGE_SIZES, CREATOR_DETAILS_KEYS, CREATOR_RELATED_STRIPS, CreatorCardBaseComponent, CreatorDetailsBaseComponent, CreatorsCatalogBaseComponent, CreatorsFiltersBaseComponent, CreatorsIndexBaseComponent, CreatorsJumpToBaseComponent, CreatorsListContext, CreatorsSearchListBaseComponent, CreatorsService, EMPTY_CREATORS_FILTER, createCreatorsCatalog, createCreatorsFacade, creatorBiography, creatorBiographyExcerpt, creatorDetailsCommands, creatorDictLabel, creatorDictionaryUrl, creatorDisplayName, creatorIsLongBiography, creatorLifeYears, creatorNfdLetter, creatorPlainName, creatorPlainText, creatorProfileUrl, creatorRelatedUrl, creatorRoles, creatorSlug, creatorWorksUrl, creatorYears, creatorsDisplayPrefix, creatorsFilterQueryParams, creatorsFilterToQuery, creatorsFilterValueOf, creatorsFindPrefix, creatorsIndexPrefixOf, creatorsLetterOf, creatorsListSeoMetadata, creatorsListUrl, creatorsPrefixAnchor, creatorsPrefixOf, creatorsPrefixesUrl, groupCreatorsByIndexPrefixes, groupCreatorsByLetter, groupCreatorsByPrefix, hasCreatorsFilter, isCreatorsBirthRangeValid, isCreatorsBirthYear, parseCreatorsLetter, resolveCreatorsFragment };
2146
2300
  //# sourceMappingURL=smartsoft001-mobilems-creators-shell-angular.mjs.map