diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts
index dc9b427634..9b1a2f28c9 100644
--- a/src/app/app-routes.ts
+++ b/src/app/app-routes.ts
@@ -109,14 +109,12 @@ export const APP_ROUTES: Route[] = [
path: COMMUNITY_MODULE_PATH,
loadChildren: () => import('./community-page/community-page-routes')
.then((m) => m.ROUTES),
- data: { enableRSS: true },
canActivate: [endUserAgreementCurrentUserGuard],
},
{
path: COLLECTION_MODULE_PATH,
loadChildren: () => import('./collection-page/collection-page-routes')
.then((m) => m.ROUTES),
- data: { enableRSS: true },
canActivate: [endUserAgreementCurrentUserGuard],
},
{
diff --git a/src/app/collection-page/collection-page-routes.ts b/src/app/collection-page/collection-page-routes.ts
index d03c57779f..ce583b2cd5 100644
--- a/src/app/collection-page/collection-page-routes.ts
+++ b/src/app/collection-page/collection-page-routes.ts
@@ -100,6 +100,7 @@ export const ROUTES: Route[] = [
data: {
breadcrumbKey: 'collection.search',
menuRoute: MenuRoute.COLLECTION_PAGE,
+ enableRSS: true,
},
},
{
diff --git a/src/app/community-page/community-page-routes.ts b/src/app/community-page/community-page-routes.ts
index 35eb7780c8..979525ee1e 100644
--- a/src/app/community-page/community-page-routes.ts
+++ b/src/app/community-page/community-page-routes.ts
@@ -90,6 +90,7 @@ export const ROUTES: Route[] = [
data: {
breadcrumbKey: 'community.search',
menuRoute: MenuRoute.COMMUNITY_PAGE,
+ enableRSS: true,
},
},
{
diff --git a/src/app/home-page/top-level-community-list/top-level-community-list.component.html b/src/app/home-page/top-level-community-list/top-level-community-list.component.html
index 00446d658a..8b0ee4b853 100644
--- a/src/app/home-page/top-level-community-list/top-level-community-list.component.html
+++ b/src/app/home-page/top-level-community-list/top-level-community-list.component.html
@@ -8,6 +8,7 @@
diff --git a/src/app/home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/home-page/top-level-community-list/top-level-community-list.component.ts
index 4af7bcc483..7f3e384df8 100644
--- a/src/app/home-page/top-level-community-list/top-level-community-list.component.ts
+++ b/src/app/home-page/top-level-community-list/top-level-community-list.component.ts
@@ -65,9 +65,10 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
pageId = 'tl';
/**
- * The sorting configuration
+ * The sorting configuration for the community list itself, and the optional RSS feed button
*/
sortConfig: SortOptions;
+ rssSortConfig: SortOptions;
/**
* The subscription to the observable for the current page.
@@ -84,6 +85,7 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
this.config.pageSize = appConfig.homePage.topLevelCommunityList.pageSize;
this.config.currentPage = 1;
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
+ this.rssSortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
}
ngOnInit() {
diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html
index 10378ba3f5..1d9be701d2 100644
--- a/src/app/shared/object-collection/object-collection.component.html
+++ b/src/app/shared/object-collection/object-collection.component.html
@@ -13,6 +13,7 @@
[linkType]="linkType"
[context]="context"
[hidePaginationDetail]="hidePaginationDetail"
+ [showRSS]="showRSS"
[showPaginator]="showPaginator"
[showThumbnails]="showThumbnails"
(paginationChange)="onPaginationChange($event)"
@@ -58,6 +59,7 @@
[sortConfig]="sortConfig"
[objects]="objects"
[hideGear]="hideGear"
+ [showRSS]="showRSS"
[linkType]="linkType"
[context]="context"
[hidePaginationDetail]="hidePaginationDetail"
diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts
index 204685930a..d44c0153b2 100644
--- a/src/app/shared/object-collection/object-collection.component.ts
+++ b/src/app/shared/object-collection/object-collection.component.ts
@@ -71,17 +71,22 @@ export class ObjectCollectionComponent implements OnInit {
@Input() sortConfig: SortOptions;
/**
- * Whether or not the list elements have a border or not
+ * Whether the list elements have a border or not
*/
@Input() hasBorder = false;
/**
- * Whether or not to hide the gear to change the sort and pagination configuration
+ * Whether to hide the gear to change the sort and pagination configuration
*/
@Input() hideGear = false;
@Input() selectable = false;
@Input() selectionConfig: {repeatable: boolean, listId: string};
+ /**
+ * Whether to show an RSS syndication button for the current search options
+ */
+ @Input() showRSS: SortOptions | boolean = false;
+
/**
* Emit custom event for listable object custom actions.
*/
diff --git a/src/app/shared/object-detail/object-detail.component.html b/src/app/shared/object-detail/object-detail.component.html
index ad9f0bce0e..2696730cf6 100644
--- a/src/app/shared/object-detail/object-detail.component.html
+++ b/src/app/shared/object-detail/object-detail.component.html
@@ -4,6 +4,7 @@
[sortOptions]="sortConfig"
[objects]="objects"
[hideGear]="hideGear"
+ [showRSS]="showRSS"
[hidePaginationDetail]="hidePaginationDetail"
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
[showPaginator]="showPaginator"
diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts
index 01e1795fcb..67037aa6bd 100644
--- a/src/app/shared/object-detail/object-detail.component.ts
+++ b/src/app/shared/object-detail/object-detail.component.ts
@@ -85,6 +85,11 @@ export class ObjectDetailComponent {
*/
@Input() showThumbnails;
+ /**
+ * Whether to show the RSS syndication link. Either false, or valid SortOptions object
+ */
+ @Input() showRSS: SortOptions | boolean = false;
+
/**
* Emit when one of the listed object has changed.
*/
diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html
index 3073623c2e..5670199fc5 100644
--- a/src/app/shared/object-list/object-list.component.html
+++ b/src/app/shared/object-list/object-list.component.html
@@ -4,6 +4,7 @@
[objects]="objects"
[sortOptions]="sortConfig"
[hideGear]="hideGear"
+ [showRSS]="showRSS"
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
[hidePaginationDetail]="hidePaginationDetail"
[showPaginator]="showPaginator"
diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts
index cf3e0164f6..3c0136fec9 100644
--- a/src/app/shared/object-list/object-list.component.ts
+++ b/src/app/shared/object-list/object-list.component.ts
@@ -39,7 +39,7 @@ import { SelectableListService } from './selectable-list/selectable-list.service
})
export class ObjectListComponent {
/**
- * The view mode of the this component
+ * The view mode of this component
*/
viewMode = ViewMode.ListElement;
@@ -70,6 +70,11 @@ export class ObjectListComponent {
@Input() selectable = false;
@Input() selectionConfig: { repeatable: boolean, listId: string };
+ /**
+ * Whether to show an RSS syndication button for the current search options
+ */
+ @Input() showRSS: SortOptions | boolean = false;
+
/**
* The link type of the listable elements
*/
diff --git a/src/app/shared/object-list/themed-object-list.component.ts b/src/app/shared/object-list/themed-object-list.component.ts
index e6e0527c90..df932e3a0f 100644
--- a/src/app/shared/object-list/themed-object-list.component.ts
+++ b/src/app/shared/object-list/themed-object-list.component.ts
@@ -59,6 +59,8 @@ export class ThemedObjectListComponent extends ThemedComponent
}
-
+ @if (showRSS !== false) {
+
+ }
diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts
index 907b427354..a10373af1e 100644
--- a/src/app/shared/pagination/pagination.component.ts
+++ b/src/app/shared/pagination/pagination.component.ts
@@ -168,6 +168,13 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
*/
@Input() public retainScrollPosition = false;
+ /**
+ * Options for showing or hiding the RSS syndication feed. This is useful for e.g. top-level community lists
+ * or other lists where an RSS feed doesn't make sense, but uses the same components as recent items or search result
+ * lists.
+ */
+ @Input() public showRSS: SortOptions | boolean = false;
+
/**
* Current page.
*/
@@ -266,7 +273,6 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
* Initializes all default variables
*/
private initializeConfig() {
- // Set initial values
this.id = this.paginationOptions.id || null;
this.pageSizeOptions = this.paginationOptions.pageSizeOptions;
this.currentPage$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(
@@ -436,4 +442,19 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
});
}
+ /**
+ * Get the sort options to use for the RSS feed. Defaults to the sort options used for this pagination component
+ * so it matches the search/browse context, but also allows more flexibility if, for example a top-level community
+ * list is displayed in "title asc" order, but the RSS feed should default to an item list of "date desc" order.
+ * If the SortOptions are null, incomplete or invalid, the pagination sortOptions will be used instead.
+ */
+ get rssSortOptions() {
+ if (this.showRSS !== false && this.showRSS instanceof SortOptions
+ && this.showRSS.direction !== null
+ && this.showRSS.field !== null) {
+ return this.showRSS;
+ }
+ return this.sortOptions;
+ }
+
}
diff --git a/src/app/shared/rss-feed/rss.component.ts b/src/app/shared/rss-feed/rss.component.ts
index 8d2b49f0e1..1ed29697bf 100644
--- a/src/app/shared/rss-feed/rss.component.ts
+++ b/src/app/shared/rss-feed/rss.component.ts
@@ -2,12 +2,16 @@ import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
+ Input,
+ OnChanges,
OnDestroy,
OnInit,
+ SimpleChanges,
ViewEncapsulation,
} from '@angular/core';
import {
ActivatedRoute,
+ NavigationEnd,
Router,
} from '@angular/router';
import {
@@ -16,12 +20,10 @@ import {
} from '@ngx-translate/core';
import {
BehaviorSubject,
+ filter,
Subscription,
} from 'rxjs';
-import {
- map,
- switchMap,
-} from 'rxjs/operators';
+import { map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { SortOptions } from '../../core/cache/models/sort-options.model';
@@ -36,8 +38,8 @@ import {
hasValue,
isUndefined,
} from '../empty.util';
-import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
import { SearchFilter } from '../search/models/search-filter.model';
+
/**
* The Rss feed button component.
*/
@@ -51,17 +53,16 @@ import { SearchFilter } from '../search/models/search-filter.model';
standalone: true,
imports: [AsyncPipe, TranslateModule],
})
-export class RSSComponent implements OnInit, OnDestroy {
+export class RSSComponent implements OnInit, OnDestroy, OnChanges {
route$: BehaviorSubject = new BehaviorSubject('');
-
isEnabled$: BehaviorSubject = new BehaviorSubject(null);
-
isActivated$: BehaviorSubject = new BehaviorSubject(false);
+ @Input() sortConfig?: SortOptions;
uuid: string;
-
subs: Subscription[] = [];
+ openSearchUri: string;
constructor(private groupDataService: GroupDataService,
private linkHeadService: LinkHeadService,
@@ -72,9 +73,7 @@ export class RSSComponent implements OnInit, OnDestroy {
protected paginationService: PaginationService,
protected translateService: TranslateService) {
}
- /**
- * Removes the linktag created when the component gets removed from the page.
- */
+
ngOnDestroy(): void {
this.linkHeadService.removeTag("rel='alternate'");
this.subs.forEach(sub => {
@@ -82,24 +81,52 @@ export class RSSComponent implements OnInit, OnDestroy {
});
}
-
- /**
- * Generates the link tags and the url to opensearch when the component is loaded.
- */
ngOnInit(): void {
+ // Set initial activation state
if (hasValue(this.route.snapshot.data?.enableRSS)) {
this.isActivated$.next(this.route.snapshot.data.enableRSS);
} else if (isUndefined(this.route.snapshot.data?.enableRSS)) {
this.isActivated$.next(false);
}
+
+ // Get initial UUID from URL
+ this.uuid = this.groupDataService.getUUIDFromString(this.router.url);
+
+ // Check if RSS is enabled
this.subs.push(this.configurationService.findByPropertyName('websvc.opensearch.enable').pipe(
getFirstCompletedRemoteData(),
).subscribe((result) => {
if (result.hasSucceeded) {
const enabled = (result.payload.values[0] === 'true');
this.isEnabled$.next(enabled);
+
+ // If enabled, get the OpenSearch URI
+ if (enabled) {
+ this.getOpenSearchUri();
+ }
}
}));
+
+ // Listen for navigation events to update the UUID
+ this.subs.push(this.router.events.pipe(
+ filter(event => event instanceof NavigationEnd),
+ ).subscribe(() => {
+ this.uuid = this.groupDataService.getUUIDFromString(this.router.url);
+ this.updateRssLinks();
+ }));
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ // If sortConfig changes, update the RSS links
+ if (changes.sortConfig && this.openSearchUri && this.isEnabled$.getValue()) {
+ this.updateRssLinks();
+ }
+ }
+
+ /**
+ * Get the OpenSearch URI and update RSS links
+ */
+ private getOpenSearchUri(): void {
this.subs.push(this.configurationService.findByPropertyName('websvc.opensearch.svccontext').pipe(
getFirstCompletedRemoteData(),
map((result: RemoteData) => {
@@ -108,35 +135,60 @@ export class RSSComponent implements OnInit, OnDestroy {
}
return null;
}),
- switchMap((openSearchUri: string) =>
- this.searchConfigurationService.paginatedSearchOptions.pipe(
- map((searchOptions: PaginatedSearchOptions) => ({ openSearchUri, searchOptions })),
- ),
- ),
- ).subscribe(({ openSearchUri, searchOptions }) => {
- if (!openSearchUri) {
- return null;
- }
- this.uuid = this.groupDataService.getUUIDFromString(this.router.url);
- const route = environment.rest.baseUrl + this.formulateRoute(this.uuid, openSearchUri, searchOptions.sort, searchOptions.query, searchOptions.filters, searchOptions.configuration, searchOptions.pagination?.pageSize, searchOptions.fixedFilter);
- this.addLinks(route);
- this.linkHeadService.addTag({
- href: environment.rest.baseUrl + '/' + openSearchUri + '/service',
- type: 'application/atom+xml',
- rel: 'search',
- title: 'Dspace',
- });
- this.route$.next(route);
+ filter(uri => !!uri),
+ ).subscribe(uri => {
+ this.openSearchUri = uri;
+ this.updateRssLinks();
}));
}
/**
- * Function created a route given the different params available to opensearch
- * @param uuid The uuid if a scope is present
- * @param opensearch openSearch uri
- * @param sort The sort options for the opensearch request
- * @param query The query string that was provided in the search
- * @returns The combine URL to opensearch
+ * Update RSS links based on current search configuration and sortConfig input
+ */
+ private updateRssLinks(): void {
+ if (!this.openSearchUri || !this.isEnabled$.getValue()) {
+ return;
+ }
+
+ // Remove existing link tags before adding new ones
+ this.linkHeadService.removeTag("rel='alternate'");
+
+ // Get the current search options and apply our sortConfig if provided
+ const searchOptions = this.searchConfigurationService.paginatedSearchOptions.value;
+ const modifiedOptions = { ...searchOptions };
+ if (hasValue(this.sortConfig)) {
+ modifiedOptions.sort = this.sortConfig;
+ }
+
+ // Create the RSS feed URL
+ const route = environment.rest.baseUrl + this.formulateRoute(
+ this.uuid,
+ this.openSearchUri,
+ modifiedOptions.sort,
+ modifiedOptions.query,
+ modifiedOptions.filters,
+ modifiedOptions.configuration,
+ modifiedOptions.pagination?.pageSize,
+ modifiedOptions.fixedFilter,
+ );
+
+ // Add the link tags
+ this.addLinks(route);
+
+ // Add the OpenSearch service link
+ this.linkHeadService.addTag({
+ href: environment.rest.baseUrl + '/' + this.openSearchUri + '/service',
+ type: 'application/atom+xml',
+ rel: 'search',
+ title: 'Dspace',
+ });
+
+ // Update the route subject
+ this.route$.next(route);
+ }
+
+ /**
+ * Create a route given the different params available to opensearch
*/
formulateRoute(uuid: string, opensearch: string, sort?: SortOptions, query?: string, searchFilters?: SearchFilter[], configuration?: string, pageSize?: number, fixedFilter?: string): string {
let route = 'format=atom';
@@ -158,9 +210,9 @@ export class RSSComponent implements OnInit, OnDestroy {
route += `&rpp=${pageSize}`;
}
if (searchFilters) {
- for (const filter of searchFilters) {
- for (const val of filter.values) {
- route += '&' + filter.key + '=' + encodeURIComponent(val) + (filter.operator ? ',' + filter.operator : '');
+ for (const searchFilter of searchFilters) {
+ for (const val of searchFilter.values) {
+ route += '&' + searchFilter.key + '=' + encodeURIComponent(val) + (searchFilter.operator ? ',' + searchFilter.operator : '');
}
}
}
@@ -171,20 +223,8 @@ export class RSSComponent implements OnInit, OnDestroy {
return route;
}
- /**
- * Check if the router url contains the specified route
- *
- * @param {string} route
- * @returns
- * @memberof MyComponent
- */
- hasRoute(route: string) {
- return this.router.url.includes(route);
- }
-
/**
* Creates tags in the header of the page
- * @param route The composed url to opensearch
*/
addLinks(route: string): void {
this.linkHeadService.addTag({
@@ -201,5 +241,4 @@ export class RSSComponent implements OnInit, OnDestroy {
title: 'Sitewide RSS feed',
});
}
-
}
diff --git a/src/app/shared/search/search-results/search-results.component.html b/src/app/shared/search/search-results/search-results.component.html
index 0026d0ea5b..75e5fdf9b4 100644
--- a/src/app/shared/search/search-results/search-results.component.html
+++ b/src/app/shared/search/search-results/search-results.component.html
@@ -25,6 +25,7 @@
[sortConfig]="searchConfig.sort"
[objects]="searchResults"
[hideGear]="true"
+ [showRSS]="true"
[selectable]="selectable"
[selectionConfig]="selectionConfig"
[linkType]="linkType"