diff --git a/cypress/e2e/browse-by-subject.cy.ts b/cypress/e2e/browse-by-subject.cy.ts index 0937a2542bb..6db50cd7025 100644 --- a/cypress/e2e/browse-by-subject.cy.ts +++ b/cypress/e2e/browse-by-subject.cy.ts @@ -1,13 +1,18 @@ -import { testA11y } from 'cypress/support/utils'; -describe('Browse By Subject', () => { - it('should pass accessibility tests', () => { - cy.visit('/browse/subject'); +describe('Browse By Subject - Removed from FDA Theme', () => { + it('should only show allowed browse types (title, author, dateissued)', () => { + // Navigate to community list + cy.visit('/community-list'); - // Wait for to be visible - cy.get('ds-browse-by-metadata').should('be.visible'); + // Click on first community + cy.get('ds-community-list a').first().click(); - // Analyze for accessibility - testA11y('ds-browse-by-metadata'); + // Check that only allowed browse tabs exist + cy.get('a[href*="/browse/title"]').should('exist'); + cy.get('a[href*="/browse/author"]').should('exist'); + cy.get('a[href*="/browse/dateissued"]').should('exist'); + + // Verify subject browse tab doesn't exist + cy.get('a[href*="/browse/subject"]').should('not.exist'); }); }); diff --git a/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html new file mode 100644 index 00000000000..92ce6d99b73 --- /dev/null +++ b/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -0,0 +1,31 @@ +

{{'browse.comcol.head' | translate}}

+ diff --git a/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.scss b/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts new file mode 100644 index 00000000000..1e1a69d850b --- /dev/null +++ b/src/themes/fda/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -0,0 +1,205 @@ +import { + AsyncPipe, + NgForOf, + NgIf, +} from '@angular/common'; +import { + Component, + Inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { + EventType, + NavigationEnd, + Router, + RouterLink, + RouterLinkActive, + Scroll, +} from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { + BehaviorSubject, + combineLatest, + Observable, + Subscription, +} from 'rxjs'; +import { + distinctUntilChanged, + filter, + map, + startWith, + take, +} from 'rxjs/operators'; + +import { getCollectionPageRoute } from '../../../../../../app/collection-page/collection-page-routing-paths'; +import { getCommunityPageRoute } from '../../../../../../app/community-page/community-page-routing-paths'; +import { BrowseService } from '../../../../../../app/core/browse/browse.service'; +import { PaginatedList } from '../../../../../../app/core/data/paginated-list.model'; +import { RemoteData } from '../../../../../../app/core/data/remote-data'; +import { BrowseDefinition } from '../../../../../../app/core/shared/browse-definition.model'; +import { getFirstCompletedRemoteData } from '../../../../../../app/core/shared/operators'; +import { isNotEmpty } from '../../../../../../app/shared/empty.util'; +import { + APP_CONFIG, + AppConfig, +} from '../../../../../../config/app-config.interface'; + +export interface ComColPageNavOption { + id: string; + label: string; + routerLink: string; + params?: any; +} + +/** + * A component to display the "Browse By" section of a Community or Collection page + * It expects the ID of the Community or Collection as input to be passed on as a scope + */ +@Component({ + selector: 'ds-themed-base-comcol-page-browse-by', + styleUrls: ['./comcol-page-browse-by.component.scss'], + templateUrl: './comcol-page-browse-by.component.html', + imports: [ + FormsModule, + NgForOf, + RouterLink, + RouterLinkActive, + TranslateModule, + AsyncPipe, + NgIf, + ], + standalone: true, +}) +export class ComcolPageBrowseByComponent implements OnDestroy, OnInit { + /** + * The ID of the Community or Collection + */ + @Input() id: string; + @Input() contentType: string; + + allOptions$: Observable; + + currentOption$: BehaviorSubject = new BehaviorSubject(undefined); + + subs: Subscription[] = []; + + constructor( + @Inject(APP_CONFIG) public appConfig: AppConfig, + public router: Router, + private browseService: BrowseService, + ) { + } + + ngOnInit(): void { + this.allOptions$ = this.browseService.getBrowseDefinitions().pipe( + getFirstCompletedRemoteData(), + map((browseDefListRD: RemoteData>) => { + const allOptions: ComColPageNavOption[] = []; + if (browseDefListRD.hasSucceeded) { + let comColRoute: string; + if (this.contentType === 'collection') { + comColRoute = getCollectionPageRoute(this.id); + allOptions.push({ + id: 'search', + label: 'collection.page.browse.search.head', + routerLink: `${comColRoute}/search`, + }); + } else if (this.contentType === 'community') { + comColRoute = getCommunityPageRoute(this.id); + allOptions.push({ + id: 'search', + label: 'collection.page.browse.search.head', + routerLink: `${comColRoute}/search`, + }); + allOptions.push({ + id: 'comcols', + label: 'community.all-lists.head', + routerLink: `${comColRoute}/subcoms-cols`, + }); + } + + //Only browse types: title, author, dateissued are allowed for comcol pages + const allowedBrowseTypes = ['title', 'author', 'dateissued']; + + allOptions.push(...browseDefListRD.payload.page + .filter((config: BrowseDefinition) => allowedBrowseTypes.includes(config.id)) + .map((config: BrowseDefinition) => ({ + id: `browse_${config.id}`, + label: `browse.comcol.by.${config.id}`, + routerLink: `${comColRoute}/browse/${config.id}`, + })), + ); + + // When the default tab is not the "search" tab, the "search" tab is moved + // at the end of the tabs ribbon for aesthetics purposes. + if (this.appConfig[this.contentType].defaultBrowseTab !== 'search') { + allOptions.push(allOptions.shift()); + } + } + return allOptions; + }), + ); + + let comColRoute: string; + if (this.contentType === 'collection') { + comColRoute = getCollectionPageRoute(this.id); + } else if (this.contentType === 'community') { + comColRoute = getCommunityPageRoute(this.id); + } + + this.subs.push(combineLatest([ + this.allOptions$, + this.router.events.pipe( + startWith(this.router), + filter((next: Router|Scroll) => (isNotEmpty((next as Router)?.url) || (next as Scroll)?.type === EventType.Scroll)), + map((next: Router|Scroll) => (next as Router)?.url || ((next as Scroll).routerEvent as NavigationEnd).urlAfterRedirects), + distinctUntilChanged(), + ), + ]).subscribe(([navOptions, url]: [ComColPageNavOption[], string]) => { + for (const option of navOptions) { + if (url?.split('?')[0] === comColRoute && option.id === this.appConfig[this.contentType].defaultBrowseTab) { + void this.router.navigate([option.routerLink], { queryParams: option.params }); + break; + } else if (option.routerLink === url?.split('?')[0]) { + this.currentOption$.next(option); + break; + } + } + })); + + if (this.router.url?.split('?')[0] === comColRoute) { + this.allOptions$.pipe( + take(1), + ).subscribe((allOptions: ComColPageNavOption[]) => { + for (const option of allOptions) { + if (option.id === this.appConfig[this.contentType].defaultBrowseTab) { + this.currentOption$.next(option[0]); + void this.router.navigate([option.routerLink], { queryParams: option.params }); + break; + } + } + }); + } + } + + ngOnDestroy(): void { + this.subs.forEach((sub: Subscription) => sub.unsubscribe()); + } + + onSelectChange(event: any): void { + this.allOptions$.pipe( + take(1), + ).subscribe((allOptions: ComColPageNavOption[]) => { + for (const option of allOptions) { + if (option.id === event.target.value) { + this.currentOption$.next(option[0]); + void this.router.navigate([option.routerLink], { queryParams: option.params }); + break; + } + } + }); + } +} diff --git a/src/themes/fda/eager-theme.module.ts b/src/themes/fda/eager-theme.module.ts index b212541cbc4..9495555d231 100644 --- a/src/themes/fda/eager-theme.module.ts +++ b/src/themes/fda/eager-theme.module.ts @@ -10,6 +10,7 @@ import { HomeNewsComponent } from './app/home-page/home-news/home-news.component import { TopLevelCommunityListComponent } from './app/home-page/top-level-community-list/top-level-community-list.component'; import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; import { NavbarComponent } from './app/navbar/navbar.component'; +import { ComcolPageBrowseByComponent } from './app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component'; import { BadgesComponent } from './app/shared/object-collection/shared/badges/badges.component'; /** * Add components that use a custom decorator to ENTRY_COMPONENTS as well as DECLARATIONS. @@ -31,6 +32,7 @@ const DECLARATIONS = [ UntypedItemComponent, FooterComponent, BadgesComponent, + ComcolPageBrowseByComponent, ]; @NgModule({