Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/app/community-list-page/community-list-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ShowMoreFlatNode } from './show-more-flat-node.model';
import { FindListOptions } from '../core/data/find-list-options.model';
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
import { v4 as uuidv4 } from 'uuid';
import { reorderZcuPublicationCollections } from '../shared/zcu-collection-order';

// Helper method to combine and flatten an array of observables of flatNode arrays
export const combineAndFlatten = (obsList: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>
Expand Down Expand Up @@ -255,7 +256,8 @@ export class CommunityListService {
getFirstCompletedRemoteData(),
map((rd: RemoteData<PaginatedList<Collection>>) => {
if (hasValue(rd) && hasValue(rd.payload)) {
let nodes = rd.payload.page
// apply the ZCU collection display order in the community browse tree
let nodes = reorderZcuPublicationCollections(rd.payload.page)
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
nodes = [...nodes, showMoreFlatNode(`collection-${uuidv4()}`, level + 1, communityFlatNode)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,20 @@ export class CommunityPageSubCollectionListComponent implements OnInit, OnDestro
});
})
).subscribe((results) => {
this.subCollectionsRDObs.next(results);
this.subCollectionsRDObs.next(this.applyCustomCollectionOrder(results));
}));
}

/**
* Extension point for theme/customer-specific ordering of the current page of collections.
*
* @param rd the RemoteData holding the current page of collections
* @returns the RemoteData to emit, potentially with a reordered page
*/
protected applyCustomCollectionOrder(rd: RemoteData<PaginatedList<Collection>>): RemoteData<PaginatedList<Collection>> {
return rd;
}

ngOnDestroy(): void {
this.paginationService.clearPagination(this.config?.id);
this.subscriptions.map((subscription: Subscription) => subscription.unsubscribe());
Expand Down
114 changes: 114 additions & 0 deletions src/app/shared/zcu-collection-order.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Collection } from '../core/shared/collection.model';
import {
reorderZcuPublicationCollections,
ZCU_ARTICLES_COLLECTION_PREFIX,
ZCU_BOOKPARTS_COLLECTION_PREFIX,
} from './zcu-collection-order';

function fakeCollection(name: string): Collection {
return { get name(): string { return name; } } as Collection;
}

function names(collections: Collection[]): string[] {
return collections.map((collection: Collection) => collection.name);
}

describe('reorderZcuPublicationCollections', () => {
it('uses the Czech title prefixes', () => {
expect(ZCU_BOOKPARTS_COLLECTION_PREFIX).toBe('Kapitoly v knihách');
expect(ZCU_ARTICLES_COLLECTION_PREFIX).toBe('Články');
});

it('moves articles directly after book parts when book parts is 2nd (-> articles 3rd)', () => {
const input = [
fakeCollection('Habilitace'),
fakeCollection('Kapitoly v knihách'),
fakeCollection('Sborníky'),
fakeCollection('Články'),
];

expect(names(reorderZcuPublicationCollections(input)))
.toEqual(['Habilitace', 'Kapitoly v knihách', 'Články', 'Sborníky']);
});

it('moves articles to 2nd when book parts is 1st', () => {
const input = [
fakeCollection('Kapitoly v knihách'),
fakeCollection('Sborníky'),
fakeCollection('Zprávy'),
fakeCollection('Články'),
];

expect(names(reorderZcuPublicationCollections(input)))
.toEqual(['Kapitoly v knihách', 'Články', 'Sborníky', 'Zprávy']);
});

it('matches the bilingual, department-suffixed names used in production', () => {
const input = [
fakeCollection('Disertační práce'),
fakeCollection('Kapitoly v knihách / Bookparts (KAE)'),
fakeCollection('Konferenční příspěvky'),
fakeCollection('Monografie a kolektivní monografie'),
fakeCollection('Zprávy'),
fakeCollection('Články / Articles (KAE)'),
];

expect(names(reorderZcuPublicationCollections(input))).toEqual([
'Disertační práce',
'Kapitoly v knihách / Bookparts (KAE)',
'Články / Articles (KAE)',
'Konferenční příspěvky',
'Monografie a kolektivní monografie',
'Zprávy',
]);
});

it('matches mixed variants (plain articles, bilingual book parts)', () => {
const input = [
fakeCollection('Kapitoly v knihách / Bookparts'),
fakeCollection('Sborníky'),
fakeCollection('Články'),
];

expect(names(reorderZcuPublicationCollections(input)))
.toEqual(['Kapitoly v knihách / Bookparts', 'Články', 'Sborníky']);
});

it('never moves book parts out of its natural position', () => {
const input = [
fakeCollection('Abstrakty'),
fakeCollection('Habilitace'),
fakeCollection('Kapitoly v knihách'),
fakeCollection('Články'),
];

expect(names(reorderZcuPublicationCollections(input)))
.toEqual(['Abstrakty', 'Habilitace', 'Kapitoly v knihách', 'Články']);
});

it('leaves the list unchanged when articles is already directly after book parts', () => {
const input = [
fakeCollection('Habilitace'),
fakeCollection('Kapitoly v knihách'),
fakeCollection('Články'),
fakeCollection('Sborníky'),
];

expect(reorderZcuPublicationCollections(input)).toBe(input);
});

it('leaves the list unchanged when an anchor collection is missing', () => {
expect(names(reorderZcuPublicationCollections([
fakeCollection('Knihy'), fakeCollection('Články'),
]))).toEqual(['Knihy', 'Články']);

expect(names(reorderZcuPublicationCollections([
fakeCollection('Knihy'), fakeCollection('Kapitoly v knihách'),
]))).toEqual(['Knihy', 'Kapitoly v knihách']);
});

it('handles empty and single-element input without error', () => {
expect(reorderZcuPublicationCollections([])).toEqual([]);
expect(names(reorderZcuPublicationCollections([fakeCollection('Knihy')]))).toEqual(['Knihy']);
});
});
42 changes: 42 additions & 0 deletions src/app/shared/zcu-collection-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Collection } from '../core/shared/collection.model';

/**
* Title prefix of the collection that anchors the ordering.
*/
export const ZCU_BOOKPARTS_COLLECTION_PREFIX = 'Kapitoly v knihách';

/**
* Title prefix of the collection pinned directly after book parts.
*/
export const ZCU_ARTICLES_COLLECTION_PREFIX = 'Články';

function nameStartsWith(collection: Collection, prefix: string): boolean {
const name = collection?.name;
return typeof name === 'string' && name.trim().toLocaleLowerCase().startsWith(prefix.toLocaleLowerCase());
}

/**
* Reorders a list of collections so that the articles collection is shown immediately after
* the book parts collection, while every other collection keeps its original position.
*/
export function reorderZcuPublicationCollections(collections: Collection[]): Collection[] {
if (!Array.isArray(collections) || collections.length < 2) {
return collections;
}

const bookPartsIndex = collections.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_BOOKPARTS_COLLECTION_PREFIX));
const articlesIndex = collections.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_ARTICLES_COLLECTION_PREFIX));

if (bookPartsIndex === -1 || articlesIndex === -1) {
return collections;
}
if (articlesIndex === bookPartsIndex + 1) {
return collections;
}

const articles = collections[articlesIndex];
const withoutArticles = collections.filter((_: Collection, index: number) => index !== articlesIndex);
const insertAt = withoutArticles.findIndex((collection: Collection) => nameStartsWith(collection, ZCU_BOOKPARTS_COLLECTION_PREFIX)) + 1;

return [...withoutArticles.slice(0, insertAt), articles, ...withoutArticles.slice(insertAt)];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Collection } from '../../../../../app/core/shared/collection.model';
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';

/**
* Builds a minimal Collection-like stub that only exposes the `name` getter used by the
* reordering logic under test.
*
* @param name the collection name (dc.title) to expose
* @returns an object typed as Collection for the purposes of these tests
*/
function fakeCollection(name: string): Collection {
return { get name(): string { return name; } } as Collection;
}

/**
* Maps a list of collections to their names for concise assertions.
*
* @param collections the collections to map
* @returns the ordered list of collection names
*/
function names(collections: Collection[]): string[] {
return collections.map((collection: Collection) => collection.name);
}

const BOOKPARTS_COLLECTION_NAME = 'Kapitoly v knihách';
const ARTICLES_COLLECTION_NAME = 'Články';

describe('CommunityPageSubCollectionListComponent (custom theme) reorderCollections', () => {
let component: CommunityPageSubCollectionListComponent;

const reorder = (collections: Collection[]): Collection[] =>
(component as any).reorderCollections(collections);

beforeEach(() => {
component = new CommunityPageSubCollectionListComponent(null, null, null);
});

it('shows "Články" directly after "Kapitoly v knihách" (book parts 2nd -> articles 3rd)', () => {
const input = [
fakeCollection('Alfa'),
fakeCollection(BOOKPARTS_COLLECTION_NAME),
fakeCollection('Sborníky'),
fakeCollection(ARTICLES_COLLECTION_NAME),
];

const result = reorder(input);

expect(names(result)).toEqual(['Alfa', BOOKPARTS_COLLECTION_NAME, ARTICLES_COLLECTION_NAME, 'Sborníky']);
expect(names(result).indexOf(ARTICLES_COLLECTION_NAME))
.toBe(names(result).indexOf(BOOKPARTS_COLLECTION_NAME) + 1);
});

it('shows "Články" 2nd when "Kapitoly v knihách" is 1st', () => {
const input = [
fakeCollection(BOOKPARTS_COLLECTION_NAME),
fakeCollection('Sborníky'),
fakeCollection(ARTICLES_COLLECTION_NAME),
];

expect(names(reorder(input))).toEqual([BOOKPARTS_COLLECTION_NAME, ARTICLES_COLLECTION_NAME, 'Sborníky']);
});

it('leaves the list unchanged when "Kapitoly v knihách" is missing', () => {
const input = [
fakeCollection('Alfa'),
fakeCollection(ARTICLES_COLLECTION_NAME),
fakeCollection('Zeta'),
];

expect(names(reorder(input))).toEqual(['Alfa', ARTICLES_COLLECTION_NAME, 'Zeta']);
});

it('leaves the list unchanged when "Články" is missing', () => {
const input = [
fakeCollection('Alfa'),
fakeCollection(BOOKPARTS_COLLECTION_NAME),
fakeCollection('Zeta'),
];

expect(names(reorder(input))).toEqual(['Alfa', BOOKPARTS_COLLECTION_NAME, 'Zeta']);
});

it('uses the exact diacritic collection names', () => {
expect(BOOKPARTS_COLLECTION_NAME).toBe('Kapitoly v knihách');
expect(ARTICLES_COLLECTION_NAME).toBe('Články');

const input = [
fakeCollection('Alfa'),
fakeCollection('Kapitoly v knihach'),
fakeCollection('Clanky'),
fakeCollection('Zeta'),
];

expect(names(reorder(input))).toEqual(['Alfa', 'Kapitoly v knihach', 'Clanky', 'Zeta']);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Component } from '@angular/core';
import { CommunityPageSubCollectionListComponent as BaseComponent }
from '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component';
import { RemoteData } from '../../../../../app/core/data/remote-data';
import { PaginatedList } from '../../../../../app/core/data/paginated-list.model';
import { Collection } from '../../../../../app/core/shared/collection.model';
import { reorderZcuPublicationCollections } from '../../../../../app/shared/zcu-collection-order';

@Component({
selector: 'ds-community-page-sub-collection-list',
Expand All @@ -9,4 +13,32 @@ import { CommunityPageSubCollectionListComponent as BaseComponent }
// templateUrl: './community-page-sub-collection-list.component.html',
templateUrl: '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component.html'
})
export class CommunityPageSubCollectionListComponent extends BaseComponent {}
export class CommunityPageSubCollectionListComponent extends BaseComponent {

/**
* Reorders the current page of collections so that "Články" is shown directly after
* "Kapitoly v knihách", while every other collection keeps its existing alphabetical position.
*
* @param rd the RemoteData holding the current page of collections
* @returns the RemoteData with a reordered page, or unchanged when the page is empty
*/
protected applyCustomCollectionOrder(rd: RemoteData<PaginatedList<Collection>>): RemoteData<PaginatedList<Collection>> {
const page = rd?.payload?.page;
if (Array.isArray(page) && page.length > 0) {
rd.payload.page = this.reorderCollections(page);
}
return rd;
}

/**
* Reorders the current page of collections using the shared ZCU ordering rule,
* moving "Články" to sit directly after "Kapitoly v knihách".
*
* @param collections the collections of the current page (alphabetical by dc.title)
* @returns a new, reordered array, or the original array when the rule does not apply
*/
protected reorderCollections(collections: Collection[]): Collection[] {
return reorderZcuPublicationCollections(collections);
}

}
Loading