Skip to content

Commit 1bb0366

Browse files
KasinhouMatus Kasakclaude
authored
ZCU-PUB/feat(community): show "Články" collection 3rd, after "Kapitoly v knihách" (#953) (#1492)
* ZCU-PUB/feat(community): show "Články" collection 3rd, after "Kapitoly v knihách" (#953) Hardcode the display order for the ZCU publications community so that the "Články" collection appears in 3rd position, immediately after "Kapitoly v knihách", while all other collections keep their existing alphabetical order. Approach: the base sub-collection-list component gains a no-op `applyCustomCollectionOrder` seam applied to each fetched page, keeping behavior identical for every other theme/customer. The custom theme overrides it to pin the two named collections via a small, unit-tested pure `reorderCollections` helper. Assumption: only the "Články -> 3rd" constraint is encoded here; a full configurable custom ordering would be the separate Option B feature. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ZCU-PUB/feat(community): also apply "Články" 3rd order in the community browse tree (#953) The initial fix only reordered the community *detail* page (the themed sub-collection list). The "Communities & Collections" browse tree (/community-list) renders collections through CommunityListService and was still showing the default alphabetical order (Články last). Extract the ordering rule into a shared pure helper (shared/zcu-collection-order.ts) and apply it in both places: - the custom-theme sub-collection-list override (delegates to the helper), - CommunityListService (reorders each fetched collection page in the tree). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ZCU-PUB/fix(community): pin "Články" directly after "Kapitoly v knihách" (#953) The previous #953 hardcode force-pinned "Kapitoly v knihách" to the 2nd slot and "Články" to the 3rd, which is wrong whenever book parts is not naturally 2nd. Per the refined requirement, "Články" must simply follow book parts wherever it sits: book parts 1st -> articles 2nd, book parts 2nd -> articles 3rd, etc. Book parts and every other collection keep their natural (alphabetical) order; only "Články" moves. reorderZcuPublicationCollections now finds book parts, removes "Články", and re-inserts it immediately after book parts (no-op when either is absent or they are already adjacent). Constants renamed ZCU_{SECOND,THIRD} -> ZCU_{BOOKPARTS,ARTICLES}_COLLECTION_NAME to stop implying fixed positions. The shared helper is used by both the community page (themed sub-collection-list) and the community browse tree (CommunityListService), so both are fixed. Verified end-to-end on a seeded Publikační činnost hierarchy: a department with book parts 2nd shows Články 3rd, and one with book parts 1st shows Články 2nd, in both the /community-list tree and the community detail page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update docd * fix(community): match book parts / articles collections by Czech title prefix Departments name these collections inconsistently ("Články", "Články / Articles", "Články / Articles (KAE)", and the same pattern for "Kapitoly v knihách / Bookparts (KAE)"). The exact-name match only handled the plain Czech variant, so the reorder never fired for most departments. Match by Czech title prefix (trimmed, case-insensitive) so every bilingual/suffixed variant is handled regardless of UI language. * Update docs --------- Co-authored-by: Matus Kasak <matus.kasak@dataquest.sk> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a29ed78 commit 1bb0366

6 files changed

Lines changed: 299 additions & 3 deletions

File tree

src/app/community-list-page/community-list-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { ShowMoreFlatNode } from './show-more-flat-node.model';
2525
import { FindListOptions } from '../core/data/find-list-options.model';
2626
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
2727
import { v4 as uuidv4 } from 'uuid';
28+
import { reorderZcuPublicationCollections } from '../shared/zcu-collection-order';
2829

2930
// Helper method to combine and flatten an array of observables of flatNode arrays
3031
export const combineAndFlatten = (obsList: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>
@@ -255,7 +256,8 @@ export class CommunityListService {
255256
getFirstCompletedRemoteData(),
256257
map((rd: RemoteData<PaginatedList<Collection>>) => {
257258
if (hasValue(rd) && hasValue(rd.payload)) {
258-
let nodes = rd.payload.page
259+
// apply the ZCU collection display order in the community browse tree
260+
let nodes = reorderZcuPublicationCollections(rd.payload.page)
259261
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
260262
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
261263
nodes = [...nodes, showMoreFlatNode(`collection-${uuidv4()}`, level + 1, communityFlatNode)];

src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,20 @@ export class CommunityPageSubCollectionListComponent implements OnInit, OnDestro
8888
});
8989
})
9090
).subscribe((results) => {
91-
this.subCollectionsRDObs.next(results);
91+
this.subCollectionsRDObs.next(this.applyCustomCollectionOrder(results));
9292
}));
9393
}
9494

95+
/**
96+
* Extension point for theme/customer-specific ordering of the current page of collections.
97+
*
98+
* @param rd the RemoteData holding the current page of collections
99+
* @returns the RemoteData to emit, potentially with a reordered page
100+
*/
101+
protected applyCustomCollectionOrder(rd: RemoteData<PaginatedList<Collection>>): RemoteData<PaginatedList<Collection>> {
102+
return rd;
103+
}
104+
95105
ngOnDestroy(): void {
96106
this.paginationService.clearPagination(this.config?.id);
97107
this.subscriptions.map((subscription: Subscription) => subscription.unsubscribe());
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Collection } from '../core/shared/collection.model';
2+
import {
3+
reorderZcuPublicationCollections,
4+
ZCU_ARTICLES_COLLECTION_PREFIX,
5+
ZCU_BOOKPARTS_COLLECTION_PREFIX,
6+
} from './zcu-collection-order';
7+
8+
function fakeCollection(name: string): Collection {
9+
return { get name(): string { return name; } } as Collection;
10+
}
11+
12+
function names(collections: Collection[]): string[] {
13+
return collections.map((collection: Collection) => collection.name);
14+
}
15+
16+
describe('reorderZcuPublicationCollections', () => {
17+
it('uses the Czech title prefixes', () => {
18+
expect(ZCU_BOOKPARTS_COLLECTION_PREFIX).toBe('Kapitoly v knihách');
19+
expect(ZCU_ARTICLES_COLLECTION_PREFIX).toBe('Články');
20+
});
21+
22+
it('moves articles directly after book parts when book parts is 2nd (-> articles 3rd)', () => {
23+
const input = [
24+
fakeCollection('Habilitace'),
25+
fakeCollection('Kapitoly v knihách'),
26+
fakeCollection('Sborníky'),
27+
fakeCollection('Články'),
28+
];
29+
30+
expect(names(reorderZcuPublicationCollections(input)))
31+
.toEqual(['Habilitace', 'Kapitoly v knihách', 'Články', 'Sborníky']);
32+
});
33+
34+
it('moves articles to 2nd when book parts is 1st', () => {
35+
const input = [
36+
fakeCollection('Kapitoly v knihách'),
37+
fakeCollection('Sborníky'),
38+
fakeCollection('Zprávy'),
39+
fakeCollection('Články'),
40+
];
41+
42+
expect(names(reorderZcuPublicationCollections(input)))
43+
.toEqual(['Kapitoly v knihách', 'Články', 'Sborníky', 'Zprávy']);
44+
});
45+
46+
it('matches the bilingual, department-suffixed names used in production', () => {
47+
const input = [
48+
fakeCollection('Disertační práce'),
49+
fakeCollection('Kapitoly v knihách / Bookparts (KAE)'),
50+
fakeCollection('Konferenční příspěvky'),
51+
fakeCollection('Monografie a kolektivní monografie'),
52+
fakeCollection('Zprávy'),
53+
fakeCollection('Články / Articles (KAE)'),
54+
];
55+
56+
expect(names(reorderZcuPublicationCollections(input))).toEqual([
57+
'Disertační práce',
58+
'Kapitoly v knihách / Bookparts (KAE)',
59+
'Články / Articles (KAE)',
60+
'Konferenční příspěvky',
61+
'Monografie a kolektivní monografie',
62+
'Zprávy',
63+
]);
64+
});
65+
66+
it('matches mixed variants (plain articles, bilingual book parts)', () => {
67+
const input = [
68+
fakeCollection('Kapitoly v knihách / Bookparts'),
69+
fakeCollection('Sborníky'),
70+
fakeCollection('Články'),
71+
];
72+
73+
expect(names(reorderZcuPublicationCollections(input)))
74+
.toEqual(['Kapitoly v knihách / Bookparts', 'Články', 'Sborníky']);
75+
});
76+
77+
it('never moves book parts out of its natural position', () => {
78+
const input = [
79+
fakeCollection('Abstrakty'),
80+
fakeCollection('Habilitace'),
81+
fakeCollection('Kapitoly v knihách'),
82+
fakeCollection('Články'),
83+
];
84+
85+
expect(names(reorderZcuPublicationCollections(input)))
86+
.toEqual(['Abstrakty', 'Habilitace', 'Kapitoly v knihách', 'Články']);
87+
});
88+
89+
it('leaves the list unchanged when articles is already directly after book parts', () => {
90+
const input = [
91+
fakeCollection('Habilitace'),
92+
fakeCollection('Kapitoly v knihách'),
93+
fakeCollection('Články'),
94+
fakeCollection('Sborníky'),
95+
];
96+
97+
expect(reorderZcuPublicationCollections(input)).toBe(input);
98+
});
99+
100+
it('leaves the list unchanged when an anchor collection is missing', () => {
101+
expect(names(reorderZcuPublicationCollections([
102+
fakeCollection('Knihy'), fakeCollection('Články'),
103+
]))).toEqual(['Knihy', 'Články']);
104+
105+
expect(names(reorderZcuPublicationCollections([
106+
fakeCollection('Knihy'), fakeCollection('Kapitoly v knihách'),
107+
]))).toEqual(['Knihy', 'Kapitoly v knihách']);
108+
});
109+
110+
it('handles empty and single-element input without error', () => {
111+
expect(reorderZcuPublicationCollections([])).toEqual([]);
112+
expect(names(reorderZcuPublicationCollections([fakeCollection('Knihy')]))).toEqual(['Knihy']);
113+
});
114+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Collection } from '../core/shared/collection.model';
2+
3+
/**
4+
* Title prefix of the collection that anchors the ordering.
5+
*/
6+
export const ZCU_BOOKPARTS_COLLECTION_PREFIX = 'Kapitoly v knihách';
7+
8+
/**
9+
* Title prefix of the collection pinned directly after book parts.
10+
*/
11+
export const ZCU_ARTICLES_COLLECTION_PREFIX = 'Články';
12+
13+
function nameStartsWith(collection: Collection, prefix: string): boolean {
14+
const name = collection?.name;
15+
return typeof name === 'string' && name.trim().toLocaleLowerCase().startsWith(prefix.toLocaleLowerCase());
16+
}
17+
18+
/**
19+
* Reorders a list of collections so that the articles collection is shown immediately after
20+
* the book parts collection, while every other collection keeps its original position.
21+
*/
22+
export function reorderZcuPublicationCollections(collections: Collection[]): Collection[] {
23+
if (!Array.isArray(collections) || collections.length < 2) {
24+
return collections;
25+
}
26+
27+
const bookPartsIndex = collections.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_BOOKPARTS_COLLECTION_PREFIX));
28+
const articlesIndex = collections.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_ARTICLES_COLLECTION_PREFIX));
29+
30+
if (bookPartsIndex === -1 || articlesIndex === -1) {
31+
return collections;
32+
}
33+
if (articlesIndex === bookPartsIndex + 1) {
34+
return collections;
35+
}
36+
37+
const articles = collections[articlesIndex];
38+
const withoutArticles = collections.filter((_: Collection, index: number) => index !== articlesIndex);
39+
const insertAt = withoutArticles.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_BOOKPARTS_COLLECTION_PREFIX)) + 1;
40+
41+
return [...withoutArticles.slice(0, insertAt), articles, ...withoutArticles.slice(insertAt)];
42+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Collection } from '../../../../../app/core/shared/collection.model';
2+
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
3+
4+
/**
5+
* Builds a minimal Collection-like stub that only exposes the `name` getter used by the
6+
* reordering logic under test.
7+
*
8+
* @param name the collection name (dc.title) to expose
9+
* @returns an object typed as Collection for the purposes of these tests
10+
*/
11+
function fakeCollection(name: string): Collection {
12+
return { get name(): string { return name; } } as Collection;
13+
}
14+
15+
/**
16+
* Maps a list of collections to their names for concise assertions.
17+
*
18+
* @param collections the collections to map
19+
* @returns the ordered list of collection names
20+
*/
21+
function names(collections: Collection[]): string[] {
22+
return collections.map((collection: Collection) => collection.name);
23+
}
24+
25+
const BOOKPARTS_COLLECTION_NAME = 'Kapitoly v knihách';
26+
const ARTICLES_COLLECTION_NAME = 'Články';
27+
28+
describe('CommunityPageSubCollectionListComponent (custom theme) reorderCollections', () => {
29+
let component: CommunityPageSubCollectionListComponent;
30+
31+
const reorder = (collections: Collection[]): Collection[] =>
32+
(component as any).reorderCollections(collections);
33+
34+
beforeEach(() => {
35+
component = new CommunityPageSubCollectionListComponent(null, null, null);
36+
});
37+
38+
it('shows "Články" directly after "Kapitoly v knihách" (book parts 2nd -> articles 3rd)', () => {
39+
const input = [
40+
fakeCollection('Alfa'),
41+
fakeCollection(BOOKPARTS_COLLECTION_NAME),
42+
fakeCollection('Sborníky'),
43+
fakeCollection(ARTICLES_COLLECTION_NAME),
44+
];
45+
46+
const result = reorder(input);
47+
48+
expect(names(result)).toEqual(['Alfa', BOOKPARTS_COLLECTION_NAME, ARTICLES_COLLECTION_NAME, 'Sborníky']);
49+
expect(names(result).indexOf(ARTICLES_COLLECTION_NAME))
50+
.toBe(names(result).indexOf(BOOKPARTS_COLLECTION_NAME) + 1);
51+
});
52+
53+
it('shows "Články" 2nd when "Kapitoly v knihách" is 1st', () => {
54+
const input = [
55+
fakeCollection(BOOKPARTS_COLLECTION_NAME),
56+
fakeCollection('Sborníky'),
57+
fakeCollection(ARTICLES_COLLECTION_NAME),
58+
];
59+
60+
expect(names(reorder(input))).toEqual([BOOKPARTS_COLLECTION_NAME, ARTICLES_COLLECTION_NAME, 'Sborníky']);
61+
});
62+
63+
it('leaves the list unchanged when "Kapitoly v knihách" is missing', () => {
64+
const input = [
65+
fakeCollection('Alfa'),
66+
fakeCollection(ARTICLES_COLLECTION_NAME),
67+
fakeCollection('Zeta'),
68+
];
69+
70+
expect(names(reorder(input))).toEqual(['Alfa', ARTICLES_COLLECTION_NAME, 'Zeta']);
71+
});
72+
73+
it('leaves the list unchanged when "Články" is missing', () => {
74+
const input = [
75+
fakeCollection('Alfa'),
76+
fakeCollection(BOOKPARTS_COLLECTION_NAME),
77+
fakeCollection('Zeta'),
78+
];
79+
80+
expect(names(reorder(input))).toEqual(['Alfa', BOOKPARTS_COLLECTION_NAME, 'Zeta']);
81+
});
82+
83+
it('uses the exact diacritic collection names', () => {
84+
expect(BOOKPARTS_COLLECTION_NAME).toBe('Kapitoly v knihách');
85+
expect(ARTICLES_COLLECTION_NAME).toBe('Články');
86+
87+
const input = [
88+
fakeCollection('Alfa'),
89+
fakeCollection('Kapitoly v knihach'),
90+
fakeCollection('Clanky'),
91+
fakeCollection('Zeta'),
92+
];
93+
94+
expect(names(reorder(input))).toEqual(['Alfa', 'Kapitoly v knihach', 'Clanky', 'Zeta']);
95+
});
96+
});
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Component } from '@angular/core';
22
import { CommunityPageSubCollectionListComponent as BaseComponent }
33
from '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component';
4+
import { RemoteData } from '../../../../../app/core/data/remote-data';
5+
import { PaginatedList } from '../../../../../app/core/data/paginated-list.model';
6+
import { Collection } from '../../../../../app/core/shared/collection.model';
7+
import { reorderZcuPublicationCollections } from '../../../../../app/shared/zcu-collection-order';
48

59
@Component({
610
selector: 'ds-community-page-sub-collection-list',
@@ -9,4 +13,32 @@ import { CommunityPageSubCollectionListComponent as BaseComponent }
913
// templateUrl: './community-page-sub-collection-list.component.html',
1014
templateUrl: '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component.html'
1115
})
12-
export class CommunityPageSubCollectionListComponent extends BaseComponent {}
16+
export class CommunityPageSubCollectionListComponent extends BaseComponent {
17+
18+
/**
19+
* Reorders the current page of collections so that "Články" is shown directly after
20+
* "Kapitoly v knihách", while every other collection keeps its existing alphabetical position.
21+
*
22+
* @param rd the RemoteData holding the current page of collections
23+
* @returns the RemoteData with a reordered page, or unchanged when the page is empty
24+
*/
25+
protected applyCustomCollectionOrder(rd: RemoteData<PaginatedList<Collection>>): RemoteData<PaginatedList<Collection>> {
26+
const page = rd?.payload?.page;
27+
if (Array.isArray(page) && page.length > 0) {
28+
rd.payload.page = this.reorderCollections(page);
29+
}
30+
return rd;
31+
}
32+
33+
/**
34+
* Reorders the current page of collections using the shared ZCU ordering rule,
35+
* moving "Články" to sit directly after "Kapitoly v knihách".
36+
*
37+
* @param collections the collections of the current page (alphabetical by dc.title)
38+
* @returns a new, reordered array, or the original array when the rule does not apply
39+
*/
40+
protected reorderCollections(collections: Collection[]): Collection[] {
41+
return reorderZcuPublicationCollections(collections);
42+
}
43+
44+
}

0 commit comments

Comments
 (0)