From edfaee6aab58bcb3b1f3d122d3b70276b0c0d8fa Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 10 Dec 2024 13:54:57 +0100 Subject: [PATCH 01/54] 119276: Fixed search service first emitting cached stale values instead of waiting for non-stale response This was problematic for the places that used getFist operators. This is because they only emit data once, and the first value could be the old cached value --- src/app/core/shared/search/search.service.ts | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index a88a8b0d16..d9fd85f9a2 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -1,7 +1,7 @@ /* eslint-disable max-classes-per-file */ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; -import { map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take, skipWhile } from 'rxjs/operators'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { ResponseParsingService } from '../../data/parsing.service'; import { RemoteData } from '../../data/remote-data'; @@ -140,6 +140,7 @@ export class SearchService implements OnDestroy { search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { const href$ = this.getEndpoint(searchOptions); + let startTime: number; href$.pipe( take(1), map((href: string) => { @@ -163,6 +164,7 @@ export class SearchService implements OnDestroy { searchOptions: searchOptions }); + startTime = new Date().getTime(); this.requestService.send(request, useCachedVersionIfAvailable); }); @@ -170,7 +172,13 @@ export class SearchService implements OnDestroy { switchMap((href: string) => this.rdb.buildFromHref>(href)) ); - return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe( + // This skip ensures that if a stale object is present in the cache when you do a + // call it isn't immediately returned, but we wait until the remote data for the new request + // is created. If useCachedVersionIfAvailable is false it also ensures you don't get a + // cached completed object + skipWhile((rd: RemoteData>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)), + ); } /** @@ -291,9 +299,16 @@ export class SearchService implements OnDestroy { return FacetValueResponseParsingService; } }); + const startTime = new Date().getTime(); this.requestService.send(request, useCachedVersionIfAvailable); - return this.rdb.buildFromHref(href); + return this.rdb.buildFromHref(href).pipe( + // This skip ensures that if a stale object is present in the cache when you do a + // call it isn't immediately returned, but we wait until the remote data for the new request + // is created. If useCachedVersionIfAvailable is false it also ensures you don't get a + // cached completed object + skipWhile((rd: RemoteData) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)), + ); } /** From 58ff240c0525ce71c73046382f66cff458c970ad Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 23 Jan 2025 19:59:58 +0100 Subject: [PATCH 02/54] 119276: Added tests to prove that cached stale requests are not emitted Also cleaned up test class --- .../core/shared/search/search.service.spec.ts | 494 +++++++++++------- 1 file changed, 305 insertions(+), 189 deletions(-) diff --git a/src/app/core/shared/search/search.service.spec.ts b/src/app/core/shared/search/search.service.spec.ts index fe5b495ab0..e1060c9dc8 100644 --- a/src/app/core/shared/search/search.service.spec.ts +++ b/src/app/core/shared/search/search.service.spec.ts @@ -1,216 +1,332 @@ -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; -import { SearchService } from './search.service'; -import { Router, UrlTree } from '@angular/router'; -import { RequestService } from '../../data/request.service'; -import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; -import { RouterStub } from '../../../shared/testing/router.stub'; -import { HALEndpointService } from '../hal-endpoint.service'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; -import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model'; -import { RemoteData } from '../../data/remote-data'; -import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; -import { CommunityDataService } from '../../data/community-data.service'; -import { ViewMode } from '../view-mode.model'; -import { DSpaceObjectDataService } from '../../data/dspace-object-data.service'; -import { map } from 'rxjs/operators'; -import { RouteService } from '../../services/route.service'; -import { routeServiceStub } from '../../../shared/testing/route-service.stub'; -import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; -import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; -import { SearchObjects } from '../../../shared/search/models/search-objects.model'; -import { PaginationService } from '../../pagination/pagination.service'; -import { SearchConfigurationService } from './search-configuration.service'; -import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; -import { RequestEntry } from '../../data/request-entry.model'; +import { TestBed } from '@angular/core/testing'; +import { RouterModule } from '@angular/router'; import { Angulartics2 } from 'angulartics2'; -import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model'; -import anything = jasmine.anything; +import { of as observableOf } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; -@Component({ template: '' }) -class DummyComponent { -} +import { environment } from '../../../../environments/environment.test'; +import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock'; +import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; +import { FacetValues } from '../../../shared/search/models/facet-values.model'; +import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model'; +import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model'; +import { SearchObjects } from '../../../shared/search/models/search-objects.model'; +import { HALEndpointServiceStub } from '../../../shared/testing/hal-endpoint-service.stub'; +import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { routeServiceStub } from '../../../shared/testing/route-service.stub'; +import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service.stub'; +import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; +import { DSpaceObjectDataService } from '../../data/dspace-object-data.service'; +import { RemoteData } from '../../data/remote-data'; +import { RequestService } from '../../data/request.service'; +import { RequestEntryState } from '../../data/request-entry-state.model'; +import { PaginationService } from '../../pagination/pagination.service'; +import { RouteService } from '../../services/route.service'; +import { HALEndpointService } from '../hal-endpoint.service'; +import { ViewMode } from '../view-mode.model'; +import { SearchService } from './search.service'; +import { SearchConfigurationService } from './search-configuration.service'; +import anything = jasmine.anything; +import SpyObj = jasmine.SpyObj; describe('SearchService', () => { - describe('By default', () => { - let searchService: SearchService; - const router = new RouterStub(); - const route = new ActivatedRouteStub(); - const searchConfigService = { paginationID: 'page-id' }; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - CommonModule, - RouterTestingModule.withRoutes([ - { path: 'search', component: DummyComponent, pathMatch: 'full' }, - ]) - ], - declarations: [ - DummyComponent - ], - providers: [ - { provide: Router, useValue: router }, - { provide: RouteService, useValue: routeServiceStub }, - { provide: RequestService, useValue: getMockRequestService() }, - { provide: RemoteDataBuildService, useValue: {} }, - { provide: HALEndpointService, useValue: {} }, - { provide: CommunityDataService, useValue: {} }, - { provide: DSpaceObjectDataService, useValue: {} }, - { provide: PaginationService, useValue: {} }, - { provide: SearchConfigurationService, useValue: searchConfigService }, - { provide: Angulartics2, useValue: {} }, - SearchService - ], - }); - searchService = TestBed.inject(SearchService); - }); + let service: SearchService; - it('should return list view mode', () => { - searchService.getViewMode().subscribe((viewMode) => { - expect(viewMode).toBe(ViewMode.ListElement); - }); + let halService: HALEndpointServiceStub; + let paginationService: PaginationServiceStub; + let remoteDataBuildService: RemoteDataBuildService; + let requestService: SpyObj; + let routeService: RouteService; + let searchConfigService: SearchConfigurationServiceStub; + + let testScheduler: TestScheduler; + let msToLive: number; + let remoteDataTimestamp: number; + + beforeEach(() => { + halService = new HALEndpointServiceStub(environment.rest.baseUrl); + paginationService = new PaginationServiceStub(); + remoteDataBuildService = getMockRemoteDataBuildService(); + requestService = getMockRequestService(); + searchConfigService = new SearchConfigurationServiceStub(); + + initTestData(); + + TestBed.configureTestingModule({ + imports: [ + CommonModule, + RouterModule.forRoot([]), + ], + providers: [ + { provide: RouteService, useValue: routeServiceStub }, + { provide: RequestService, useValue: requestService }, + { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, + { provide: HALEndpointService, useValue: halService }, + { provide: DSpaceObjectDataService, useValue: {} }, + { provide: PaginationService, useValue: paginationService }, + { provide: SearchConfigurationService, useValue: searchConfigService }, + { provide: Angulartics2, useValue: {} }, + SearchService, + ], }); + service = TestBed.inject(SearchService); + routeService = TestBed.inject(RouteService); }); - describe('', () => { - let searchService: SearchService; - const router = new RouterStub(); - let routeService; - const halService = { - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - getEndpoint: () => { - } - /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ - - }; - - const remoteDataBuildService = { - toRemoteDataObservable: (requestEntryObs: Observable, payloadObs: Observable) => { - return observableCombineLatest([requestEntryObs, payloadObs]).pipe( - map(([req, pay]) => { - return { req, pay }; - }) - ); - }, - aggregate: (input: Observable>[]): Observable> => { - return createSuccessfulRemoteDataObject$([]); - }, - buildFromHref: (href: string): Observable> => { - return createSuccessfulRemoteDataObject$(Object.assign(new SearchObjects(), { - page: [] - })); - } - }; - - const paginationService = new PaginationServiceStub(); - const searchConfigService = { paginationID: 'page-id' }; - const requestService = getMockRequestService(); - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - CommonModule, - RouterTestingModule.withRoutes([ - { path: 'search', component: DummyComponent, pathMatch: 'full' }, - ]) - ], - declarations: [ - DummyComponent - ], - providers: [ - { provide: Router, useValue: router }, - { provide: RouteService, useValue: routeServiceStub }, - { provide: RequestService, useValue: requestService }, - { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, - { provide: HALEndpointService, useValue: halService }, - { provide: CommunityDataService, useValue: {} }, - { provide: DSpaceObjectDataService, useValue: {} }, - { provide: PaginationService, useValue: paginationService }, - { provide: SearchConfigurationService, useValue: searchConfigService }, - { provide: Angulartics2, useValue: {} }, - SearchService - ], - }); - searchService = TestBed.inject(SearchService); - routeService = TestBed.inject(RouteService); - const urlTree = Object.assign(new UrlTree(), { root: { children: { primary: 'search' } } }); - router.parseUrl.and.returnValue(urlTree); + function initTestData(): void { + testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); }); + msToLive = 15 * 60 * 1000; + // The response's lastUpdated equals the time of 60 seconds after the test started, ensuring they are not perceived + // as cached values. + remoteDataTimestamp = new Date().getTime() + 60 * 1000; + } + + describe('setViewMode', () => { it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => { - searchService.setViewMode(ViewMode.ListElement); - expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement } - ); + service.setViewMode(ViewMode.ListElement); + + expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement }); }); it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => { - searchService.setViewMode(ViewMode.GridElement); - expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement } - ); + service.setViewMode(ViewMode.GridElement); + + expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement }); + }); + }); + + describe('getViewMode', () => { + it('should return list view mode', () => { + testScheduler.run(({ expectObservable }) => { + expectObservable(service.getViewMode()).toBe('(a|)', { + a: ViewMode.ListElement, + }); + }); }); it('should return ViewMode.List when the viewMode is set to ViewMode.List in the ActivatedRoute', () => { - let viewMode = ViewMode.GridElement; - spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([ - ['view', ViewMode.ListElement], - ]))); + testScheduler.run(({ expectObservable }) => { + spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([ + ['view', ViewMode.ListElement], + ]))); - searchService.getViewMode().subscribe((mode) => viewMode = mode); - expect(viewMode).toEqual(ViewMode.ListElement); + expectObservable(service.getViewMode()).toBe('(a|)', { + a: ViewMode.ListElement, + }); + }); }); it('should return ViewMode.Grid when the viewMode is set to ViewMode.Grid in the ActivatedRoute', () => { - let viewMode = ViewMode.ListElement; - spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([ - ['view', ViewMode.GridElement], - ]))); - searchService.getViewMode().subscribe((mode) => viewMode = mode); - expect(viewMode).toEqual(ViewMode.GridElement); - }); + testScheduler.run(({ expectObservable }) => { + spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([ + ['view', ViewMode.GridElement], + ]))); - describe('when search is called', () => { - const endPoint = 'http://endpoint.com/test/test'; - const searchOptions = new PaginatedSearchOptions({}); - beforeEach(() => { - spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint)); - spyOn((searchService as any).rdb, 'buildFromHref').and.callThrough(); - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - searchService.search(searchOptions).subscribe((t) => { - }); // subscribe to make sure all methods are called - /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ - }); - - it('should call getEndpoint on the halService', () => { - expect((searchService as any).halService.getEndpoint).toHaveBeenCalled(); - }); - - it('should send out the request on the request service', () => { - expect((searchService as any).requestService.send).toHaveBeenCalled(); - }); - - it('should call getByHref on the request service with the correct request url', () => { - expect((searchService as any).rdb.buildFromHref).toHaveBeenCalledWith(endPoint); - }); - }); - - describe('when getFacetValuesFor is called with a filterQuery', () => { - it('should add the encoded filterQuery to the args list', () => { - jasmine.getEnv().allowRespy(true); - const spyRequest = spyOn((searchService as any), 'request').and.stub(); - spyOn(requestService, 'send').and.returnValue(true); - const searchFilterConfig = new SearchFilterConfig(); - searchFilterConfig._links = { - self: { - href: 'https://demo.dspace.org/', - }, - }; - - searchService.getFacetValuesFor(searchFilterConfig, 1, undefined, 'filter&Query'); - - expect(spyRequest).toHaveBeenCalledWith(anything(), 'https://demo.dspace.org?page=0&size=5&prefix=filter%26Query'); + expectObservable(service.getViewMode()).toBe('(a|)', { + a: ViewMode.GridElement, + }); }); }); }); + + describe('search', () => { + let remoteDataMocks: Record>>; + + beforeEach(() => { + remoteDataMocks = { + RequestPending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.RequestPending, undefined, undefined, undefined), + ResponsePending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.ResponsePending, undefined, undefined, undefined), + Success: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Success, undefined, new SearchObjects(), 200), + SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, new SearchObjects(), 200), + }; + }); + + describe('when useCachedVersionIfAvailable is true', () => { + it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets re-requested`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b-c-d-e'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.search(undefined, msToLive, true)).toBe(expected, values); + }); + }); + }); + + describe('when useCachedVersionIfAvailable is false', () => { + it('should not emit a cached completed RemoteData', () => { + // Old cached value from 1 minute before the test started + const oldCachedSucceededData: RemoteData> = Object.assign(new SearchObjects(), remoteDataMocks.Success, { + timeCompleted: remoteDataTimestamp - 2 * 60 * 1000, + lastUpdated: remoteDataTimestamp - 2 * 60 * 1000, + }); + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', { + a: oldCachedSucceededData, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.search(undefined, msToLive, false)).toBe(expected, values); + }); + }); + + it('should emit the first completed RemoteData since the request was made', () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b', { + a: remoteDataMocks.Success, + b: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.SuccessStale, + }; + expectObservable(service.search(undefined, msToLive, false)).toBe(expected, values); + }); + }); + }); + + it('should call getEndpoint on the halService', () => { + spyOn(halService, 'getEndpoint').and.callThrough(); + + service.search(new PaginatedSearchOptions({})).subscribe(); + + expect(halService.getEndpoint).toHaveBeenCalled(); + }); + + it('should send out the request on the request service', () => { + service.search(new PaginatedSearchOptions({})).subscribe(); + + expect(requestService.send).toHaveBeenCalled(); + }); + + it('should call getByHref on the request service with the correct request url', () => { + spyOn(remoteDataBuildService, 'buildFromHref').and.callThrough(); + + service.search(new PaginatedSearchOptions({})).subscribe(); + + expect(remoteDataBuildService.buildFromHref).toHaveBeenCalledWith(environment.rest.baseUrl + '/discover/search/objects'); + }); + }); + + describe('getFacetValuesFor', () => { + let remoteDataMocks: Record>; + let filterConfig: SearchFilterConfig; + + beforeEach(() => { + remoteDataMocks = { + RequestPending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.RequestPending, undefined, undefined, undefined), + ResponsePending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.ResponsePending, undefined, undefined, undefined), + Success: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Success, undefined, new FacetValues(), 200), + SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, new FacetValues(), 200), + }; + filterConfig = new SearchFilterConfig(); + filterConfig._links = { + self: { + href: environment.rest.baseUrl, + }, + }; + }); + + describe('when useCachedVersionIfAvailable is true', () => { + it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets re-requested`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b-c-d-e'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, true)).toBe(expected, values); + }); + }); + }); + + describe('when useCachedVersionIfAvailable is false', () => { + it('should not emit a cached completed RemoteData', () => { + // Old cached value from 1 minute before the test started + const oldCachedSucceededData: RemoteData = Object.assign(new FacetValues(), remoteDataMocks.Success, { + timeCompleted: remoteDataTimestamp - 2 * 60 * 1000, + lastUpdated: remoteDataTimestamp - 2 * 60 * 1000, + }); + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', { + a: oldCachedSucceededData, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, false)).toBe(expected, values); + }); + }); + + it('should emit the first completed RemoteData since the request was made', () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b', { + a: remoteDataMocks.Success, + b: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.SuccessStale, + }; + expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, false)).toBe(expected, values); + }); + }); + }); + + it('should encode the filterQuery', () => { + spyOn((service as any), 'request').and.callThrough(); + + service.getFacetValuesFor(filterConfig, 1, undefined, 'filter&Query'); + + expect((service as any).request).toHaveBeenCalledWith(anything(), environment.rest.baseUrl + '?page=0&size=5&prefix=filter%26Query'); + }); + }); }); From b69b21af6c558f2fa4f2f694d3f2cb713ea625a6 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 20 Jan 2025 16:45:36 +0100 Subject: [PATCH 03/54] 119612: UI warning that only first part of configured items will be exported --- .../search-export-csv.component.html | 17 ++++++++-- .../search-export-csv.component.ts | 34 ++++++++++++++++++- .../search-results.component.html | 3 +- src/assets/i18n/en.json5 | 2 ++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.html b/src/app/shared/search/search-export-csv/search-export-csv.component.html index 7bf8704300..35ef89b728 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.html +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.html @@ -1,7 +1,20 @@ + +
+

{{tooltipMsg | translate}}

+
+
+ + +
+

{{tooltipMsg | translate}}

+

{{exportLimitExceededMsg}}

+
+
+ \ No newline at end of file + diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts index f8ec2012b1..8b6252ada7 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -21,10 +21,12 @@ import { switchMap, } from 'rxjs/operators'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { RemoteData } from '../../../core/data/remote-data'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { getProcessDetailRoute } from '../../../process-page/process-page-routing.paths'; import { Process } from '../../../process-page/processes/process.model'; @@ -53,6 +55,11 @@ export class SearchExportCsvComponent implements OnInit { */ @Input() searchConfig: PaginatedSearchOptions; + /** + * The total number of items in the search results which can be exported + */ + @Input() total: number; + /** * Observable used to determine whether the button should be shown */ @@ -63,12 +70,18 @@ export class SearchExportCsvComponent implements OnInit { */ tooltipMsg = 'metadata-export-search.tooltip'; + exportLimitExceededKey = 'metadata-export-search.submit.error.limit-exceeded'; + + exportLimitExceededMsg = ''; + + shouldShowWarning$: Observable; + constructor(private scriptDataService: ScriptDataService, private authorizationDataService: AuthorizationDataService, private notificationsService: NotificationsService, private translateService: TranslateService, private router: Router, - ) { + private configurationService: ConfigurationDataService) { } ngOnInit(): void { @@ -78,6 +91,25 @@ export class SearchExportCsvComponent implements OnInit { map((canExecute: boolean) => canExecute), startWith(false), ); + this.shouldShowWarning$ = this.itemExceeds(); + } + + /** + * Checks if the export limit has been exceeded and updates the tooltip accordingly + */ + private itemExceeds(): Observable { + return this.configurationService.findByPropertyName('metadataexport.max.items').pipe( + getFirstCompletedRemoteData(), + map((response: RemoteData) => { + const limit = Number(response.payload?.values?.[0]); + if (response.hasSucceeded && limit < this.total) { + this.exportLimitExceededMsg = this.translateService.instant(this.exportLimitExceededKey, { limit: response.payload?.values?.[0] }); + return true; + } else { + return false; + } + }), + ); } /** 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 871b2b2ca8..10ef1ea2cb 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,6 +1,7 @@

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

- +
Date: Mon, 3 Feb 2025 10:11:28 +0100 Subject: [PATCH 04/54] 119612: Check if a warning should be shown on changes to the total elements of the search, default to 500 if no value for the configuration property was returned --- .../search-export-csv.component.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts index 8b6252ada7..2727c6cce7 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -5,7 +5,9 @@ import { import { Component, Input, + OnChanges, OnInit, + SimpleChanges, } from '@angular/core'; import { Router } from '@angular/router'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; @@ -48,7 +50,7 @@ import { SearchFilter } from '../models/search-filter.model'; /** * Display a button to export the current search results as csv */ -export class SearchExportCsvComponent implements OnInit { +export class SearchExportCsvComponent implements OnInit, OnChanges { /** * The current configuration of the search @@ -94,16 +96,22 @@ export class SearchExportCsvComponent implements OnInit { this.shouldShowWarning$ = this.itemExceeds(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.total) { + this.shouldShowWarning$ = this.itemExceeds(); + } + } + /** * Checks if the export limit has been exceeded and updates the tooltip accordingly */ private itemExceeds(): Observable { - return this.configurationService.findByPropertyName('metadataexport.max.items').pipe( + return this.configurationService.findByPropertyName('bulkedit.export.max.items').pipe( getFirstCompletedRemoteData(), map((response: RemoteData) => { - const limit = Number(response.payload?.values?.[0]); - if (response.hasSucceeded && limit < this.total) { - this.exportLimitExceededMsg = this.translateService.instant(this.exportLimitExceededKey, { limit: response.payload?.values?.[0] }); + const limit = Number(response.payload?.values?.[0]) || 500; + if (limit < this.total) { + this.exportLimitExceededMsg = this.translateService.instant(this.exportLimitExceededKey, { limit: String(limit) }); return true; } else { return false; From 8eaff7873761d8ef5c34af98780f28c16c374700 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 3 Feb 2025 11:20:18 +0100 Subject: [PATCH 05/54] 119612: aria-label to also include warning message if applicable --- .../search/search-export-csv/search-export-csv.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.html b/src/app/shared/search/search-export-csv/search-export-csv.component.html index 35ef89b728..8c92ff8fbd 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.html +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.html @@ -15,6 +15,6 @@ class="export-button btn btn-dark btn-sm" [ngbTooltip]="(shouldShowWarning$ | async) ? tipContentWarning : tipContent" (click)="export()" - [title]="tooltipMsg |translate" [attr.aria-label]="tooltipMsg |translate"> + [title]="tooltipMsg | translate" [attr.aria-label]="(shouldShowWarning$ | async) ? ((tooltipMsg | translate) + ' ' + exportLimitExceededMsg): (tooltipMsg | translate)"> From 858ec87f2f115d5bc73cf9e4d260750c3d9eb2bf Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Feb 2025 14:14:03 +0100 Subject: [PATCH 06/54] 126853: Themeable SubmissionSectionContainerComponent and SubmissionFormFooterComponent --- ...themed-submission-form-footer.component.ts | 27 +++++++++++++++++ .../form/submission-form.component.html | 8 ++--- .../themed-section-container.component.ts | 29 +++++++++++++++++++ src/app/submission/submission.module.ts | 4 +++ .../submission-form-footer.component.html | 0 .../submission-form-footer.component.scss | 0 .../submission-form-footer.component.ts | 15 ++++++++++ .../section-container.component.html | 0 .../section-container.component.scss | 0 .../container/section-container.component.ts | 15 ++++++++++ src/themes/custom/lazy-theme.module.ts | 4 +++ 11 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/app/submission/form/footer/themed-submission-form-footer.component.ts create mode 100644 src/app/submission/sections/container/themed-section-container.component.ts create mode 100644 src/themes/custom/app/submission/form/footer/submission-form-footer.component.html create mode 100644 src/themes/custom/app/submission/form/footer/submission-form-footer.component.scss create mode 100644 src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts create mode 100644 src/themes/custom/app/submission/sections/container/section-container.component.html create mode 100644 src/themes/custom/app/submission/sections/container/section-container.component.scss create mode 100644 src/themes/custom/app/submission/sections/container/section-container.component.ts diff --git a/src/app/submission/form/footer/themed-submission-form-footer.component.ts b/src/app/submission/form/footer/themed-submission-form-footer.component.ts new file mode 100644 index 0000000000..041a090f32 --- /dev/null +++ b/src/app/submission/form/footer/themed-submission-form-footer.component.ts @@ -0,0 +1,27 @@ +import { ThemedComponent } from '../../../shared/theme-support/themed.component'; +import { SubmissionFormFooterComponent } from './submission-form-footer.component'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ds-themed-submission-form-footer', + styleUrls: [], + templateUrl: '../../../shared/theme-support/themed.component.html', +}) +export class ThemedSubmissionFormFooterComponent extends ThemedComponent { + @Input() submissionId: string; + + protected inAndOutputNames: (keyof SubmissionFormFooterComponent & keyof this)[] = ['submissionId']; + + protected getComponentName(): string { + return 'SubmissionFormFooterComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/submission/form/footer/submission-form-footer.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./submission-form-footer.component`); + } + +} diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index 4a916cfe23..9ca8090564 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -28,12 +28,12 @@
- +
diff --git a/src/app/submission/sections/container/themed-section-container.component.ts b/src/app/submission/sections/container/themed-section-container.component.ts new file mode 100644 index 0000000000..11ec6b63d7 --- /dev/null +++ b/src/app/submission/sections/container/themed-section-container.component.ts @@ -0,0 +1,29 @@ +import { ThemedComponent } from '../../../shared/theme-support/themed.component'; +import { SubmissionSectionContainerComponent } from './section-container.component'; +import { Component, Input } from '@angular/core'; +import { SectionDataObject } from '../models/section-data.model'; + +@Component({ + selector: 'ds-themed-submission-section-container', + styleUrls: [], + templateUrl: '../../../shared/theme-support/themed.component.html', +}) +export class ThemedSubmissionSectionContainerComponent extends ThemedComponent { + @Input() collectionId: string; + @Input() sectionData: SectionDataObject; + @Input() submissionId: string; + + protected inAndOutputNames: (keyof SubmissionSectionContainerComponent & keyof this)[] = ['collectionId', 'sectionData', 'submissionId']; + + protected getComponentName(): string { + return 'SubmissionSectionContainerComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/submission/sections/container/section-container.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./section-container.component`); + } +} diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index cf0ab2b369..8807a76fe4 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -67,6 +67,8 @@ import { } from './sections/sherpa-policies/metadata-information/metadata-information.component'; import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component'; +import { ThemedSubmissionSectionContainerComponent } from './sections/container/themed-section-container.component'; +import { ThemedSubmissionFormFooterComponent } from './form/footer/themed-submission-form-footer.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -106,6 +108,8 @@ const DECLARATIONS = [ PublicationInformationComponent, MetadataInformationComponent, ThemedSubmissionSectionUploadFileComponent, + ThemedSubmissionSectionContainerComponent, + ThemedSubmissionFormFooterComponent, ]; @NgModule({ diff --git a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.html b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.scss b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts new file mode 100644 index 0000000000..3a93410edf --- /dev/null +++ b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts @@ -0,0 +1,15 @@ +import { + SubmissionFormFooterComponent as BaseComponent +} from '../../../../../../app/submission/form/footer/submission-form-footer.component'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-submission-form-footer', + // styleUrls: ['./submission-form-footer.component.scss'], + styleUrls: ['../../../../../../app/submission/form/footer/submission-form-footer.component.scss'], + // templateUrl: './submission-form-footer.component.html' + templateUrl: '../../../../../../app/submission/form/footer/submission-form-footer.component.html' +}) +export class SubmissionFormFooterComponent extends BaseComponent { + +} diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.html b/src/themes/custom/app/submission/sections/container/section-container.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.scss b/src/themes/custom/app/submission/sections/container/section-container.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.ts b/src/themes/custom/app/submission/sections/container/section-container.component.ts new file mode 100644 index 0000000000..fcb6855f78 --- /dev/null +++ b/src/themes/custom/app/submission/sections/container/section-container.component.ts @@ -0,0 +1,15 @@ +import { + SubmissionSectionContainerComponent as BaseComponent +} from '../../../../../../app/submission/sections/container/section-container.component'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-submission-upload-section-file', + // styleUrls: ['./section-container.component.scss'], + styleUrls: ['../../../../../../app/submission/sections/container/section-container.component.scss'], + // templateUrl: './section-container.component.html' + templateUrl: '../../../../../../app/submission/sections/container/section-container.component.html' +}) +export class SubmissionSectionContainerComponent extends BaseComponent { + +} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index edb3f5478c..a52ba8f04a 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -156,6 +156,8 @@ import { ItemStatusComponent } from './app/item-page/edit-item-page/item-status/ import { EditBitstreamPageComponent } from './app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component'; import { FormModule } from '../../app/shared/form/form.module'; import { RequestCopyModule } from 'src/app/request-copy/request-copy.module'; +import { SubmissionSectionContainerComponent } from './app/submission/sections/container/section-container.component'; +import { SubmissionFormFooterComponent } from './app/submission/form/footer/submission-form-footer.component'; const DECLARATIONS = [ FileSectionComponent, @@ -239,6 +241,8 @@ const DECLARATIONS = [ SubmissionSectionUploadFileComponent, ItemStatusComponent, EditBitstreamPageComponent, + SubmissionSectionContainerComponent, + SubmissionFormFooterComponent, ]; @NgModule({ From 261af2a2f53417e59063dc8a38007c14015e5333 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 3 Mar 2025 10:30:16 +0100 Subject: [PATCH 07/54] 126853: ThemedSubmissionFormComponent --- .../edit/submission-edit.component.html | 4 +- .../form/themed-submission-form.component.ts | 44 +++++++++++++++++++ src/app/submission/submission.module.ts | 2 + .../form/submission-form.component.html | 0 .../form/submission-form.component.scss | 0 .../form/submission-form.component.ts | 15 +++++++ src/themes/custom/lazy-theme.module.ts | 2 + 7 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/app/submission/form/themed-submission-form.component.ts create mode 100644 src/themes/custom/app/submission/form/submission-form.component.html create mode 100644 src/themes/custom/app/submission/form/submission-form.component.scss create mode 100644 src/themes/custom/app/submission/form/submission-form.component.ts diff --git a/src/app/submission/edit/submission-edit.component.html b/src/app/submission/edit/submission-edit.component.html index 9c0e9eae72..71702d8c7d 100644 --- a/src/app/submission/edit/submission-edit.component.html +++ b/src/app/submission/edit/submission-edit.component.html @@ -1,10 +1,10 @@
- + [submissionId]="submissionId">
diff --git a/src/app/submission/form/themed-submission-form.component.ts b/src/app/submission/form/themed-submission-form.component.ts new file mode 100644 index 0000000000..96b5ecebd5 --- /dev/null +++ b/src/app/submission/form/themed-submission-form.component.ts @@ -0,0 +1,44 @@ +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { SubmissionFormComponent } from './submission-form.component'; +import { Component, Input } from '@angular/core'; +import { Item } from '../../core/shared/item.model'; +import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; +import { SubmissionError } from '../objects/submission-error.model'; +import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; + +@Component({ + selector: 'ds-themed-submission-form', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) +export class ThemedSubmissionFormComponent extends ThemedComponent { + @Input() collectionId: string; + + @Input() item: Item; + + @Input() collectionModifiable: boolean | null = null; + + @Input() sections: WorkspaceitemSectionsObject; + + @Input() submissionErrors: SubmissionError; + + @Input() selfUrl: string; + + @Input() submissionDefinition: SubmissionDefinitionsModel; + + @Input() submissionId: string; + + protected inAndOutputNames: (keyof SubmissionFormComponent & keyof this)[] = ['collectionId', 'item', 'collectionModifiable', 'sections', 'submissionErrors', 'selfUrl', 'submissionDefinition', 'submissionId']; + + protected getComponentName(): string { + return 'SubmissionFormComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/submission/form/submission-form.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./submission-form.component`); + } +} diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 8807a76fe4..c710b3b297 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -69,6 +69,7 @@ import { SectionFormOperationsService } from './sections/form/section-form-opera import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component'; import { ThemedSubmissionSectionContainerComponent } from './sections/container/themed-section-container.component'; import { ThemedSubmissionFormFooterComponent } from './form/footer/themed-submission-form-footer.component'; +import { ThemedSubmissionFormComponent } from './form/themed-submission-form.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -110,6 +111,7 @@ const DECLARATIONS = [ ThemedSubmissionSectionUploadFileComponent, ThemedSubmissionSectionContainerComponent, ThemedSubmissionFormFooterComponent, + ThemedSubmissionFormComponent, ]; @NgModule({ diff --git a/src/themes/custom/app/submission/form/submission-form.component.html b/src/themes/custom/app/submission/form/submission-form.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/form/submission-form.component.scss b/src/themes/custom/app/submission/form/submission-form.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/submission/form/submission-form.component.ts b/src/themes/custom/app/submission/form/submission-form.component.ts new file mode 100644 index 0000000000..15b52a2657 --- /dev/null +++ b/src/themes/custom/app/submission/form/submission-form.component.ts @@ -0,0 +1,15 @@ +import { + SubmissionFormComponent as BaseComponent +} from '../../../../../app/submission/form/submission-form.component'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-submission-form', + // styleUrls: ['./submission-form.component.scss'], + styleUrls: ['../../../../../app/submission/form/submission-form.component.scss'], + // templateUrl: './submission-form.component.html' + templateUrl: '../../../../../app/submission/form/submission-form.component.html' +}) +export class SubmissionFormComponent extends BaseComponent { + +} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index a52ba8f04a..de7aea3243 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -158,6 +158,7 @@ import { FormModule } from '../../app/shared/form/form.module'; import { RequestCopyModule } from 'src/app/request-copy/request-copy.module'; import { SubmissionSectionContainerComponent } from './app/submission/sections/container/section-container.component'; import { SubmissionFormFooterComponent } from './app/submission/form/footer/submission-form-footer.component'; +import { SubmissionFormComponent } from './app/submission/form/submission-form.component'; const DECLARATIONS = [ FileSectionComponent, @@ -243,6 +244,7 @@ const DECLARATIONS = [ EditBitstreamPageComponent, SubmissionSectionContainerComponent, SubmissionFormFooterComponent, + SubmissionFormComponent, ]; @NgModule({ From c0bfc3a73982ecc45eff16e1b40b342b5433f826 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 6 Mar 2025 14:34:25 +0100 Subject: [PATCH 08/54] apply spellCheck configuration to input model --- src/app/shared/form/builder/parsers/onebox-field-parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts index 50fc585abf..2ae456a6ea 100644 --- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts +++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts @@ -90,6 +90,7 @@ export class OneboxFieldParser extends FieldParser { return new DynamicOneboxModel(oneboxModelConfig); } else { const inputModelConfig: DsDynamicInputModelConfig = this.initModel(null, label); + inputModelConfig.spellCheck = environment.form.spellCheck; this.setValues(inputModelConfig, fieldValue); return new DsDynamicInputModel(inputModelConfig); From c835b215af0cb79308b31a5b5dd6b89104ad1420 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 6 Mar 2025 14:43:41 +0100 Subject: [PATCH 09/54] add import --- src/app/shared/form/builder/parsers/onebox-field-parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts index 2ae456a6ea..e5a951359b 100644 --- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts +++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts @@ -21,6 +21,7 @@ import { } from '../ds-dynamic-form-ui/models/onebox/dynamic-onebox.model'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { FieldParser } from './field-parser'; +import { environment } from '../../../../../environments/environment'; export class OneboxFieldParser extends FieldParser { From 13ae42f217fa7f32b7a96995322538b2aaf12c84 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 6 Mar 2025 14:59:41 +0100 Subject: [PATCH 10/54] reorganize imports --- src/app/shared/form/builder/parsers/onebox-field-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts index e5a951359b..23c5d73efb 100644 --- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts +++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts @@ -3,6 +3,7 @@ import { DynamicSelectModelConfig, } from '@ng-dynamic-forms/core'; +import { environment } from '../../../../../environments/environment'; import { isNotEmpty } from '../../../empty.util'; import { DsDynamicInputModel, @@ -21,7 +22,6 @@ import { } from '../ds-dynamic-form-ui/models/onebox/dynamic-onebox.model'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { FieldParser } from './field-parser'; -import { environment } from '../../../../../environments/environment'; export class OneboxFieldParser extends FieldParser { From da2c98aa3224b2a2af48530ab142196e7aa8b874 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Fri, 14 Mar 2025 13:45:43 +0200 Subject: [PATCH 11/54] Fix lint issues --- .../edit/submission-edit.component.html | 4 +- .../edit/submission-edit.component.ts | 4 +- .../submission-form-footer.component.ts | 2 +- ...themed-submission-form-footer.component.ts | 10 +- .../form/submission-form.component.html | 8 +- .../form/submission-form.component.ts | 10 +- .../form/themed-submission-form.component.ts | 16 ++- .../container/section-container.component.ts | 2 +- .../themed-section-container.component.ts | 12 +- src/app/submission/submission.module.ts | 130 ++++++++---------- .../edit/submission-edit.component.ts | 4 +- .../submission-form-footer.component.ts | 10 +- .../form/submission-form.component.ts | 10 +- .../container/section-container.component.ts | 10 +- src/themes/custom/lazy-theme.module.ts | 6 +- 15 files changed, 119 insertions(+), 119 deletions(-) diff --git a/src/app/submission/edit/submission-edit.component.html b/src/app/submission/edit/submission-edit.component.html index 71702d8c7d..9c0e9eae72 100644 --- a/src/app/submission/edit/submission-edit.component.html +++ b/src/app/submission/edit/submission-edit.component.html @@ -1,10 +1,10 @@
- + [submissionId]="submissionId">
diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 822818ee24..c2a914c85d 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -36,7 +36,7 @@ import { isNotNull, } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { SubmissionFormComponent } from '../form/submission-form.component'; +import { ThemedSubmissionFormComponent } from '../form/themed-submission-form.component'; import { SubmissionError } from '../objects/submission-error.model'; import { SubmissionService } from '../submission.service'; import parseSectionErrors from '../utils/parseSectionErrors'; @@ -50,7 +50,7 @@ import parseSectionErrors from '../utils/parseSectionErrors'; templateUrl: './submission-edit.component.html', standalone: true, imports: [ - SubmissionFormComponent, + ThemedSubmissionFormComponent, ], }) export class SubmissionEditComponent implements OnDestroy, OnInit { diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 8645003783..a61e2599a2 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -24,7 +24,7 @@ import { SubmissionService } from '../../submission.service'; * This component represents submission form footer bar. */ @Component({ - selector: 'ds-submission-form-footer', + selector: 'ds-base-submission-form-footer', styleUrls: ['./submission-form-footer.component.scss'], templateUrl: './submission-form-footer.component.html', standalone: true, diff --git a/src/app/submission/form/footer/themed-submission-form-footer.component.ts b/src/app/submission/form/footer/themed-submission-form-footer.component.ts index 041a090f32..82240abcf5 100644 --- a/src/app/submission/form/footer/themed-submission-form-footer.component.ts +++ b/src/app/submission/form/footer/themed-submission-form-footer.component.ts @@ -1,11 +1,17 @@ +import { + Component, + Input, +} from '@angular/core'; + import { ThemedComponent } from '../../../shared/theme-support/themed.component'; import { SubmissionFormFooterComponent } from './submission-form-footer.component'; -import { Component, Input } from '@angular/core'; @Component({ - selector: 'ds-themed-submission-form-footer', + selector: 'ds-submission-form-footer', styleUrls: [], templateUrl: '../../../shared/theme-support/themed.component.html', + standalone: true, + imports: [SubmissionFormFooterComponent], }) export class ThemedSubmissionFormFooterComponent extends ThemedComponent { @Input() submissionId: string; diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index fc424ed60d..802d0e1c4c 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -31,18 +31,18 @@
@if ((isLoading() | async)) { - + } @for (object of $any(submissionSections | async); track object) { - - + }
@if ((isLoading() | async) !== true) { } diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index 74c262befc..7a1d67f871 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -38,14 +38,14 @@ import { UploaderOptions } from '../../shared/upload/uploader/uploader-options.m import { SectionVisibility } from '../objects/section-visibility.model'; import { SubmissionError } from '../objects/submission-error.model'; import { SubmissionObjectEntry } from '../objects/submission-objects.reducer'; -import { SubmissionSectionContainerComponent } from '../sections/container/section-container.component'; +import { ThemedSubmissionSectionContainerComponent } from '../sections/container/themed-section-container.component'; import { SectionDataObject } from '../sections/models/section-data.model'; import { SectionsService } from '../sections/sections.service'; import { SectionsType } from '../sections/sections-type'; import { VisibilityType } from '../sections/visibility-type'; import { SubmissionService } from '../submission.service'; import { SubmissionFormCollectionComponent } from './collection/submission-form-collection.component'; -import { SubmissionFormFooterComponent } from './footer/submission-form-footer.component'; +import { ThemedSubmissionFormFooterComponent } from './footer/themed-submission-form-footer.component'; import { SubmissionFormSectionAddComponent } from './section-add/submission-form-section-add.component'; import { ThemedSubmissionUploadFilesComponent } from './submission-upload-files/themed-submission-upload-files.component'; @@ -53,14 +53,14 @@ import { ThemedSubmissionUploadFilesComponent } from './submission-upload-files/ * This component represents the submission form. */ @Component({ - selector: 'ds-submission-form', + selector: 'ds-base-submission-form', styleUrls: ['./submission-form.component.scss'], templateUrl: './submission-form.component.html', imports: [ CommonModule, ThemedLoadingComponent, - SubmissionSectionContainerComponent, - SubmissionFormFooterComponent, + ThemedSubmissionSectionContainerComponent, + ThemedSubmissionFormFooterComponent, ThemedSubmissionUploadFilesComponent, SubmissionFormCollectionComponent, SubmissionFormSectionAddComponent, diff --git a/src/app/submission/form/themed-submission-form.component.ts b/src/app/submission/form/themed-submission-form.component.ts index 96b5ecebd5..b7414aa8e9 100644 --- a/src/app/submission/form/themed-submission-form.component.ts +++ b/src/app/submission/form/themed-submission-form.component.ts @@ -1,15 +1,21 @@ -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { SubmissionFormComponent } from './submission-form.component'; -import { Component, Input } from '@angular/core'; +import { + Component, + Input, +} from '@angular/core'; + +import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { Item } from '../../core/shared/item.model'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { SubmissionError } from '../objects/submission-error.model'; -import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; +import { SubmissionFormComponent } from './submission-form.component'; @Component({ - selector: 'ds-themed-submission-form', + selector: 'ds-submission-form', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', + standalone: true, + imports: [SubmissionFormComponent], }) export class ThemedSubmissionFormComponent extends ThemedComponent { @Input() collectionId: string; diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index 96f8cbcff6..174aed93b5 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -23,7 +23,7 @@ import { rendersSectionType } from '../sections-decorator'; * This component represents a section that contains the submission license form. */ @Component({ - selector: 'ds-submission-section-container', + selector: 'ds-base-submission-section-container', templateUrl: './section-container.component.html', styleUrls: ['./section-container.component.scss'], imports: [ diff --git a/src/app/submission/sections/container/themed-section-container.component.ts b/src/app/submission/sections/container/themed-section-container.component.ts index 11ec6b63d7..951fb2bdcb 100644 --- a/src/app/submission/sections/container/themed-section-container.component.ts +++ b/src/app/submission/sections/container/themed-section-container.component.ts @@ -1,12 +1,18 @@ +import { + Component, + Input, +} from '@angular/core'; + import { ThemedComponent } from '../../../shared/theme-support/themed.component'; -import { SubmissionSectionContainerComponent } from './section-container.component'; -import { Component, Input } from '@angular/core'; import { SectionDataObject } from '../models/section-data.model'; +import { SubmissionSectionContainerComponent } from './section-container.component'; @Component({ - selector: 'ds-themed-submission-section-container', + selector: 'ds-submission-section-container', styleUrls: [], templateUrl: '../../../shared/theme-support/themed.component.html', + standalone: true, + imports: [SubmissionSectionContainerComponent], }) export class ThemedSubmissionSectionContainerComponent extends ThemedComponent { @Input() collectionId: string; diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index c710b3b297..59305dd60e 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -1,75 +1,64 @@ -import { NgModule } from '@angular/core'; -import { CoreModule } from '../core/core.module'; -import { SharedModule } from '../shared/shared.module'; - -import { SubmissionSectionFormComponent } from './sections/form/section-form.component'; -import { SectionsDirective } from './sections/sections.directive'; -import { SectionsService } from './sections/sections.service'; -import { SubmissionFormCollectionComponent } from './form/collection/submission-form-collection.component'; -import { SubmissionFormFooterComponent } from './form/footer/submission-form-footer.component'; -import { SubmissionFormComponent } from './form/submission-form.component'; -import { SubmissionFormSectionAddComponent } from './form/section-add/submission-form-section-add.component'; -import { SubmissionSectionContainerComponent } from './sections/container/section-container.component'; import { CommonModule } from '@angular/common'; -import { Action, StoreConfig, StoreModule } from '@ngrx/store'; +import { NgModule } from '@angular/core'; +import { + NgbAccordionModule, + NgbCollapseModule, + NgbModalModule, +} from '@ng-bootstrap/ng-bootstrap'; import { EffectsModule } from '@ngrx/effects'; -import { submissionReducers, SubmissionState } from './submission.reducers'; -import { submissionEffects } from './submission.effects'; -import { SubmissionSectionUploadComponent } from './sections/upload/section-upload.component'; -import { SectionUploadService } from './sections/upload/section-upload.service'; -import { SubmissionUploadFilesComponent } from './form/submission-upload-files/submission-upload-files.component'; -import { SubmissionSectionLicenseComponent } from './sections/license/section-license.component'; +import { + Action, + StoreConfig, + StoreModule, +} from '@ngrx/store'; + +import { storeModuleConfig } from '../app.reducer'; +import { SubmissionAccessesConfigDataService } from '../core/config/submission-accesses-config-data.service'; import { SubmissionUploadsConfigDataService } from '../core/config/submission-uploads-config-data.service'; import { SubmissionEditComponent } from './edit/submission-edit.component'; -import { SubmissionSectionUploadFileComponent } from './sections/upload/file/section-upload-file.component'; -import { - SubmissionSectionUploadFileEditComponent -} from './sections/upload/file/edit/section-upload-file-edit.component'; -import { - SubmissionSectionUploadFileViewComponent -} from './sections/upload/file/view/section-upload-file-view.component'; -import { - SubmissionSectionUploadAccessConditionsComponent -} from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; -import { SubmissionSubmitComponent } from './submit/submission-submit.component'; -import { storeModuleConfig } from '../app.reducer'; -import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; -import { - SubmissionImportExternalSearchbarComponent -} from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; -import { - SubmissionImportExternalPreviewComponent -} from './import-external/import-external-preview/submission-import-external-preview.component'; -import { - SubmissionImportExternalCollectionComponent -} from './import-external/import-external-collection/submission-import-external-collection.component'; -import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component'; -import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; -import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; import { ThemedSubmissionEditComponent } from './edit/themed-submission-edit.component'; -import { ThemedSubmissionSubmitComponent } from './submit/themed-submission-submit.component'; -import { ThemedSubmissionImportExternalComponent } from './import-external/themed-submission-import-external.component'; -import { ThemedSubmissionSectionUploadFileComponent } from './sections/upload/file/themed-section-upload-file.component'; -import { FormModule } from '../shared/form/form.module'; -import { NgbAccordionModule, NgbCollapseModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; -import { SubmissionSectionAccessesComponent } from './sections/accesses/section-accesses.component'; -import { SubmissionAccessesConfigDataService } from '../core/config/submission-accesses-config-data.service'; -import { SectionAccessesService } from './sections/accesses/section-accesses.service'; -import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; -import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; -import { PublisherPolicyComponent } from './sections/sherpa-policies/publisher-policy/publisher-policy.component'; -import { - PublicationInformationComponent -} from './sections/sherpa-policies/publication-information/publication-information.component'; -import { UploadModule } from '../shared/upload/upload.module'; -import { - MetadataInformationComponent -} from './sections/sherpa-policies/metadata-information/metadata-information.component'; -import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; -import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component'; -import { ThemedSubmissionSectionContainerComponent } from './sections/container/themed-section-container.component'; +import { SubmissionFormCollectionComponent } from './form/collection/submission-form-collection.component'; +import { SubmissionFormFooterComponent } from './form/footer/submission-form-footer.component'; import { ThemedSubmissionFormFooterComponent } from './form/footer/themed-submission-form-footer.component'; +import { SubmissionFormSectionAddComponent } from './form/section-add/submission-form-section-add.component'; +import { SubmissionFormComponent } from './form/submission-form.component'; +import { SubmissionUploadFilesComponent } from './form/submission-upload-files/submission-upload-files.component'; import { ThemedSubmissionFormComponent } from './form/themed-submission-form.component'; +import { SubmissionImportExternalCollectionComponent } from './import-external/import-external-collection/submission-import-external-collection.component'; +import { SubmissionImportExternalPreviewComponent } from './import-external/import-external-preview/submission-import-external-preview.component'; +import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; +import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; +import { ThemedSubmissionImportExternalComponent } from './import-external/themed-submission-import-external.component'; +import { SubmissionSectionAccessesComponent } from './sections/accesses/section-accesses.component'; +import { SectionAccessesService } from './sections/accesses/section-accesses.service'; +import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component'; +import { SubmissionSectionContainerComponent } from './sections/container/section-container.component'; +import { ThemedSubmissionSectionContainerComponent } from './sections/container/themed-section-container.component'; +import { SubmissionSectionFormComponent } from './sections/form/section-form.component'; +import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; +import { SubmissionSectionIdentifiersComponent } from './sections/identifiers/section-identifiers.component'; +import { SubmissionSectionLicenseComponent } from './sections/license/section-license.component'; +import { SectionsDirective } from './sections/sections.directive'; +import { SectionsService } from './sections/sections.service'; +import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; +import { MetadataInformationComponent } from './sections/sherpa-policies/metadata-information/metadata-information.component'; +import { PublicationInformationComponent } from './sections/sherpa-policies/publication-information/publication-information.component'; +import { PublisherPolicyComponent } from './sections/sherpa-policies/publisher-policy/publisher-policy.component'; +import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; +import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; +import { SubmissionSectionUploadFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; +import { SubmissionSectionUploadFileComponent } from './sections/upload/file/section-upload-file.component'; +import { ThemedSubmissionSectionUploadFileComponent } from './sections/upload/file/themed-section-upload-file.component'; +import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; +import { SubmissionSectionUploadComponent } from './sections/upload/section-upload.component'; +import { SectionUploadService } from './sections/upload/section-upload.service'; +import { submissionEffects } from './submission.effects'; +import { + submissionReducers, + SubmissionState, +} from './submission.reducers'; +import { SubmissionSubmitComponent } from './submit/submission-submit.component'; +import { ThemedSubmissionSubmitComponent } from './submit/themed-submission-submit.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -117,22 +106,15 @@ const DECLARATIONS = [ @NgModule({ imports: [ CommonModule, - CoreModule.forRoot(), - SharedModule, StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), EffectsModule.forFeature(submissionEffects), - JournalEntitiesModule.withEntryComponents(), - ResearchEntitiesModule.withEntryComponents(), - FormModule, NgbModalModule, NgbCollapseModule, NgbAccordionModule, - UploadModule, ], declarations: DECLARATIONS, exports: [ ...DECLARATIONS, - FormModule, ], providers: [ SectionUploadService, @@ -141,7 +123,7 @@ const DECLARATIONS = [ SubmissionAccessesConfigDataService, SectionAccessesService, SectionFormOperationsService, - ] + ], }) /** @@ -155,7 +137,7 @@ export class SubmissionModule { static withEntryComponents() { return { ngModule: SubmissionModule, - providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })) + providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), }; } } diff --git a/src/themes/custom/app/submission/edit/submission-edit.component.ts b/src/themes/custom/app/submission/edit/submission-edit.component.ts index c41b4321fa..1afe61b44a 100644 --- a/src/themes/custom/app/submission/edit/submission-edit.component.ts +++ b/src/themes/custom/app/submission/edit/submission-edit.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { SubmissionEditComponent as BaseComponent } from '../../../../../app/submission/edit/submission-edit.component'; -import { SubmissionFormComponent } from '../../../../../app/submission/form/submission-form.component'; +import { ThemedSubmissionFormComponent } from '../../../../../app/submission/form/themed-submission-form.component'; /** * This component allows to edit an existing workspaceitem/workflowitem. @@ -14,7 +14,7 @@ import { SubmissionFormComponent } from '../../../../../app/submission/form/subm templateUrl: '../../../../../app/submission/edit/submission-edit.component.html', standalone: true, imports: [ - SubmissionFormComponent, + ThemedSubmissionFormComponent, ], }) export class SubmissionEditComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts index 3a93410edf..c1deceecfa 100644 --- a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts @@ -1,14 +1,14 @@ -import { - SubmissionFormFooterComponent as BaseComponent -} from '../../../../../../app/submission/form/footer/submission-form-footer.component'; import { Component } from '@angular/core'; +import { SubmissionFormFooterComponent as BaseComponent } from '../../../../../../app/submission/form/footer/submission-form-footer.component'; + @Component({ - selector: 'ds-submission-form-footer', + selector: 'ds-themed-submission-form-footer', // styleUrls: ['./submission-form-footer.component.scss'], styleUrls: ['../../../../../../app/submission/form/footer/submission-form-footer.component.scss'], // templateUrl: './submission-form-footer.component.html' - templateUrl: '../../../../../../app/submission/form/footer/submission-form-footer.component.html' + templateUrl: '../../../../../../app/submission/form/footer/submission-form-footer.component.html', + standalone: true, }) export class SubmissionFormFooterComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/form/submission-form.component.ts b/src/themes/custom/app/submission/form/submission-form.component.ts index 15b52a2657..1c2cf07d09 100644 --- a/src/themes/custom/app/submission/form/submission-form.component.ts +++ b/src/themes/custom/app/submission/form/submission-form.component.ts @@ -1,14 +1,14 @@ -import { - SubmissionFormComponent as BaseComponent -} from '../../../../../app/submission/form/submission-form.component'; import { Component } from '@angular/core'; +import { SubmissionFormComponent as BaseComponent } from '../../../../../app/submission/form/submission-form.component'; + @Component({ - selector: 'ds-submission-form', + selector: 'ds-themed-submission-form', // styleUrls: ['./submission-form.component.scss'], styleUrls: ['../../../../../app/submission/form/submission-form.component.scss'], // templateUrl: './submission-form.component.html' - templateUrl: '../../../../../app/submission/form/submission-form.component.html' + templateUrl: '../../../../../app/submission/form/submission-form.component.html', + standalone: true, }) export class SubmissionFormComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.ts b/src/themes/custom/app/submission/sections/container/section-container.component.ts index fcb6855f78..f24d679fb9 100644 --- a/src/themes/custom/app/submission/sections/container/section-container.component.ts +++ b/src/themes/custom/app/submission/sections/container/section-container.component.ts @@ -1,14 +1,14 @@ -import { - SubmissionSectionContainerComponent as BaseComponent -} from '../../../../../../app/submission/sections/container/section-container.component'; import { Component } from '@angular/core'; +import { SubmissionSectionContainerComponent as BaseComponent } from '../../../../../../app/submission/sections/container/section-container.component'; + @Component({ - selector: 'ds-submission-upload-section-file', + selector: 'ds-themed-submission-upload-section-file', // styleUrls: ['./section-container.component.scss'], styleUrls: ['../../../../../../app/submission/sections/container/section-container.component.scss'], // templateUrl: './section-container.component.html' - templateUrl: '../../../../../../app/submission/sections/container/section-container.component.html' + templateUrl: '../../../../../../app/submission/sections/container/section-container.component.html', + standalone: true, }) export class SubmissionSectionContainerComponent extends BaseComponent { diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index a3f4e21d07..bc8e9ed07c 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -100,17 +100,17 @@ import { CommunityStatisticsPageComponent } from './app/statistics-page/communit import { ItemStatisticsPageComponent } from './app/statistics-page/item-statistics-page/item-statistics-page.component'; import { SiteStatisticsPageComponent } from './app/statistics-page/site-statistics-page/site-statistics-page.component'; import { SubmissionEditComponent } from './app/submission/edit/submission-edit.component'; +import { SubmissionFormFooterComponent } from './app/submission/form/footer/submission-form-footer.component'; +import { SubmissionFormComponent } from './app/submission/form/submission-form.component'; import { SubmissionUploadFilesComponent } from './app/submission/form/submission-upload-files/submission-upload-files.component'; import { SubmissionImportExternalComponent } from './app/submission/import-external/submission-import-external.component'; +import { SubmissionSectionContainerComponent } from './app/submission/sections/container/section-container.component'; import { SubmissionSectionUploadFileComponent } from './app/submission/sections/upload/file/section-upload-file.component'; import { SubmissionSubmitComponent } from './app/submission/submit/submission-submit.component'; import { ThumbnailComponent } from './app/thumbnail/thumbnail.component'; import { WorkflowItemDeleteComponent } from './app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component'; import { WorkflowItemSendBackComponent } from './app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component'; import { WorkspaceItemsDeletePageComponent } from './app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component'; -import { SubmissionSectionContainerComponent } from './app/submission/sections/container/section-container.component'; -import { SubmissionFormFooterComponent } from './app/submission/form/footer/submission-form-footer.component'; -import { SubmissionFormComponent } from './app/submission/form/submission-form.component'; const DECLARATIONS = [ FileSectionComponent, From fa8ffaefd0d0a2a66a12231ab2ae71efbe9afd7c Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Fri, 28 Mar 2025 15:08:12 +0200 Subject: [PATCH 12/54] 127047: Fix imports --- src/app/submission/submission.module.ts | 143 ------------------ .../submission-form-footer.component.ts | 5 + .../form/submission-form.component.ts | 16 ++ .../container/section-container.component.ts | 18 +++ 4 files changed, 39 insertions(+), 143 deletions(-) delete mode 100644 src/app/submission/submission.module.ts diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts deleted file mode 100644 index 59305dd60e..0000000000 --- a/src/app/submission/submission.module.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { - NgbAccordionModule, - NgbCollapseModule, - NgbModalModule, -} from '@ng-bootstrap/ng-bootstrap'; -import { EffectsModule } from '@ngrx/effects'; -import { - Action, - StoreConfig, - StoreModule, -} from '@ngrx/store'; - -import { storeModuleConfig } from '../app.reducer'; -import { SubmissionAccessesConfigDataService } from '../core/config/submission-accesses-config-data.service'; -import { SubmissionUploadsConfigDataService } from '../core/config/submission-uploads-config-data.service'; -import { SubmissionEditComponent } from './edit/submission-edit.component'; -import { ThemedSubmissionEditComponent } from './edit/themed-submission-edit.component'; -import { SubmissionFormCollectionComponent } from './form/collection/submission-form-collection.component'; -import { SubmissionFormFooterComponent } from './form/footer/submission-form-footer.component'; -import { ThemedSubmissionFormFooterComponent } from './form/footer/themed-submission-form-footer.component'; -import { SubmissionFormSectionAddComponent } from './form/section-add/submission-form-section-add.component'; -import { SubmissionFormComponent } from './form/submission-form.component'; -import { SubmissionUploadFilesComponent } from './form/submission-upload-files/submission-upload-files.component'; -import { ThemedSubmissionFormComponent } from './form/themed-submission-form.component'; -import { SubmissionImportExternalCollectionComponent } from './import-external/import-external-collection/submission-import-external-collection.component'; -import { SubmissionImportExternalPreviewComponent } from './import-external/import-external-preview/submission-import-external-preview.component'; -import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; -import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; -import { ThemedSubmissionImportExternalComponent } from './import-external/themed-submission-import-external.component'; -import { SubmissionSectionAccessesComponent } from './sections/accesses/section-accesses.component'; -import { SectionAccessesService } from './sections/accesses/section-accesses.service'; -import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component'; -import { SubmissionSectionContainerComponent } from './sections/container/section-container.component'; -import { ThemedSubmissionSectionContainerComponent } from './sections/container/themed-section-container.component'; -import { SubmissionSectionFormComponent } from './sections/form/section-form.component'; -import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; -import { SubmissionSectionIdentifiersComponent } from './sections/identifiers/section-identifiers.component'; -import { SubmissionSectionLicenseComponent } from './sections/license/section-license.component'; -import { SectionsDirective } from './sections/sections.directive'; -import { SectionsService } from './sections/sections.service'; -import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; -import { MetadataInformationComponent } from './sections/sherpa-policies/metadata-information/metadata-information.component'; -import { PublicationInformationComponent } from './sections/sherpa-policies/publication-information/publication-information.component'; -import { PublisherPolicyComponent } from './sections/sherpa-policies/publisher-policy/publisher-policy.component'; -import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; -import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; -import { SubmissionSectionUploadFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; -import { SubmissionSectionUploadFileComponent } from './sections/upload/file/section-upload-file.component'; -import { ThemedSubmissionSectionUploadFileComponent } from './sections/upload/file/themed-section-upload-file.component'; -import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; -import { SubmissionSectionUploadComponent } from './sections/upload/section-upload.component'; -import { SectionUploadService } from './sections/upload/section-upload.service'; -import { submissionEffects } from './submission.effects'; -import { - submissionReducers, - SubmissionState, -} from './submission.reducers'; -import { SubmissionSubmitComponent } from './submit/submission-submit.component'; -import { ThemedSubmissionSubmitComponent } from './submit/themed-submission-submit.component'; - -const ENTRY_COMPONENTS = [ - // put only entry components that use custom decorator - SubmissionSectionUploadComponent, - SubmissionSectionFormComponent, - SubmissionSectionLicenseComponent, - SubmissionSectionCcLicensesComponent, - SubmissionSectionAccessesComponent, - SubmissionSectionSherpaPoliciesComponent, -]; - -const DECLARATIONS = [ - ...ENTRY_COMPONENTS, - SectionsDirective, - SubmissionEditComponent, - ThemedSubmissionEditComponent, - SubmissionFormSectionAddComponent, - SubmissionFormCollectionComponent, - SubmissionFormComponent, - SubmissionFormFooterComponent, - SubmissionSubmitComponent, - ThemedSubmissionSubmitComponent, - SubmissionUploadFilesComponent, - SubmissionSectionContainerComponent, - SubmissionSectionUploadAccessConditionsComponent, - SubmissionSectionUploadFileComponent, - SubmissionSectionUploadFileEditComponent, - SubmissionSectionUploadFileViewComponent, - SubmissionSectionIdentifiersComponent, - SubmissionImportExternalComponent, - ThemedSubmissionImportExternalComponent, - SubmissionImportExternalSearchbarComponent, - SubmissionImportExternalPreviewComponent, - SubmissionImportExternalCollectionComponent, - ContentAccordionComponent, - PublisherPolicyComponent, - PublicationInformationComponent, - MetadataInformationComponent, - ThemedSubmissionSectionUploadFileComponent, - ThemedSubmissionSectionContainerComponent, - ThemedSubmissionFormFooterComponent, - ThemedSubmissionFormComponent, -]; - -@NgModule({ - imports: [ - CommonModule, - StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), - EffectsModule.forFeature(submissionEffects), - NgbModalModule, - NgbCollapseModule, - NgbAccordionModule, - ], - declarations: DECLARATIONS, - exports: [ - ...DECLARATIONS, - ], - providers: [ - SectionUploadService, - SectionsService, - SubmissionUploadsConfigDataService, - SubmissionAccessesConfigDataService, - SectionAccessesService, - SectionFormOperationsService, - ], -}) - -/** - * This module handles all components that are necessary for the submission process - */ -export class SubmissionModule { - /** - * NOTE: this method allows to resolve issue with components that using a custom decorator - * which are not loaded during SSR otherwise - */ - static withEntryComponents() { - return { - ngModule: SubmissionModule, - providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), - }; - } -} diff --git a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts index c1deceecfa..350a6204f3 100644 --- a/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/themes/custom/app/submission/form/footer/submission-form-footer.component.ts @@ -1,5 +1,9 @@ +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../../../../../../app/shared/btn-disabled.directive'; +import { BrowserOnlyPipe } from '../../../../../../app/shared/utils/browser-only.pipe'; import { SubmissionFormFooterComponent as BaseComponent } from '../../../../../../app/submission/form/footer/submission-form-footer.component'; @Component({ @@ -9,6 +13,7 @@ import { SubmissionFormFooterComponent as BaseComponent } from '../../../../../. // templateUrl: './submission-form-footer.component.html' templateUrl: '../../../../../../app/submission/form/footer/submission-form-footer.component.html', standalone: true, + imports: [CommonModule, BrowserOnlyPipe, TranslateModule, BtnDisabledDirective], }) export class SubmissionFormFooterComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/form/submission-form.component.ts b/src/themes/custom/app/submission/form/submission-form.component.ts index 1c2cf07d09..d175b7b9c9 100644 --- a/src/themes/custom/app/submission/form/submission-form.component.ts +++ b/src/themes/custom/app/submission/form/submission-form.component.ts @@ -1,6 +1,13 @@ +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; +import { ThemedLoadingComponent } from '../../../../../app/shared/loading/themed-loading.component'; +import { SubmissionFormCollectionComponent } from '../../../../../app/submission/form/collection/submission-form-collection.component'; +import { ThemedSubmissionFormFooterComponent } from '../../../../../app/submission/form/footer/themed-submission-form-footer.component'; +import { SubmissionFormSectionAddComponent } from '../../../../../app/submission/form/section-add/submission-form-section-add.component'; import { SubmissionFormComponent as BaseComponent } from '../../../../../app/submission/form/submission-form.component'; +import { ThemedSubmissionUploadFilesComponent } from '../../../../../app/submission/form/submission-upload-files/themed-submission-upload-files.component'; +import { ThemedSubmissionSectionContainerComponent } from '../../../../../app/submission/sections/container/themed-section-container.component'; @Component({ selector: 'ds-themed-submission-form', @@ -9,6 +16,15 @@ import { SubmissionFormComponent as BaseComponent } from '../../../../../app/sub // templateUrl: './submission-form.component.html' templateUrl: '../../../../../app/submission/form/submission-form.component.html', standalone: true, + imports: [ + CommonModule, + ThemedLoadingComponent, + ThemedSubmissionSectionContainerComponent, + ThemedSubmissionFormFooterComponent, + ThemedSubmissionUploadFilesComponent, + SubmissionFormCollectionComponent, + SubmissionFormSectionAddComponent, + ], }) export class SubmissionFormComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.ts b/src/themes/custom/app/submission/sections/container/section-container.component.ts index f24d679fb9..0dfe51946c 100644 --- a/src/themes/custom/app/submission/sections/container/section-container.component.ts +++ b/src/themes/custom/app/submission/sections/container/section-container.component.ts @@ -1,6 +1,15 @@ +import { + AsyncPipe, + NgClass, + NgComponentOutlet, +} from '@angular/common'; import { Component } from '@angular/core'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { AlertComponent } from '../../../../../../app/shared/alert/alert.component'; import { SubmissionSectionContainerComponent as BaseComponent } from '../../../../../../app/submission/sections/container/section-container.component'; +import { SectionsDirective } from '../../../../../../app/submission/sections/sections.directive'; @Component({ selector: 'ds-themed-submission-upload-section-file', @@ -9,6 +18,15 @@ import { SubmissionSectionContainerComponent as BaseComponent } from '../../../. // templateUrl: './section-container.component.html' templateUrl: '../../../../../../app/submission/sections/container/section-container.component.html', standalone: true, + imports: [ + AlertComponent, + NgbAccordionModule, + NgComponentOutlet, + TranslateModule, + NgClass, + AsyncPipe, + SectionsDirective, + ], }) export class SubmissionSectionContainerComponent extends BaseComponent { From 2fc84783a54afa9fb454ad75606831d4ce1b3746 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Fri, 28 Mar 2025 15:49:37 +0200 Subject: [PATCH 13/54] 127047: Fix submission-form.component.spec.ts --- .../form/submission-form.component.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/app/submission/form/submission-form.component.spec.ts b/src/app/submission/form/submission-form.component.spec.ts index f097a1e7ea..5f0528003a 100644 --- a/src/app/submission/form/submission-form.component.spec.ts +++ b/src/app/submission/form/submission-form.component.spec.ts @@ -10,13 +10,18 @@ import { TestBed, waitForAsync, } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; import { cold, getTestScheduler, } from 'jasmine-marbles'; -import { of as observableOf } from 'rxjs'; +import { + of as observableOf, + of, +} from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; import { AuthService } from '../../core/auth/auth.service'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Item } from '../../core/shared/item.model'; @@ -56,6 +61,8 @@ describe('SubmissionFormComponent Component', () => { let scheduler: TestScheduler; const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub(); + submissionServiceStub.getSubmissionStatus = jasmine.createSpy('getSubmissionStatus') + .and.returnValue(of(true)); const submissionId = mockSubmissionId; const collectionId = mockSubmissionCollectionId; const submissionObjectNew: any = mockSubmissionObjectNew; @@ -64,6 +71,7 @@ describe('SubmissionFormComponent Component', () => { const selfUrl: any = mockSubmissionSelfUrl; const sectionsList: any = mockSectionsList; const sectionsData: any = mockSectionsData; + const store = jasmine.createSpyObj('store', ['dispatch']); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -75,6 +83,8 @@ describe('SubmissionFormComponent Component', () => { { provide: SubmissionService, useValue: submissionServiceStub }, { provide: SectionsService, useValue: { isSectionTypeAvailable: () => observableOf(true) } }, { provide: ThemeService, useValue: getMockThemeService() }, + { provide: Store, useValue: store }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, ChangeDetectorRef, SubmissionFormComponent, ], From 502597aadc68ef6f565c48d0a75c62ab12513bbf Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Fri, 4 Apr 2025 13:20:01 +0300 Subject: [PATCH 14/54] 127047: Fix submission-edit.component.spec.ts --- src/app/submission/edit/submission-edit.component.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/submission/edit/submission-edit.component.spec.ts b/src/app/submission/edit/submission-edit.component.spec.ts index 59f9883f19..3ef6c7045d 100644 --- a/src/app/submission/edit/submission-edit.component.spec.ts +++ b/src/app/submission/edit/submission-edit.component.spec.ts @@ -57,6 +57,8 @@ describe('SubmissionEditComponent Component', () => { const submissionObject: any = mockSubmissionObject; beforeEach(waitForAsync(() => { + // Fix for missing CSS custom property + document.documentElement.style.setProperty('--bs-xl', '1200'); itemDataService = jasmine.createSpyObj('itemDataService', { findByHref: createSuccessfulRemoteDataObject$(submissionObject.item), }); @@ -105,6 +107,10 @@ describe('SubmissionEditComponent Component', () => { }); afterEach(() => { + if (fixture) { + // Ensure Angular cleans up the component properly + fixture.destroy(); + } comp = null; fixture = null; router = null; From 7864909466c0423718b37fa06ede3d86cdbd9ab2 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Tue, 15 Apr 2025 12:03:25 +0300 Subject: [PATCH 15/54] 127047: Fix selector of SubmissionSectionContainerComponent in custom theme --- .../sections/container/section-container.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/custom/app/submission/sections/container/section-container.component.ts b/src/themes/custom/app/submission/sections/container/section-container.component.ts index 0dfe51946c..aa548c0a9c 100644 --- a/src/themes/custom/app/submission/sections/container/section-container.component.ts +++ b/src/themes/custom/app/submission/sections/container/section-container.component.ts @@ -12,7 +12,7 @@ import { SubmissionSectionContainerComponent as BaseComponent } from '../../../. import { SectionsDirective } from '../../../../../../app/submission/sections/sections.directive'; @Component({ - selector: 'ds-themed-submission-upload-section-file', + selector: 'ds-themed-base-submission-section-container', // styleUrls: ['./section-container.component.scss'], styleUrls: ['../../../../../../app/submission/sections/container/section-container.component.scss'], // templateUrl: './section-container.component.html' From d269d668e5436caa2f93f3cb758b3a9dab29fc5a Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Tue, 22 Apr 2025 09:57:27 +0300 Subject: [PATCH 16/54] 127047: Remove default value of collectionModifiable and Update overrideComponent to exclude themed components --- .../form/submission-form.component.spec.ts | 20 +++++-------------- .../form/themed-submission-form.component.ts | 2 +- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/app/submission/form/submission-form.component.spec.ts b/src/app/submission/form/submission-form.component.spec.ts index 5f0528003a..f1136946ee 100644 --- a/src/app/submission/form/submission-form.component.spec.ts +++ b/src/app/submission/form/submission-form.component.spec.ts @@ -10,18 +10,13 @@ import { TestBed, waitForAsync, } from '@angular/core/testing'; -import { Store } from '@ngrx/store'; import { cold, getTestScheduler, } from 'jasmine-marbles'; -import { - of as observableOf, - of, -} from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; import { AuthService } from '../../core/auth/auth.service'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Item } from '../../core/shared/item.model'; @@ -42,12 +37,12 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic import { SubmissionServiceStub } from '../../shared/testing/submission-service.stub'; import { createTestComponent } from '../../shared/testing/utils.test'; import { ThemeService } from '../../shared/theme-support/theme.service'; -import { SubmissionSectionContainerComponent } from '../sections/container/section-container.component'; +import { ThemedSubmissionSectionContainerComponent } from '../sections/container/themed-section-container.component'; import { SectionsService } from '../sections/sections.service'; import { VisibilityType } from '../sections/visibility-type'; import { SubmissionService } from '../submission.service'; import { SubmissionFormCollectionComponent } from './collection/submission-form-collection.component'; -import { SubmissionFormFooterComponent } from './footer/submission-form-footer.component'; +import { ThemedSubmissionFormFooterComponent } from './footer/themed-submission-form-footer.component'; import { SubmissionFormSectionAddComponent } from './section-add/submission-form-section-add.component'; import { SubmissionFormComponent } from './submission-form.component'; import { ThemedSubmissionUploadFilesComponent } from './submission-upload-files/themed-submission-upload-files.component'; @@ -61,8 +56,6 @@ describe('SubmissionFormComponent Component', () => { let scheduler: TestScheduler; const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub(); - submissionServiceStub.getSubmissionStatus = jasmine.createSpy('getSubmissionStatus') - .and.returnValue(of(true)); const submissionId = mockSubmissionId; const collectionId = mockSubmissionCollectionId; const submissionObjectNew: any = mockSubmissionObjectNew; @@ -71,7 +64,6 @@ describe('SubmissionFormComponent Component', () => { const selfUrl: any = mockSubmissionSelfUrl; const sectionsList: any = mockSectionsList; const sectionsData: any = mockSectionsData; - const store = jasmine.createSpyObj('store', ['dispatch']); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -83,8 +75,6 @@ describe('SubmissionFormComponent Component', () => { { provide: SubmissionService, useValue: submissionServiceStub }, { provide: SectionsService, useValue: { isSectionTypeAvailable: () => observableOf(true) } }, { provide: ThemeService, useValue: getMockThemeService() }, - { provide: Store, useValue: store }, - { provide: APP_DATA_SERVICES_MAP, useValue: {} }, ChangeDetectorRef, SubmissionFormComponent, ], @@ -94,8 +84,8 @@ describe('SubmissionFormComponent Component', () => { remove: { imports: [ ThemedLoadingComponent, - SubmissionSectionContainerComponent, - SubmissionFormFooterComponent, + ThemedSubmissionSectionContainerComponent, + ThemedSubmissionFormFooterComponent, ThemedSubmissionUploadFilesComponent, SubmissionFormCollectionComponent, SubmissionFormSectionAddComponent, diff --git a/src/app/submission/form/themed-submission-form.component.ts b/src/app/submission/form/themed-submission-form.component.ts index b7414aa8e9..4a6f8d67cd 100644 --- a/src/app/submission/form/themed-submission-form.component.ts +++ b/src/app/submission/form/themed-submission-form.component.ts @@ -22,7 +22,7 @@ export class ThemedSubmissionFormComponent extends ThemedComponent Date: Tue, 22 Apr 2025 12:58:33 +0200 Subject: [PATCH 17/54] [DURACOM-353] fix orejime callbacks --- .../cookies/browser-orejime.service.spec.ts | 27 +++++++++++++++++ .../shared/cookies/browser-orejime.service.ts | 30 +++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/app/shared/cookies/browser-orejime.service.spec.ts b/src/app/shared/cookies/browser-orejime.service.spec.ts index 126e113227..b0a108e17d 100644 --- a/src/app/shared/cookies/browser-orejime.service.spec.ts +++ b/src/app/shared/cookies/browser-orejime.service.spec.ts @@ -433,4 +433,31 @@ describe('BrowserOrejimeService', () => { expect(service.orejimeConfig.apps).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); }); + + describe('applyUpdateSettingsCallbackToApps', () => { + let user2: EPerson; + let mockApp1, mockApp2; + let updateSettingsSpy; + + beforeEach(() => { + user2 = Object.assign(new EPerson(), { uuid: 'test-user' }); + mockApp1 = { name: 'app1', callback: jasmine.createSpy('originalCallback1') }; + mockApp2 = { name: 'app2', callback: jasmine.createSpy('originalCallback2') }; + service.orejimeConfig.apps = [mockApp1, mockApp2]; + updateSettingsSpy = spyOn(service, 'updateSettingsForUsers'); + }); + + it('calls updateSettingsForUsers in a debounced manner when a callback is triggered', (done) => { + service.applyUpdateSettingsCallbackToApps(user2); + + mockApp1.callback(true); + mockApp2.callback(false); + + setTimeout(() => { + expect(updateSettingsSpy).toHaveBeenCalledTimes(1); + expect(updateSettingsSpy).toHaveBeenCalledWith(user2); + done(); + }, 400); + }); + }); }); diff --git a/src/app/shared/cookies/browser-orejime.service.ts b/src/app/shared/cookies/browser-orejime.service.ts index b2d3b6a4c0..e30576ee20 100644 --- a/src/app/shared/cookies/browser-orejime.service.ts +++ b/src/app/shared/cookies/browser-orejime.service.ts @@ -192,12 +192,39 @@ export class BrowserOrejimeService extends OrejimeService { this.translateConfiguration(); this.orejimeConfig.apps = this.filterConfigApps(appsToHide); - this.lazyOrejime.then(({ init }) => { + + this.applyUpdateSettingsCallbackToApps(user); + + void this.lazyOrejime.then(({ init }) => { this.orejimeInstance = init(this.orejimeConfig); }); }); } + /** + * Applies a debounced callback to update user settings for all apps in the Orejime configuration. + * + * This method modifies the `callback` property of each app in the `orejimeConfig.apps` array. + * It ensures that the `updateSettingsForUsers` method is called in a debounced manner whenever + * a consent change occurs for any app. Additionally, it preserves and invokes the original + * callback for each app if one is defined. + * + * @param {EPerson} user - The authenticated user whose settings are being updated. + */ + applyUpdateSettingsCallbackToApps(user: EPerson) { + const updateSettingsCallback = debounce(() => this.updateSettingsForUsers(user), updateDebounce); + + this.orejimeConfig.apps.forEach((app) => { + const originalCallback = app.callback; + app.callback = (consent: boolean) => { + updateSettingsCallback(); + if (originalCallback) { + originalCallback(consent); + } + }; + }); + } + /** * Return saved preferences stored in the orejime cookie */ @@ -220,7 +247,6 @@ export class BrowserOrejimeService extends OrejimeService { * @param user The authenticated user */ private initializeUser(user: EPerson) { - this.orejimeConfig.callback = debounce((consent, app) => this.updateSettingsForUsers(user), updateDebounce); this.orejimeConfig.cookieName = this.getStorageName(user.uuid); const anonCookie = this.cookieService.get(ANONYMOUS_STORAGE_NAME_OREJIME); From b89a626ffcfc660566fc75ca33c77a1232a149c9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 17 Apr 2025 17:30:59 +0200 Subject: [PATCH 18/54] #4172 Several fixes to RSS component and object lists * RSS button displayed on search result pages * RSS button displayed on Recent Items component * RSS button NOT displayed in other components like top-level community, etc. * RSS component tracks sort option changes --- .../recent-item-list.component.html | 1 + .../recent-item-list.component.ts | 3 +- .../object-collection.component.html | 2 + .../object-collection.component.ts | 9 +- .../object-detail.component.html | 1 + .../object-detail/object-detail.component.ts | 2 + .../object-list/object-list.component.html | 1 + .../object-list/object-list.component.ts | 7 +- .../themed-object-list.component.ts | 3 + .../pagination/pagination.component.html | 4 +- .../shared/pagination/pagination.component.ts | 8 +- src/app/shared/rss-feed/rss.component.ts | 155 +++++++++++------- .../search-results.component.html | 1 + 13 files changed, 134 insertions(+), 63 deletions(-) diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.html b/src/app/home-page/recent-item-list/recent-item-list.component.html index 4d77e5027e..7a7ecdacce 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.html +++ b/src/app/home-page/recent-item-list/recent-item-list.component.html @@ -3,6 +3,7 @@

{{'home.recent-submissions.head' | translate}}

+ @for (item of itemRD?.payload?.page; track item) {
diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.ts b/src/app/home-page/recent-item-list/recent-item-list.component.ts index b336cf7e9b..1855eed909 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.ts +++ b/src/app/home-page/recent-item-list/recent-item-list.component.ts @@ -41,6 +41,7 @@ import { ErrorComponent } from '../../shared/error/error.component'; import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { RSSComponent } from '../../shared/rss-feed/rss.component'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { followLink, @@ -59,7 +60,7 @@ import { VarDirective } from '../../shared/utils/var.directive'; fadeInOut, ], standalone: true, - imports: [VarDirective, NgClass, ListableObjectComponentLoaderComponent, ErrorComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule], + imports: [VarDirective, NgClass, ListableObjectComponentLoaderComponent, ErrorComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule, RSSComponent], }) export class RecentItemListComponent implements OnInit, OnDestroy { itemRD$: Observable>>; 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..c7849adbff 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 = 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..eae56a217d 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -85,6 +85,8 @@ export class ObjectDetailComponent { */ @Input() showThumbnails; + @Input() showRSS = 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..19d3cc9906 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 = 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..65309678bf 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) { + + }
diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 907b427354..7392d8f91e 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 = 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( diff --git a/src/app/shared/rss-feed/rss.component.ts b/src/app/shared/rss-feed/rss.component.ts index 8d2b49f0e1..8c81f7975e 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'; @@ -31,13 +33,14 @@ import { GroupDataService } from '../../core/eperson/group-data.service'; import { PaginationService } from '../../core/pagination/pagination.service'; import { LinkHeadService } from '../../core/services/link-head.service'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { SearchService } from '../../core/shared/search/search.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; 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,30 +54,28 @@ 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, private configurationService: ConfigurationDataService, private searchConfigurationService: SearchConfigurationService, + private searchService: SearchService, private router: Router, private route: ActivatedRoute, 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 +83,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 +137,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 +212,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 +225,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 +243,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" From 3a347d83b5a8cf2e5d424c1fa7f35eafbd5dd9a2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 24 Apr 2025 15:09:19 +0200 Subject: [PATCH 19/54] #4172 Allow SortOptions override to showRSS input If a valid, complete SortOptions is passed to the pagination component showRSS, it will be used instead of the underlying sortOptions, otherwise the pagination context sortOptions will be used. --- .../top-level-community-list.component.html | 1 + .../top-level-community-list.component.ts | 4 +++- .../object-collection.component.ts | 2 +- .../object-detail/object-detail.component.ts | 5 ++++- .../shared/object-list/object-list.component.ts | 2 +- .../object-list/themed-object-list.component.ts | 2 +- .../shared/pagination/pagination.component.html | 2 +- .../shared/pagination/pagination.component.ts | 17 ++++++++++++++++- 8 files changed, 28 insertions(+), 7 deletions(-) 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.ts b/src/app/shared/object-collection/object-collection.component.ts index c7849adbff..d44c0153b2 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -85,7 +85,7 @@ export class ObjectCollectionComponent implements OnInit { /** * Whether to show an RSS syndication button for the current search options */ - @Input() showRSS = false; + @Input() showRSS: SortOptions | boolean = false; /** * Emit custom event for listable object custom actions. diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts index eae56a217d..67037aa6bd 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -85,7 +85,10 @@ export class ObjectDetailComponent { */ @Input() showThumbnails; - @Input() showRSS = false; + /** + * 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.ts b/src/app/shared/object-list/object-list.component.ts index 19d3cc9906..3c0136fec9 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -73,7 +73,7 @@ export class ObjectListComponent { /** * Whether to show an RSS syndication button for the current search options */ - @Input() showRSS = false; + @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 65309678bf..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,7 +59,7 @@ export class ThemedObjectListComponent extends ThemedComponent } @if (showRSS) { - + } diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 7392d8f91e..a10373af1e 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -173,7 +173,7 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit { * 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 = false; + @Input() public showRSS: SortOptions | boolean = false; /** * Current page. @@ -442,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; + } + } From 6f878e5c4a3004c234111128bb3fd05dddb0ca92 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 24 Apr 2025 15:16:32 +0200 Subject: [PATCH 20/54] #4172 Better boolean comparison of showRSS test --- src/app/shared/pagination/pagination.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 6cfa5c456b..03898a1664 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -43,7 +43,7 @@ } - @if (showRSS) { + @if (showRSS !== false) { } From e3c6ad807b3baead4f57a505322d207dac5499d9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 24 Apr 2025 16:18:41 +0200 Subject: [PATCH 21/54] #4172 Remove unneeded SearchService from RSS --- src/app/shared/rss-feed/rss.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/shared/rss-feed/rss.component.ts b/src/app/shared/rss-feed/rss.component.ts index 8c81f7975e..1ed29697bf 100644 --- a/src/app/shared/rss-feed/rss.component.ts +++ b/src/app/shared/rss-feed/rss.component.ts @@ -33,7 +33,6 @@ import { GroupDataService } from '../../core/eperson/group-data.service'; import { PaginationService } from '../../core/pagination/pagination.service'; import { LinkHeadService } from '../../core/services/link-head.service'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { SearchService } from '../../core/shared/search/search.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { hasValue, @@ -69,7 +68,6 @@ export class RSSComponent implements OnInit, OnDestroy, OnChanges { private linkHeadService: LinkHeadService, private configurationService: ConfigurationDataService, private searchConfigurationService: SearchConfigurationService, - private searchService: SearchService, private router: Router, private route: ActivatedRoute, protected paginationService: PaginationService, From f8cfb74555894e8e5982d63bd3007bab19d4cd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chaco=CC=81n?= Date: Thu, 24 Apr 2025 13:29:01 -0600 Subject: [PATCH 22/54] tab navigation improved --- .../breadcrumbs/breadcrumbs.component.html | 2 +- .../browse-by-taxonomy.component.html | 4 ++- .../community-list.component.html | 12 +++++--- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- .../journal-issue.component.html | 2 +- .../journal-volume.component.html | 2 +- .../item-pages/journal/journal.component.html | 2 +- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-grid-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- .../org-unit/org-unit.component.html | 2 +- .../item-pages/person/person.component.html | 2 +- .../item-pages/project/project.component.html | 2 +- src/app/footer/footer.component.html | 16 +++++----- src/app/header/header.component.html | 2 +- .../home-news/home-news.component.html | 2 +- .../recent-item-list.component.html | 2 +- .../alerts/item-alerts.component.html | 2 +- .../collections/collections.component.html | 4 ++- .../metadata-uri-values.component.html | 2 +- .../metadata-values.component.html | 6 ++-- .../publication/publication.component.html | 2 +- .../untyped-item/untyped-item.component.html | 2 +- .../objectnotfound.component.html | 2 +- src/app/page-error/page-error.component.html | 2 +- .../pagenotfound/pagenotfound.component.html | 2 +- .../grant-deny-request-copy.component.html | 2 +- .../search-navbar.component.html | 2 +- .../auth-nav-menu.component.html | 6 ++-- .../comcol-page-browse-by.component.html | 4 ++- .../comcol-page-handle.component.html | 2 +- .../file-download-link.component.html | 4 ++- .../vocabulary-treeview.component.html | 30 +++++++++++++------ .../log-in-external-provider.component.html | 2 +- .../password/log-in-password.component.html | 6 ++-- .../menu-item/link-menu-item.component.html | 1 + .../menu-item/text-menu-item.component.html | 2 +- .../browse-entry-list-element.component.html | 2 +- .../collection-list-element.component.html | 2 +- .../community-list-element.component.html | 2 +- ...-text-metadata-list-element.component.html | 6 ++-- ...-search-result-list-element.component.html | 4 +-- .../pagination/pagination.component.html | 10 +++---- .../results-back-button.component.html | 2 +- src/app/shared/rss-feed/rss.component.html | 2 +- .../search-form/search-form.component.html | 6 ++-- .../search-authority-filter.component.html | 4 +-- .../search-boolean-filter.component.html | 4 +-- .../search-facet-option.component.html | 2 +- .../search-facet-range-option.component.html | 3 +- ...earch-facet-selected-option.component.html | 2 +- .../search-filter.component.html | 2 ++ .../search-hierarchy-filter.component.html | 6 ++-- .../search-range-filter.component.html | 4 +-- .../search-text-filter.component.html | 4 +-- .../search-results.component.html | 2 +- .../date/starts-with-date.component.html | 2 +- .../text/starts-with-text.component.html | 2 +- .../truncatable-part.component.html | 1 + .../dspace/app/header/header.component.html | 2 +- .../home-news/home-news.component.html | 4 +-- 69 files changed, 149 insertions(+), 116 deletions(-) diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html index cbf9f0c886..b16f46f082 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.html +++ b/src/app/breadcrumbs/breadcrumbs.component.html @@ -12,7 +12,7 @@ } - + diff --git a/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html index f533757d68..6b10260e92 100644 --- a/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html +++ b/src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.html @@ -20,6 +20,8 @@ + [queryParamsHandling]="'merge'" + role="link" + tabindex="0"> {{ 'browse.taxonomy.button' | translate }} diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 864b5032d5..9b8eb46703 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -12,7 +12,7 @@
@if ((dataSource.loading$ | async) !== true) { } @@ -34,7 +34,11 @@
@@ -53,40 +53,40 @@

{{ 'footer.link.dspace' | translate}} + href="http://www.dspace.org/" role="link" tabindex="0">{{ 'footer.link.dspace' | translate}} {{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }} {{ 'footer.link.lyrasis' | translate}} + href="https://www.lyrasis.org/" role="link" tabindex="0">{{ 'footer.link.lyrasis' | translate}}

@if (coarLdnEnabled$ | async) {
- + {{ 'footer.link.coar-notify-support' | translate }} diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 67a4a96c23..dc3d7f94b9 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,7 +1,7 @@
- + diff --git a/src/app/home-page/home-news/home-news.component.html b/src/app/home-page/home-news/home-news.component.html index 972c8cc293..9a226ca83f 100644 --- a/src/app/home-page/home-news/home-news.component.html +++ b/src/app/home-page/home-news/home-news.component.html @@ -14,7 +14,7 @@
  • issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI
  • Join an international community of leading institutions using DSpace. + target="_blank" role="link" tabindex="0">leading institutions using DSpace.

    diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.html b/src/app/home-page/recent-item-list/recent-item-list.component.html index 4d77e5027e..1a7fc342ed 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.html +++ b/src/app/home-page/recent-item-list/recent-item-list.component.html @@ -9,7 +9,7 @@
    } - + } @if (itemRD?.hasFailed) { diff --git a/src/app/item-page/alerts/item-alerts.component.html b/src/app/item-page/alerts/item-alerts.component.html index cb69f873bb..0f2205232b 100644 --- a/src/app/item-page/alerts/item-alerts.component.html +++ b/src/app/item-page/alerts/item-alerts.component.html @@ -10,7 +10,7 @@
    {{'item.alerts.withdrawn' | translate}}
    - {{"404.link.home-page" | translate}} + {{"404.link.home-page" | translate}} @if (showReinstateButton$ | async) { {{ 'item.alerts.reinstate-request' | translate}} } diff --git a/src/app/item-page/field-components/collections/collections.component.html b/src/app/item-page/field-components/collections/collections.component.html index f5f42bd272..88d50a1f33 100644 --- a/src/app/item-page/field-components/collections/collections.component.html +++ b/src/app/item-page/field-components/collections/collections.component.html @@ -1,7 +1,7 @@
    @for (collection of (this.collections$ | async); track collection; let last = $last) { - + {{ dsoNameService.getName(collection) }}@if (!last) { } @@ -21,6 +21,8 @@ class="load-more-btn btn btn-sm btn-outline-secondary" role="button" href="javascript:void(0);" + role="button" + tabindex="0" > {{'item.page.collections.load-more' | translate}} diff --git a/src/app/item-page/field-components/metadata-uri-values/metadata-uri-values.component.html b/src/app/item-page/field-components/metadata-uri-values/metadata-uri-values.component.html index 2093318e66..c07e85d5db 100644 --- a/src/app/item-page/field-components/metadata-uri-values/metadata-uri-values.component.html +++ b/src/app/item-page/field-components/metadata-uri-values/metadata-uri-values.component.html @@ -1,6 +1,6 @@ @for (mdValue of mdValues; track mdValue; let last = $last) { - + {{ linktext || mdValue.value }}@if (!last) { } diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.html b/src/app/item-page/field-components/metadata-values/metadata-values.component.html index 015398f041..60fca0a8b7 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.html +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.html @@ -23,14 +23,14 @@ - {{value}} + [queryParams]="getQueryParams(value)" role="link" tabindex="0">{{value}} diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index 31bb741a9f..673e58c785 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -116,7 +116,7 @@ } diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index d941bb6327..77d4168a1c 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -104,7 +104,7 @@ diff --git a/src/app/lookup-by-id/objectnotfound/objectnotfound.component.html b/src/app/lookup-by-id/objectnotfound/objectnotfound.component.html index e1cf58b5b2..e3416f8405 100644 --- a/src/app/lookup-by-id/objectnotfound/objectnotfound.component.html +++ b/src/app/lookup-by-id/objectnotfound/objectnotfound.component.html @@ -3,6 +3,6 @@

    {{missingItem}}


    - {{"404.link.home-page" | translate}} + {{"404.link.home-page" | translate}}

    diff --git a/src/app/page-error/page-error.component.html b/src/app/page-error/page-error.component.html index 9a5f02600a..9a8ffc7e90 100644 --- a/src/app/page-error/page-error.component.html +++ b/src/app/page-error/page-error.component.html @@ -5,6 +5,6 @@

    {{"error-page." + code | translate}}


    - {{ status + ".link.home-page" | translate}} + {{ status + ".link.home-page" | translate}}

    diff --git a/src/app/pagenotfound/pagenotfound.component.html b/src/app/pagenotfound/pagenotfound.component.html index e85316b0ec..8177323a7b 100644 --- a/src/app/pagenotfound/pagenotfound.component.html +++ b/src/app/pagenotfound/pagenotfound.component.html @@ -5,6 +5,6 @@

    {{"404.help" | translate}}


    - {{"404.link.home-page" | translate}} + {{"404.link.home-page" | translate}}

    \ No newline at end of file diff --git a/src/app/request-copy/grant-deny-request-copy/grant-deny-request-copy.component.html b/src/app/request-copy/grant-deny-request-copy/grant-deny-request-copy.component.html index 4a5e91273b..e62eca8941 100644 --- a/src/app/request-copy/grant-deny-request-copy/grant-deny-request-copy.component.html +++ b/src/app/request-copy/grant-deny-request-copy/grant-deny-request-copy.component.html @@ -42,7 +42,7 @@ } diff --git a/src/app/search-navbar/search-navbar.component.html b/src/app/search-navbar/search-navbar.component.html index a13abcdf53..d3148cf37f 100644 --- a/src/app/search-navbar/search-navbar.component.html +++ b/src/app/search-navbar/search-navbar.component.html @@ -7,7 +7,7 @@ [class.display]="searchExpanded ? 'inline-block' : 'none'" [tabIndex]="searchExpanded ? 0 : -1" [attr.data-test]="'header-search-box' | dsBrowserOnly"> - diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html index 4dac66b749..4082718920 100644 --- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html +++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html @@ -8,6 +8,7 @@ @if ((isAuthenticated | async) !== true) { - + {{ 'nav.login' | translate }}(current) } @if ((isAuthenticated | async)) { - + (current) diff --git a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html index c88bb16afc..49641261e0 100644 --- a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -10,7 +10,9 @@ role="tab" [routerLink]="option.routerLink" [queryParams]="option.params" - [class.active]="(currentOption$ | async)?.id === option.id"> + [class.active]="(currentOption$ | async)?.id === option.id" + role="link" + tabindex="0"> {{ option.label | translate }} } diff --git a/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html b/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html index 64f3c3c96b..e5be7732a4 100644 --- a/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html +++ b/src/app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html @@ -3,6 +3,6 @@ @if (title) { {{ title | translate }} } - {{getHandle()}} + {{getHandle()}}

    } diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html index a03836c9f1..bb9144ce99 100644 --- a/src/app/shared/file-download-link/file-download-link.component.html +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -5,7 +5,9 @@ [queryParams]="(bitstreamPath$| async)?.queryParams" [target]="isBlank ? '_blank': '_self'" [ngClass]="cssClasses" - [attr.aria-label]="('file-download-link.download' | translate) + dsoNameService.getName(bitstream)"> + [attr.aria-label]="('file-download-link.download' | translate) + dsoNameService.getName(bitstream)" + role="link" + tabindex="0"> @if ((canDownload$ | async) === false && (canDownloadWithToken$ | async) === false) { diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 1aa293ed77..1a35ed525c 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -9,21 +9,21 @@ [placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
    @if (showAdd && this.vocabularyOptions.closed) { }
    @@ -59,6 +59,8 @@ [(ngModel)]="node.isSelected" [checked]="node.isSelected" (change)="onSelect(node.item)" + role="checkbox" + tabindex="0" > {{node.item.display}} @@ -70,7 +72,9 @@ [ngbTooltip]="node.item?.otherInformation?.note" [openDelay]="500" container="body" - (click)="onSelect(node.item)"> + (click)="onSelect(node.item)" + role="button" + tabindex="0"> {{node.item.display}} } @@ -80,7 +84,11 @@ @@ -95,6 +103,8 @@ [(ngModel)]="node.isSelected" [checked]="node.isSelected" (change)="onSelect(node.item)" + role="checkbox" + tabindex="0" > {{node.item.display}} @@ -106,7 +116,9 @@ [ngbTooltip]="node.item?.otherInformation?.note" [openDelay]="500" container="body" - (click)="onSelect(node.item)"> + (click)="onSelect(node.item)" + role="button" + tabindex="0"> {{node.item.display}} } @@ -114,14 +126,14 @@ diff --git a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.html b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.html index a8d511a3f5..5a079f91a6 100644 --- a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.html +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.html @@ -1,3 +1,3 @@ - diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.html b/src/app/shared/log-in/methods/password/log-in-password.component.html index 52685c361c..5eb2088a81 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.html +++ b/src/app/shared/log-in/methods/password/log-in-password.component.html @@ -28,18 +28,18 @@ } + [dsBtnDisabled]="!form.valid" role="button" tabindex="0"> {{"login.form.submit" | translate}} @if (canShowDivider$ | async) {
    @if (canRegister$ | async) { {{"login.form.new-user" | translate}} + [attr.data-test]="'register' | dsBrowserOnly" role="menuitem" tabindex="0">{{"login.form.new-user" | translate}} } @if (canForgot$ | async) { {{"login.form.forgot-password" | translate}} + [attr.data-test]="'forgot' | dsBrowserOnly" role="menuitem" tabindex="0">{{"login.form.forgot-password" | translate}} }
    } diff --git a/src/app/shared/menu/menu-item/link-menu-item.component.html b/src/app/shared/menu/menu-item/link-menu-item.component.html index 71eeda2e68..f96084e6e1 100644 --- a/src/app/shared/menu/menu-item/link-menu-item.component.html +++ b/src/app/shared/menu/menu-item/link-menu-item.component.html @@ -8,4 +8,5 @@ (keyup.space)="navigate($event)" (keydown.enter)="navigate($event)" href="javascript:void(0);" + tabindex="0" >{{item.text | translate}} diff --git a/src/app/shared/menu/menu-item/text-menu-item.component.html b/src/app/shared/menu/menu-item/text-menu-item.component.html index ba3cf99a49..e2dd334caf 100644 --- a/src/app/shared/menu/menu-item/text-menu-item.component.html +++ b/src/app/shared/menu/menu-item/text-menu-item.component.html @@ -1 +1 @@ -{{item.text | translate}} +{{item.text | translate}} diff --git a/src/app/shared/object-list/browse-entry-list-element/browse-entry-list-element.component.html b/src/app/shared/object-list/browse-entry-list-element/browse-entry-list-element.component.html index e0bae4ec57..524243f23d 100644 --- a/src/app/shared/object-list/browse-entry-list-element/browse-entry-list-element.component.html +++ b/src/app/shared/object-list/browse-entry-list-element/browse-entry-list-element.component.html @@ -1,6 +1,6 @@
    @if (linkType !== linkTypes.None) { - + {{object.value}} } diff --git a/src/app/shared/object-list/collection-list-element/collection-list-element.component.html b/src/app/shared/object-list/collection-list-element/collection-list-element.component.html index 74f77225c1..a49f328ff3 100644 --- a/src/app/shared/object-list/collection-list-element/collection-list-element.component.html +++ b/src/app/shared/object-list/collection-list-element/collection-list-element.component.html @@ -1,6 +1,6 @@
    @if (linkType !== linkTypes.None) { - + {{ dsoNameService.getName(object) }} } diff --git a/src/app/shared/object-list/community-list-element/community-list-element.component.html b/src/app/shared/object-list/community-list-element/community-list-element.component.html index 3c7faf3dab..90cb409819 100644 --- a/src/app/shared/object-list/community-list-element/community-list-element.component.html +++ b/src/app/shared/object-list/community-list-element/community-list-element.component.html @@ -1,6 +1,6 @@
    @if (linkType !== linkTypes.None) { - + {{ dsoNameService.getName(object) }} } diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html index ecaec7ff64..8c550d0276 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html +++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html @@ -7,7 +7,7 @@ } @if ((mdRepresentation.representationType==='plain_text') && isLink()) { + target="_blank" [href]="mdRepresentation.getValue()" role="link" tabindex="0"> {{mdRepresentation.getValue()}} } @@ -18,7 +18,9 @@ + [queryParams]="getQueryParams()" + role="link" + tabindex="0"> {{mdRepresentation.getValue()}} } diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html index db647b6e74..cceb69e1ed 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html @@ -3,7 +3,7 @@
    @if (linkType !== linkTypes.None) { + [routerLink]="[itemPageRoute]" class="dont-break-out" role="button" tabindex="0"> @@ -28,7 +28,7 @@ @if (linkType !== linkTypes.None) { + [innerHTML]="dsoTitle" role="link" tabindex="0"> } @if (linkType === linkTypes.None) { @if (!hideGear) {
    - +
    diff --git a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index b25a602996..1ed8ba0fc6 100644 --- a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -13,13 +13,13 @@
    @if ((isLastPage$ | async) !== true) { + (click)="showMore()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-more" | translate}} } @if ((currentPage | async) > 1) { + (click)="showFirstPageOnly()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-less" | translate}} } diff --git a/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html index c5abc198a6..bd8d2c32fe 100644 --- a/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html @@ -13,13 +13,13 @@
    @if ((isLastPage$ | async) !== true) { + (click)="showMore()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-more" | translate}} } @if ((currentPage | async) > 1) { + (click)="showFirstPageOnly()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-less" | translate}} } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 75387550b2..767de25ae8 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -5,7 +5,7 @@ [queryParams]="addQueryParams$ | async" (click)="announceFilter(); filterService.minimizeAll()">
    } -
    diff --git a/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html index b25a602996..1ed8ba0fc6 100644 --- a/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html @@ -13,13 +13,13 @@
    @if ((isLastPage$ | async) !== true) { + (click)="showMore()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-more" | translate}} } @if ((currentPage | async) > 1) { + (click)="showFirstPageOnly()" href="javascript:void(0);" role="button" tabindex="0"> {{"search.filters.filter.show-less" | translate}} } 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..5e77d130f8 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -54,7 +54,7 @@ {{ 'search.results.no-results' | translate }} + queryParamsHandling="merge" role="link" tabindex="0"> {{"search.results.no-results-link" | translate}}
    diff --git a/src/app/shared/starts-with/date/starts-with-date.component.html b/src/app/shared/starts-with/date/starts-with-date.component.html index cd07a898c0..7c80d357a8 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.html +++ b/src/app/shared/starts-with/date/starts-with-date.component.html @@ -32,7 +32,7 @@
    - +
    diff --git a/src/app/shared/starts-with/text/starts-with-text.component.html b/src/app/shared/starts-with/text/starts-with-text.component.html index 5208427f34..6e70dc122b 100644 --- a/src/app/shared/starts-with/text/starts-with-text.component.html +++ b/src/app/shared/starts-with/text/starts-with-text.component.html @@ -3,7 +3,7 @@
    - +
    {{'browse.startsWith.type_text' | translate}} diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html index fef02ea6d7..a0ec379f54 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html @@ -10,6 +10,7 @@ (keyup.Space)="toggle()" role="button" [attr.aria-expanded]="isExpanded" + tabindex="0" > {{ 'item.truncatable-part.show-' + (isExpanded ? 'less' : 'more') | translate }} diff --git a/src/themes/dspace/app/header/header.component.html b/src/themes/dspace/app/header/header.component.html index 7007954e7d..8aea10a98e 100644 --- a/src/themes/dspace/app/header/header.component.html +++ b/src/themes/dspace/app/header/header.component.html @@ -5,7 +5,7 @@ [attr.role]="(isMobile$ | async) ? 'navigation' : 'presentation'" [attr.aria-label]="(isMobile$ | async) ? ('nav.main.description' | translate) : null" class="h-100 flex-fill d-flex flex-row flex-nowrap justify-content-start align-items-center gapx-3"> - + @if ((isMobile$ | async) !== true) { diff --git a/src/themes/dspace/app/home-page/home-news/home-news.component.html b/src/themes/dspace/app/home-page/home-news/home-news.component.html index 6734942852..7ccf4fe74f 100644 --- a/src/themes/dspace/app/home-page/home-news/home-news.component.html +++ b/src/themes/dspace/app/home-page/home-news/home-news.component.html @@ -4,7 +4,7 @@

    DSpace 9

    -

    This site is running DSpace 9. For more information, see the DSpace 9 Release Notes.

    +

    This site is running DSpace 9. For more information, see the DSpace 9 Release Notes.

    DSpace is the world leading open source repository platform that enables organisations to:

    @@ -20,7 +20,7 @@ handle.net and DataCite DOI -

    Join an international community of leading institutions using DSpace.

    +

    Join an international community of leading institutions using DSpace.

    The test user accounts below have their password set to the name of this software in lowercase.

      From 4e5b344ce8c5224713fde612b3440d0181871361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chaco=CC=81n?= Date: Thu, 24 Apr 2025 17:11:54 -0600 Subject: [PATCH 23/54] hotfix: e2e accessibility tests --- .../comcol-page-browse-by/comcol-page-browse-by.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html index 49641261e0..1758fa542c 100644 --- a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -11,7 +11,7 @@ [routerLink]="option.routerLink" [queryParams]="option.params" [class.active]="(currentOption$ | async)?.id === option.id" - role="link" + role="tab" tabindex="0"> {{ option.label | translate }} From b00f489d0979ed7c2162dc8f7bd4d04468456576 Mon Sep 17 00:00:00 2001 From: Joran De Braekeleer Date: Fri, 25 Apr 2025 10:00:35 +0200 Subject: [PATCH 24/54] 127705: Reduce margins on input fields --- .../ds-dynamic-form-control-container.component.scss | 6 +++++- .../models/list/dynamic-list.component.html | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.scss index 494e405404..c0c0305581 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.scss +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.scss @@ -40,5 +40,9 @@ .col-form-label { padding-top: 0; padding-bottom: 0; - margin-bottom: 0.5rem; + margin-bottom: 0.25rem; +} + +label { + margin-bottom: 0.25rem; } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.html index c4aade2ed7..574b9eb711 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.html @@ -7,7 +7,7 @@ [formGroupName]="model.id" [ngClass]="model.layout.element?.control"> @for (columnItems of items; track columnItems) { -
      +
      @for (item of columnItems; track item) {
      @for (columnItems of items; track columnItems) { -
      +
      @for (item of columnItems; track item) {
      Date: Mon, 28 Apr 2025 02:43:08 +0000 Subject: [PATCH 25/54] Bump webpack from 5.99.6 to 5.99.7 in the webpack group Bumps the webpack group with 1 update: [webpack](https://github.com/webpack/webpack). Updates `webpack` from 5.99.6 to 5.99.7 - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.99.6...v5.99.7) --- updated-dependencies: - dependency-name: webpack dependency-version: 5.99.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: webpack ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 ++++++++++-------- package.json | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689928b1d7..98a2f3963a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,7 @@ "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", "typescript": "~5.4.5", - "webpack": "5.99.6", + "webpack": "5.99.7", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" } @@ -20807,10 +20807,11 @@ } }, "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -22771,14 +22772,15 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.99.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz", - "integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==", + "version": "5.99.7", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", + "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", @@ -22795,7 +22797,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", diff --git a/package.json b/package.json index f9846a6b62..a66f0527eb 100644 --- a/package.json +++ b/package.json @@ -237,7 +237,7 @@ "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", "typescript": "~5.4.5", - "webpack": "5.99.6", + "webpack": "5.99.7", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" } From 3d8951db18f6ac580863fbfa9abfbce489c72ad3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 02:43:22 +0000 Subject: [PATCH 26/54] Bump axios from 1.8.4 to 1.9.0 Bumps [axios](https://github.com/axios/axios) from 1.8.4 to 1.9.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.8.4...v1.9.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689928b1d7..94d019ffaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@terraformer/wkt": "^2.2.1", "altcha": "^0.9.0", "angulartics2": "^12.2.0", - "axios": "^1.8.4", + "axios": "^1.9.0", "bootstrap": "^5.3", "cerialize": "0.1.18", "cli-progress": "^3.12.0", @@ -8830,9 +8830,9 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index f9846a6b62..963b9dd7b0 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "@terraformer/wkt": "^2.2.1", "altcha": "^0.9.0", "angulartics2": "^12.2.0", - "axios": "^1.8.4", + "axios": "^1.9.0", "bootstrap": "^5.3", "cerialize": "0.1.18", "cli-progress": "^3.12.0", From 25f474386b248b91375addf9c1252eeec3ef6911 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 02:49:17 +0000 Subject: [PATCH 27/54] Bump isbot from 5.1.26 to 5.1.27 Bumps [isbot](https://github.com/omrilotan/isbot) from 5.1.26 to 5.1.27. - [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md) - [Commits](https://github.com/omrilotan/isbot/compare/v5.1.26...v5.1.27) --- updated-dependencies: - dependency-name: isbot dependency-version: 5.1.27 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689928b1d7..13fb4b5caa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "filesize": "^10.1.6", "http-proxy-middleware": "^2.0.9", "http-terminator": "^3.2.0", - "isbot": "^5.1.26", + "isbot": "^5.1.27", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", "json5": "^2.2.3", @@ -14731,9 +14731,9 @@ } }, "node_modules/isbot": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.26.tgz", - "integrity": "sha512-3wqJEYSIm59dYQjEF7zJ7T42aqaqxbCyJQda5rKCudJykuAnISptCHR/GSGpOnw8UrvU+mGueNLRJS5HXnbsXQ==", + "version": "5.1.27", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.27.tgz", + "integrity": "sha512-V3W56Hnztt4Wdh3VUlAMbdNicX/tOM38eChW3a2ixP6KEBJAeehxzYzTD59JrU5NCTgBZwRt9lRWr8D7eMZVYQ==", "license": "Unlicense", "engines": { "node": ">=18" diff --git a/package.json b/package.json index f9846a6b62..5641593242 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "filesize": "^10.1.6", "http-proxy-middleware": "^2.0.9", "http-terminator": "^3.2.0", - "isbot": "^5.1.26", + "isbot": "^5.1.27", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", "json5": "^2.2.3", From e4d53eddccf20a5a0fed8329b976fb7122982af6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 02:49:37 +0000 Subject: [PATCH 28/54] Bump @ngtools/webpack from 18.2.18 to 18.2.19 Bumps [@ngtools/webpack](https://github.com/angular/angular-cli) from 18.2.18 to 18.2.19. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19) --- updated-dependencies: - dependency-name: "@ngtools/webpack" dependency-version: 18.2.19 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 25 +++++++++++++++++++++---- package.json | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689928b1d7..ac303be074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,7 @@ "@cypress/schematic": "^1.5.0", "@fortawesome/fontawesome-free": "^6.7.2", "@ngrx/store-devtools": "^18.1.1", - "@ngtools/webpack": "^18.2.18", + "@ngtools/webpack": "^18.2.19", "@types/deep-freeze": "0.1.5", "@types/ejs": "^3.1.2", "@types/express": "^4.17.17", @@ -491,6 +491,23 @@ "node": ">=6.9.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/@ngtools/webpack": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.18.tgz", + "integrity": "sha512-rFTf3zrAMp7KJF8F/sOn0SNits+HhRaNKw4g20Pxk4QG5XZsXChsQIKrrzAnmlCfMb3nQmBnElAhr1rvBmzZWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", + "webpack": "^5.54.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -5960,9 +5977,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.18.tgz", - "integrity": "sha512-rFTf3zrAMp7KJF8F/sOn0SNits+HhRaNKw4g20Pxk4QG5XZsXChsQIKrrzAnmlCfMb3nQmBnElAhr1rvBmzZWQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.19.tgz", + "integrity": "sha512-bExj5JrByKPibsqBbn5Pjn8lo91AUOTsyP2hgKpnOnmSr62rhWSiRwXltgz2MCiZRmuUznpt93WiOLixgYfYvQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index f9846a6b62..c035ee0d89 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "@cypress/schematic": "^1.5.0", "@fortawesome/fontawesome-free": "^6.7.2", "@ngrx/store-devtools": "^18.1.1", - "@ngtools/webpack": "^18.2.18", + "@ngtools/webpack": "^18.2.19", "@types/deep-freeze": "0.1.5", "@types/ejs": "^3.1.2", "@types/express": "^4.17.17", From 33d75cf217e1e2a3b4712d9156aa69737ec3211e Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 28 Apr 2025 14:16:13 +0200 Subject: [PATCH 29/54] [DURACOM-353] fix error in SSR --- src/app/shared/cookies/browser-orejime.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/cookies/browser-orejime.service.ts b/src/app/shared/cookies/browser-orejime.service.ts index e30576ee20..1296f4c3c1 100644 --- a/src/app/shared/cookies/browser-orejime.service.ts +++ b/src/app/shared/cookies/browser-orejime.service.ts @@ -413,7 +413,9 @@ export class BrowserOrejimeService extends OrejimeService { * @param user */ updateSettingsForUsers(user: EPerson) { - this.setSettingsForUser(user, this.cookieService.get(this.getStorageName(user.uuid))); + if (user) { + this.setSettingsForUser(user, this.cookieService.get(this.getStorageName(user.uuid))); + } } /** From 72a050c7aecd9fe8d8800e20a67c278fd21e9914 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 28 Apr 2025 15:31:31 +0200 Subject: [PATCH 30/54] [#4172] Shift enableRSS route data for coll and comm routes app-routes previously set this but it now needs to be set in collection-page-routes and community-page-routes respectively. --- src/app/app-routes.ts | 2 -- src/app/collection-page/collection-page-routes.ts | 1 + src/app/community-page/community-page-routes.ts | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts index 937947c048..0882f5b72c 100644 --- a/src/app/app-routes.ts +++ b/src/app/app-routes.ts @@ -99,14 +99,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 9b729f1dc4..118b999240 100644 --- a/src/app/collection-page/collection-page-routes.ts +++ b/src/app/collection-page/collection-page-routes.ts @@ -99,6 +99,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 258fdda049..edbbfa1516 100644 --- a/src/app/community-page/community-page-routes.ts +++ b/src/app/community-page/community-page-routes.ts @@ -86,6 +86,7 @@ export const ROUTES: Route[] = [ data: { breadcrumbKey: 'community.search', menuRoute: MenuRoute.COMMUNITY_PAGE, + enableRSS: true, }, }, { From 97a4c3c0c0218fd3f21abe5e90adb21e54b2582b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 28 Apr 2025 15:37:55 +0200 Subject: [PATCH 31/54] [#4172] Disable manual ds-rss display in recent items comp This can be added in easily with: ` ` --- .../home-page/recent-item-list/recent-item-list.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.html b/src/app/home-page/recent-item-list/recent-item-list.component.html index 7a7ecdacce..4d77e5027e 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.html +++ b/src/app/home-page/recent-item-list/recent-item-list.component.html @@ -3,7 +3,6 @@

      {{'home.recent-submissions.head' | translate}}

      - @for (item of itemRD?.payload?.page; track item) {
      From cace253d8b61d38c911874c0d775c36d81a1b9c9 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 28 Apr 2025 16:15:10 +0200 Subject: [PATCH 32/54] [DURACOM-359] fix matomo downloading its .js even if it's disabled --- src/app/statistics/matomo.service.spec.ts | 25 +++++++++++++++++++++++ src/app/statistics/matomo.service.ts | 6 +++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/app/statistics/matomo.service.spec.ts b/src/app/statistics/matomo.service.spec.ts index 51ea814231..be14585c2b 100644 --- a/src/app/statistics/matomo.service.spec.ts +++ b/src/app/statistics/matomo.service.spec.ts @@ -23,6 +23,7 @@ import { createSuccessfulRemoteDataObject$, } from '../shared/remote-data.utils'; import { + MATOMO_ENABLED, MATOMO_SITE_ID, MATOMO_TRACKER_URL, MatomoService, @@ -84,6 +85,9 @@ describe('MatomoService', () => { configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue( createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://matomo'] })), ); + configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue( + createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['true'] })), + ); configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue( createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] }))); orejimeService.getSavedPreferences.and.returnValue(of({ matomo: true })); @@ -102,6 +106,9 @@ describe('MatomoService', () => { configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue( createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://example.com'] })), ); + configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue( + createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['true'] })), + ); configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue( createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] }))); orejimeService.getSavedPreferences.and.returnValue(of({ matomo: true })); @@ -123,6 +130,24 @@ describe('MatomoService', () => { expect(matomoInitializer.initializeTracker).not.toHaveBeenCalled(); }); + it('should not initialize tracker if matomo is disabled', () => { + environment.production = true; + environment.matomo = { trackerUrl: '' }; + configService.findByPropertyName.withArgs(MATOMO_TRACKER_URL).and.returnValue( + createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['http://example.com'] })), + ); + configService.findByPropertyName.withArgs(MATOMO_ENABLED).and.returnValue( + createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(),{ values: ['false'] })), + ); + configService.findByPropertyName.withArgs(MATOMO_SITE_ID).and.returnValue( + createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { values: ['1'] }))); + orejimeService.getSavedPreferences.and.returnValue(of({ matomo: true })); + + service.init(); + + expect(matomoInitializer.initializeTracker).not.toHaveBeenCalled(); + }); + describe('with visitorId set', () => { beforeEach(() => { matomoTracker.getVisitorId.and.returnValue(Promise.resolve('12345')); diff --git a/src/app/statistics/matomo.service.ts b/src/app/statistics/matomo.service.ts index 88fc7f9476..a30e82f93a 100644 --- a/src/app/statistics/matomo.service.ts +++ b/src/app/statistics/matomo.service.ts @@ -77,10 +77,10 @@ export class MatomoService { preferences$ .pipe( tap(preferences => this.changeMatomoConsent(preferences?.matomo)), - switchMap(_ => combineLatest([this.getSiteId$(), this.getTrackerUrl$()])), + switchMap(_ => combineLatest([this.isMatomoEnabled$(), this.getSiteId$(), this.getTrackerUrl$()])), ) - .subscribe(([siteId, trackerUrl]) => { - if (siteId && trackerUrl) { + .subscribe(([isMatomoEnabled, siteId, trackerUrl]) => { + if (isMatomoEnabled && siteId && trackerUrl) { this.matomoInitializer.initializeTracker({ siteId, trackerUrl }); } }); From a16f1e824892b7f572c131b79ef938fa0dee9fb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:27:45 +0000 Subject: [PATCH 33/54] Bump sass from 1.86.3 to 1.87.0 in the sass group Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass). Updates `sass` from 1.86.3 to 1.87.0 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.86.3...1.87.0) --- updated-dependencies: - dependency-name: sass dependency-version: 1.87.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: sass ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23d4ef4539..5d8f62b65f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -150,7 +150,7 @@ "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", "rimraf": "^3.0.2", - "sass": "~1.86.3", + "sass": "~1.87.0", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", @@ -20694,9 +20694,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.86.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz", - "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==", + "version": "1.87.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz", + "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 315faf0334..0f8b8346c1 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,7 @@ "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", "rimraf": "^3.0.2", - "sass": "~1.86.3", + "sass": "~1.87.0", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", From 62822ade31266423cc9aeda708d42b638bb6d3a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:30:33 +0000 Subject: [PATCH 34/54] Bump the angular group with 3 updates Bumps the angular group with 3 updates: [@angular/ssr](https://github.com/angular/angular-cli), [@angular-devkit/build-angular](https://github.com/angular/angular-cli) and [@angular/cli](https://github.com/angular/angular-cli). Updates `@angular/ssr` from 18.2.18 to 18.2.19 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19) Updates `@angular-devkit/build-angular` from 18.2.18 to 18.2.19 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19) Updates `@angular/cli` from 18.2.18 to 18.2.19 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19) --- updated-dependencies: - dependency-name: "@angular/ssr" dependency-version: 18.2.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular-devkit/build-angular" dependency-version: 18.2.19 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cli" dependency-version: 18.2.19 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular ... Signed-off-by: dependabot[bot] --- package-lock.json | 141 +++++++++++++++++++++------------------------- package.json | 6 +- 2 files changed, 66 insertions(+), 81 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23d4ef4539..55ec3bf5ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@angular/platform-browser-dynamic": "^18.2.12", "@angular/platform-server": "^18.2.12", "@angular/router": "^18.2.12", - "@angular/ssr": "^18.2.18", + "@angular/ssr": "^18.2.19", "@babel/runtime": "7.27.0", "@kolkov/ngx-gallery": "^2.0.1", "@ng-bootstrap/ng-bootstrap": "^12.0.0", @@ -86,7 +86,7 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "~18.0.0", - "@angular-devkit/build-angular": "^18.2.18", + "@angular-devkit/build-angular": "^18.2.19", "@angular-eslint/builder": "^18.4.1", "@angular-eslint/bundled-angular-compiler": "^18.4.1", "@angular-eslint/eslint-plugin": "^18.4.1", @@ -94,7 +94,7 @@ "@angular-eslint/schematics": "^18.4.1", "@angular-eslint/template-parser": "^18.4.1", "@angular-eslint/utils": "^18.4.1", - "@angular/cli": "^18.2.18", + "@angular/cli": "^18.2.19", "@angular/compiler-cli": "^18.2.12", "@angular/language-service": "^18.2.12", "@cypress/schematic": "^1.5.0", @@ -266,13 +266,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.18", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.18.tgz", - "integrity": "sha512-3OitvTddHp7bSqEGOJlH7Zqv07DdmZHktU2jsekjcbUxmoC1WIpWSYy+Bqyu7HjidJc0xVP7wyE/NPYkrwT5SA==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.18", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -291,17 +291,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.18.tgz", - "integrity": "sha512-yNw5b46BB27YW2lgP9pAt15xtfTS8F1JdWR79bLci0MYL7VPmRBrRtZk+sozRCziit1+oNAVpOUT8QyvDmvAZA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.19.tgz", + "integrity": "sha512-xwY7v+nGE7TXOc4pgY6u57bLzIPSHuecosYr3TiWHAl9iEcKHzkCCFKsLZyunohHmq/i1uA6g3cC6iwp2xNYyg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.18", - "@angular-devkit/build-webpack": "0.1802.18", - "@angular-devkit/core": "18.2.18", - "@angular/build": "18.2.18", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/build-webpack": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular/build": "18.2.19", "@babel/core": "7.26.10", "@babel/generator": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", @@ -312,7 +312,7 @@ "@babel/preset-env": "7.26.9", "@babel/runtime": "7.26.10", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.18", + "@ngtools/webpack": "18.2.19", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", "babel-loader": "9.1.3", @@ -322,7 +322,7 @@ "css-loader": "7.1.2", "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.3", + "http-proxy-middleware": "3.0.5", "https-proxy-agent": "7.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -418,13 +418,13 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.18", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.18.tgz", - "integrity": "sha512-xSiUC2EeELKgs70aceet/iK57y2nk6VobgeeQzGzTtE5HXWX0n5/g9FIOVM1rznv/tj+9VFZpQKCdLqiP7JmCQ==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.19.tgz", + "integrity": "sha512-axz1Sasn+c+GJpJexBL+B3Rh1w3wJrQq8k8gkniodjJ594p4ti2qGk7i9Tj8A4cXx5fGY+EpuZvKfI/9Tr7QwA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.18", + "@angular-devkit/architect": "0.1802.19", "rxjs": "7.8.1" }, "engines": { @@ -491,23 +491,6 @@ "node": ">=6.9.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@ngtools/webpack": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.18.tgz", - "integrity": "sha512-rFTf3zrAMp7KJF8F/sOn0SNits+HhRaNKw4g20Pxk4QG5XZsXChsQIKrrzAnmlCfMb3nQmBnElAhr1rvBmzZWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "typescript": ">=5.4 <5.6", - "webpack": "^5.54.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -711,10 +694,11 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/http-proxy-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", - "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", @@ -757,6 +741,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1100,9 +1085,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.8.tgz", - "integrity": "sha512-/iazaeFPmL8KLA6QB7DFAU4O5j+9y/TA0D019MbLtPuFI56VK4BXFzM6j6QS9oGpScy8IIDH4S2LHv3zg/63Bw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1166,9 +1151,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.18.tgz", - "integrity": "sha512-gncn8QN73mi4in7oAfoWnJglLx5iI8d87796h1LTuAxULSkfzhW3E03NZU764FBiIAWFxuty4PWmrHxMlmbtbw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1203,13 +1188,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.18.tgz", - "integrity": "sha512-i7dy3x32Z8+lmVMKlKHdrSuCya5hUP24BOUn5lXKFAFGcJC0JT30OJrDPqQMA2RzNQiiyacPhxaCdLloEFVh3Q==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.19.tgz", + "integrity": "sha512-P/0KjkzOf2ZShuShx3cBbjLI7XlcS6B/yCRBo1MQfCC4cZfmzPQoUEOSQeYZgy5pnC24f+dKh/+TWc5uYL/Lvg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.18", + "@angular-devkit/core": "18.2.19", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -1341,14 +1326,14 @@ } }, "node_modules/@angular/build": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.18.tgz", - "integrity": "sha512-8PEhrkS1t9xpvBLaLVgi0OWt/0B72ENKKVc6BAKEZ5gg+SD7uf47sJcT1d23r7d/V6FaOJnWim6BrqgFs4rW9A==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.19.tgz", + "integrity": "sha512-dTqR+mhcZWtCRyOafvzHNVpYxMQnt8HHHqNM0kyEMzcztXL2L9zDlKr0H9d+AgGGq/v4qwCh+1gFDxsHByZwMQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.18", + "@angular-devkit/architect": "0.1802.19", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -1814,9 +1799,9 @@ } }, "node_modules/@angular/build/node_modules/@types/node": { - "version": "22.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", - "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "dev": true, "license": "MIT", "optional": true, @@ -1873,9 +1858,9 @@ "peer": true }, "node_modules/@angular/build/node_modules/vite": { - "version": "5.4.17", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz", - "integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==", + "version": "5.4.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", "dev": true, "license": "MIT", "dependencies": { @@ -1988,18 +1973,18 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.18.tgz", - "integrity": "sha512-UwwI03FVvTHbb9kgR9D0HdLajxsVm1jYkcWMfbSMnQGYM1qy1EWj9HvGnfIoQxAEzA8aeQbmsn9+h3w6MQmyCg==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.19.tgz", + "integrity": "sha512-LGVMTc36JQuw8QX8Sclxyei306EQW3KslopXbf7cfqt6D5/fHS+FqqA0O7V8ob/vOGMca+l6hQD27nW5Y3W6pA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.18", - "@angular-devkit/core": "18.2.18", - "@angular-devkit/schematics": "18.2.18", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.18", + "@schematics/angular": "18.2.19", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -2247,9 +2232,9 @@ } }, "node_modules/@angular/ssr": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-18.2.18.tgz", - "integrity": "sha512-WJ56mpiRGp18vcSH4jFHWR6dylBtUk3QOz+RQhuqYFPAfKk2YXEH5BiBXcjNicW5tIxaU8NlHfZVwWHQyEjpiA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-18.2.19.tgz", + "integrity": "sha512-kMNPWZiLGhtrXFwQpDn1laKXxwMpaiXVajpDT7m/yQkyKMH5EbyZASFcyDHK6EsRV2LQsPaXeKzeQof/C1zNcw==", "license": "MIT", "dependencies": { "critters": "0.0.24", @@ -6975,14 +6960,14 @@ "dev": true }, "node_modules/@schematics/angular": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.18.tgz", - "integrity": "sha512-ko5KmtCZz8SqZLKrNeqMauS2LPHBKf7mT01waoOD1uN2gQkSIiLzDEYuXOaIarG6VnxAy5pL6NjkD+EmPsH6eg==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.19.tgz", + "integrity": "sha512-s9aynH/fwB/LT94miVfsaL2C4Qd5BLgjMzWFx7iJ8Hyv7FjOBGYO6eGVovjCt2c6/abG+GQAk4EBOCfg3AUtCA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.18", - "@angular-devkit/schematics": "18.2.18", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "jsonc-parser": "3.3.1" }, "engines": { @@ -11077,9 +11062,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", "engines": { diff --git a/package.json b/package.json index 315faf0334..01ce3d2597 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@angular/platform-browser-dynamic": "^18.2.12", "@angular/platform-server": "^18.2.12", "@angular/router": "^18.2.12", - "@angular/ssr": "^18.2.18", + "@angular/ssr": "^18.2.19", "@babel/runtime": "7.27.0", "@kolkov/ngx-gallery": "^2.0.1", "@ng-bootstrap/ng-bootstrap": "^12.0.0", @@ -168,7 +168,7 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "~18.0.0", - "@angular-devkit/build-angular": "^18.2.18", + "@angular-devkit/build-angular": "^18.2.19", "@angular-eslint/builder": "^18.4.1", "@angular-eslint/bundled-angular-compiler": "^18.4.1", "@angular-eslint/eslint-plugin": "^18.4.1", @@ -176,7 +176,7 @@ "@angular-eslint/schematics": "^18.4.1", "@angular-eslint/template-parser": "^18.4.1", "@angular-eslint/utils": "^18.4.1", - "@angular/cli": "^18.2.18", + "@angular/cli": "^18.2.19", "@angular/compiler-cli": "^18.2.12", "@angular/language-service": "^18.2.12", "@cypress/schematic": "^1.5.0", From 6cd88ec57b44d1c3628ad5faecef71aaecfa8df0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 28 Apr 2025 22:46:36 +0200 Subject: [PATCH 35/54] [#4172] Remove unused RSS import from RecentItemListComponent --- .../home-page/recent-item-list/recent-item-list.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.ts b/src/app/home-page/recent-item-list/recent-item-list.component.ts index 1855eed909..b336cf7e9b 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.ts +++ b/src/app/home-page/recent-item-list/recent-item-list.component.ts @@ -41,7 +41,6 @@ import { ErrorComponent } from '../../shared/error/error.component'; import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { RSSComponent } from '../../shared/rss-feed/rss.component'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { followLink, @@ -60,7 +59,7 @@ import { VarDirective } from '../../shared/utils/var.directive'; fadeInOut, ], standalone: true, - imports: [VarDirective, NgClass, ListableObjectComponentLoaderComponent, ErrorComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule, RSSComponent], + imports: [VarDirective, NgClass, ListableObjectComponentLoaderComponent, ErrorComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule], }) export class RecentItemListComponent implements OnInit, OnDestroy { itemRD$: Observable>>; From 3c1d51480730992c563865b0963510f763abd8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chaco=CC=81n?= Date: Mon, 28 Apr 2025 23:42:38 -0600 Subject: [PATCH 36/54] fixed flash and translate problems on statistics --- .../statistics/models/usage-report.model.ts | 2 +- .../statistics-table.component.html | 4 +- .../statistics-table.component.spec.ts | 4 +- .../statistics-table.component.ts | 40 +------------------ 4 files changed, 7 insertions(+), 43 deletions(-) diff --git a/src/app/core/statistics/models/usage-report.model.ts b/src/app/core/statistics/models/usage-report.model.ts index a4924f3a3d..ea8262065b 100644 --- a/src/app/core/statistics/models/usage-report.model.ts +++ b/src/app/core/statistics/models/usage-report.model.ts @@ -32,7 +32,7 @@ export class UsageReport extends HALResource { id: string; @autoserializeAs('report-type') - reportType: string; + reportType: string; @autoserialize points: Point[]; diff --git a/src/app/statistics-page/statistics-table/statistics-table.component.html b/src/app/statistics-page/statistics-table/statistics-table.component.html index 4a0f87076c..0cc4f5bfbc 100644 --- a/src/app/statistics-page/statistics-table/statistics-table.component.html +++ b/src/app/statistics-page/statistics-table/statistics-table.component.html @@ -11,7 +11,7 @@ @for (header of headers; track header) { - {{ header }} + {{ 'statistics.table.header.' + header | translate }} } @@ -19,7 +19,7 @@ - {{ getLabel(point) | async }} + {{ point.label }} @for (header of headers; track header) { { expect(de.query(By.css('table'))).toBeTruthy(); expect(de.query(By.css('th.views-header')).nativeElement.innerText) - .toEqual('views'); + .toEqual('statistics.table.header.views'); expect(de.query(By.css('th.downloads-header')).nativeElement.innerText) - .toEqual('downloads'); + .toEqual('statistics.table.header.downloads'); expect(de.query(By.css('td.item_1-views-data')).nativeElement.innerText) .toEqual('7'); diff --git a/src/app/statistics-page/statistics-table/statistics-table.component.ts b/src/app/statistics-page/statistics-table/statistics-table.component.ts index 9f59e33fa8..d3cf80330e 100644 --- a/src/app/statistics-page/statistics-table/statistics-table.component.ts +++ b/src/app/statistics-page/statistics-table/statistics-table.component.ts @@ -4,27 +4,11 @@ import { Input, OnInit, } from '@angular/core'; -import { - TranslateModule, - TranslateService, -} from '@ngx-translate/core'; -import { - Observable, - of, -} from 'rxjs'; -import { map } from 'rxjs/operators'; +import { TranslateModule } from '@ngx-translate/core'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; -import { - getFinishedRemoteData, - getRemoteDataPayload, -} from '../../core/shared/operators'; -import { - Point, - UsageReport, -} from '../../core/statistics/models/usage-report.model'; -import { isEmpty } from '../../shared/empty.util'; +import { UsageReport } from '../../core/statistics/models/usage-report.model'; /** * Component representing a statistics table for a given usage report. @@ -57,7 +41,6 @@ export class StatisticsTableComponent implements OnInit { constructor( protected dsoService: DSpaceObjectDataService, protected nameService: DSONameService, - private translateService: TranslateService, ) { } @@ -68,23 +51,4 @@ export class StatisticsTableComponent implements OnInit { this.headers = Object.keys(this.report.points[0].values); } } - - /** - * Get the row label to display for a statistics point. - * @param point the statistics point to get the label for - */ - getLabel(point: Point): Observable { - switch (this.report.reportType) { - case 'TotalVisits': - return this.dsoService.findById(point.id).pipe( - getFinishedRemoteData(), - getRemoteDataPayload(), - map((item) => !isEmpty(item) ? this.nameService.getName(item) : this.translateService.instant('statistics.table.no-name')), - ); - case 'TopCities': - case 'topCountries': - default: - return of(point.label); - } - } } From 6232d4e9cf89b4fe42b7a975d6ac7d2050908fd4 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 29 Apr 2025 12:20:25 +0200 Subject: [PATCH 37/54] 119612: fix spec test --- .../search-export-csv/search-export-csv.component.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts index 9abdd8d366..f067263712 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts @@ -9,6 +9,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { getProcessDetailRoute } from '../../../process-page/process-page-routing.paths'; @@ -31,6 +32,7 @@ describe('SearchExportCsvComponent', () => { let authorizationDataService: AuthorizationDataService; let notificationsService; let router; + let configurationDataService: jasmine.SpyObj; const process = Object.assign(new Process(), { processId: 5, scriptName: 'metadata-export-search' }); @@ -45,6 +47,10 @@ describe('SearchExportCsvComponent', () => { ], }); + configurationDataService = jasmine.createSpyObj('ConfigurationDataService', { + findByPropertyName: observableOf({ payload: { value: '500' } }), + }); + function initBeforeEachAsync() { scriptDataService = jasmine.createSpyObj('scriptDataService', { scriptWithNameExistsAndCanExecute: observableOf(true), @@ -64,6 +70,7 @@ describe('SearchExportCsvComponent', () => { { provide: AuthorizationDataService, useValue: authorizationDataService }, { provide: NotificationsService, useValue: notificationsService }, { provide: Router, useValue: router }, + { provide: ConfigurationDataService, useValue: configurationDataService }, ], }).compileComponents(); } From ad7aa36f88542dd3736c62388d7fc9921bd8c72c Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 29 Apr 2025 15:48:30 +0200 Subject: [PATCH 38/54] [DURACOM-226] fix submission footer wrapping on medium screens --- .../form/footer/submission-form-footer.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/submission/form/footer/submission-form-footer.component.html b/src/app/submission/form/footer/submission-form-footer.component.html index 1001d4c619..671888e824 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.html +++ b/src/app/submission/form/footer/submission-form-footer.component.html @@ -1,6 +1,6 @@ @if (!!submissionId) { -
      -
      +
      +
      @if ((showDepositAndDiscard | async)) {
      -
      +
      @if ((hasUnsavedModification | async) !== true && (processingSaveStatus | async) !== true && (processingDepositStatus | async) !== true) { {{'submission.general.info.saved' | translate}} From 00143d29e7b2712b1b345c3dec614cd907bf46ea Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 29 Apr 2025 12:26:48 +0200 Subject: [PATCH 39/54] [DURACOM-354] fix missing input fields for integer script parameters --- .../integer-value-input.component.html | 11 +++ .../integer-value-input.component.scss | 5 ++ .../integer-value-input.component.spec.ts | 81 +++++++++++++++++++ .../integer-value-input.component.ts | 49 +++++++++++ .../parameter-value-input.component.html | 3 + .../parameter-value-input.component.ts | 3 +- .../scripts/script-parameter-type.model.ts | 1 + src/assets/i18n/ar.json5 | 5 +- src/assets/i18n/bn.json5 | 5 +- src/assets/i18n/ca.json5 | 5 +- src/assets/i18n/cs.json5 | 3 + src/assets/i18n/de.json5 | 5 +- src/assets/i18n/el.json5 | 5 +- src/assets/i18n/en.json5 | 2 + src/assets/i18n/es.json5 | 6 +- src/assets/i18n/fi.json5 | 5 +- src/assets/i18n/fr.json5 | 5 +- src/assets/i18n/gd.json5 | 5 +- src/assets/i18n/hi.json5 | 5 +- src/assets/i18n/hu.json5 | 5 +- src/assets/i18n/it.json5 | 5 +- src/assets/i18n/ja.json5 | 6 +- src/assets/i18n/kk.json5 | 5 +- src/assets/i18n/lv.json5 | 6 +- src/assets/i18n/nl.json5 | 6 +- src/assets/i18n/pl.json5 | 3 + src/assets/i18n/pt-BR.json5 | 5 +- src/assets/i18n/pt-PT.json5 | 5 +- src/assets/i18n/sr-cyr.json5 | 5 +- src/assets/i18n/sr-lat.json5 | 5 +- src/assets/i18n/sv.json5 | 6 +- src/assets/i18n/sw.json5 | 6 +- src/assets/i18n/tr.json5 | 5 +- src/assets/i18n/uk.json5 | 3 + src/assets/i18n/vi.json5 | 5 +- 35 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.html create mode 100644 src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.scss create mode 100644 src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.spec.ts create mode 100644 src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.ts diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.html b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.html new file mode 100644 index 0000000000..f5861500f7 --- /dev/null +++ b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.html @@ -0,0 +1,11 @@ + +@if (integer.invalid && (integer.dirty || integer.touched)) { +
      + @if (integer.errors.required) { +
      + {{'process.new.parameter.integer.required' | translate}} +
      + } +
      +} diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.scss b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.scss new file mode 100644 index 0000000000..8c6325f95a --- /dev/null +++ b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + gap: calc(var(--bs-spacer) / 2); +} diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.spec.ts b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.spec.ts new file mode 100644 index 0000000000..4c1fd4cd7a --- /dev/null +++ b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.spec.ts @@ -0,0 +1,81 @@ +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, + waitForAsync, +} from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; + +import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; +import { IntegerValueInputComponent } from './integer-value-input.component'; + +describe('IntegerValueInputComponent', () => { + let component: IntegerValueInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + IntegerValueInputComponent, + ], + providers: [], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IntegerValueInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should not show a validation error if the input field was left untouched but left empty', () => { + const validationError = fixture.debugElement.query(By.css('.validation-error')); + expect(validationError).toBeFalsy(); + }); + + it('should show a validation error if the input field was touched but left empty', fakeAsync(() => { + component.value = undefined; + fixture.detectChanges(); + tick(); + + const input = fixture.debugElement.query(By.css('input')); + input.triggerEventHandler('blur', null); + + fixture.detectChanges(); + + const validationError = fixture.debugElement.query(By.css('.validation-error')); + expect(validationError).toBeTruthy(); + })); + + it('should not show a validation error if the input field was touched but not left empty', fakeAsync(() => { + component.value = 1; + fixture.detectChanges(); + tick(); + + const input = fixture.debugElement.query(By.css('input')); + input.triggerEventHandler('blur', null); + + fixture.detectChanges(); + + const validationError = fixture.debugElement.query(By.css('.validation-error')); + expect(validationError).toBeFalsy(); + })); +}); diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.ts b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.ts new file mode 100644 index 0000000000..0ae56c7062 --- /dev/null +++ b/src/app/process-page/form/process-parameters/parameter-value-input/number-value-input/integer-value-input.component.ts @@ -0,0 +1,49 @@ +import { + Component, + Input, + OnInit, + Optional, +} from '@angular/core'; +import { + ControlContainer, + FormsModule, + NgForm, +} from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { controlContainerFactory } from '../../../process-form-factory'; +import { ValueInputComponent } from '../value-input.component'; + +/** + * Represents the user-inputted value of an integer parameter + */ +@Component({ + selector: 'ds-integer-value-input', + templateUrl: './integer-value-input.component.html', + styleUrls: ['./integer-value-input.component.scss'], + viewProviders: [{ provide: ControlContainer, + useFactory: controlContainerFactory, + deps: [[new Optional(), NgForm]] }], + standalone: true, + imports: [FormsModule, TranslateModule], +}) +export class IntegerValueInputComponent extends ValueInputComponent implements OnInit { + /** + * The current value of the integer + */ + value: number; + + /** + * Initial value of the field + */ + @Input() initialValue; + + ngOnInit(): void { + this.value = this.initialValue; + } + + setValue(value) { + this.value = value; + this.updateValue.emit(value); + } +} diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.html b/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.html index aa2425562e..ceb3e287ec 100644 --- a/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.html +++ b/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.html @@ -3,6 +3,9 @@ @case (parameterTypes.STRING) { } + @case (parameterTypes.INTEGER) { + + } @case (parameterTypes.OUTPUT) { } diff --git a/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.ts b/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.ts index 45d069c9ba..32c8355b9a 100644 --- a/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.ts +++ b/src/app/process-page/form/process-parameters/parameter-value-input/parameter-value-input.component.ts @@ -17,6 +17,7 @@ import { controlContainerFactory } from '../../process-form-factory'; import { BooleanValueInputComponent } from './boolean-value-input/boolean-value-input.component'; import { DateValueInputComponent } from './date-value-input/date-value-input.component'; import { FileValueInputComponent } from './file-value-input/file-value-input.component'; +import { IntegerValueInputComponent } from './number-value-input/integer-value-input.component'; import { StringValueInputComponent } from './string-value-input/string-value-input.component'; /** @@ -30,7 +31,7 @@ import { StringValueInputComponent } from './string-value-input/string-value-inp useFactory: controlContainerFactory, deps: [[new Optional(), NgForm]] }], standalone: true, - imports: [StringValueInputComponent, DateValueInputComponent, FileValueInputComponent, BooleanValueInputComponent], + imports: [StringValueInputComponent, DateValueInputComponent, FileValueInputComponent, BooleanValueInputComponent, IntegerValueInputComponent], }) export class ParameterValueInputComponent { @Input() index: number; diff --git a/src/app/process-page/scripts/script-parameter-type.model.ts b/src/app/process-page/scripts/script-parameter-type.model.ts index 2b9fd358bf..a84bf50fad 100644 --- a/src/app/process-page/scripts/script-parameter-type.model.ts +++ b/src/app/process-page/scripts/script-parameter-type.model.ts @@ -3,6 +3,7 @@ */ export enum ScriptParameterType { STRING = 'String', + INTEGER = 'Integer', DATE = 'date', BOOLEAN = 'boolean', FILE = 'InputStream', diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5 index ed4d63273a..707cca2b93 100644 --- a/src/assets/i18n/ar.json5 +++ b/src/assets/i18n/ar.json5 @@ -6045,6 +6045,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "يرجى تحديد ملف", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "قيمة المتغير مطلوبة", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "قيمة المتغير مطلوبة", @@ -11456,4 +11459,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index 8a16413aa2..2301a16a17 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -6525,6 +6525,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "দয়াকরে একটি ফাইল নির্বাচন করুন", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "পরামিতির মান প্রয়োজন", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "পরামিতির মান প্রয়োজন", @@ -12379,4 +12382,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5 index 43d2c78098..4feb4591d6 100644 --- a/src/assets/i18n/ca.json5 +++ b/src/assets/i18n/ca.json5 @@ -5850,6 +5850,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Si us plau seleccioneu un fitxer", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Es requereix el valor del paràmetre", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Es requereix el valor del paràmetre", @@ -10771,4 +10774,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index f5a71fe2b4..9d0b1d6e5b 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -6223,6 +6223,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Vyberte prosím soubor", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Hodnota parametru je povinná", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Hodnota parametru je povinná", diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index e21f994390..e2647feeaa 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5861,6 +5861,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Bitte wählen Sie eine Datei aus", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Parameterwert ist erforderlich", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Parameterwert ist erforderlich", @@ -10792,4 +10795,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/el.json5 b/src/assets/i18n/el.json5 index 0bd1c119c5..02df140d61 100644 --- a/src/assets/i18n/el.json5 +++ b/src/assets/i18n/el.json5 @@ -6444,6 +6444,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Επιλέξτε ένα αρχείο", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Απαιτείται τιμή παραμέτρου", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Απαιτείται τιμή παραμέτρου", @@ -12078,4 +12081,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 56fd637ff3..98f7ffbeb4 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3858,6 +3858,8 @@ "process.new.parameter.file.required": "Please select a file", + "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.type.value": "value", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 53f81bc238..3e60192ed5 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -5848,6 +5848,10 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Por favor seleccione un archivo", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Se requiere el valor del parámetro", + + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Se requiere el valor del parámetro", @@ -10768,4 +10772,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index d86d7dbb6d..31a32208bf 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -6223,6 +6223,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Valitse tiedosto, ole hyvä", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Parametrin arvo on pakollinen tieto", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Parametrin arvo on pakollinen tieto", @@ -11699,4 +11702,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 206d4e978f..d1cb4c2c14 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6281,6 +6281,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Veuillez sélectionner un fichier", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Veuillez spécifier un paramètre", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Veuillez spécifier un paramètre", @@ -11414,4 +11417,4 @@ "embargo.listelement.badge": "Embargo jusqu'à {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index a004acf557..07806e87cc 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -6588,6 +6588,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Tagh faidhle", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Feum air luach parameatair", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Feum air luach parameatair", @@ -12471,4 +12474,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/hi.json5 b/src/assets/i18n/hi.json5 index 078c79350a..0289f7ea28 100644 --- a/src/assets/i18n/hi.json5 +++ b/src/assets/i18n/hi.json5 @@ -6440,6 +6440,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "कृपया एक फ़ाइल चुनें", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "पैरामीटर मान आवश्यक है", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "पैरामीटर मान आवश्यक है", @@ -12073,4 +12076,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index 6ae4e10e55..ae7d9cd08a 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -6667,6 +6667,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Kérem válasszon egy fájlt", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Szüséges egy paraméter érték", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Szüséges egy paraméter érték", @@ -12624,4 +12627,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 9adc0ef9a4..7fd6c74deb 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -6256,6 +6256,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Seleziona un file", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Il valore del parametro è obbligatorio", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Il valore del parametro è obbligatorio", @@ -11749,4 +11752,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5 index e2ecc9b3c1..4ef4440a8e 100644 --- a/src/assets/i18n/ja.json5 +++ b/src/assets/i18n/ja.json5 @@ -7714,6 +7714,10 @@ // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", + // "process.new.parameter.integer.required": "Parameter value is required", + // TODO New key - Add a translation + "process.new.parameter.integer.required": "Parameter value is required", + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", @@ -14194,4 +14198,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index 16668aa04b..7ce1327f59 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -6444,6 +6444,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Файлды таңдаңыз", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Параметр мәні қажет", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Параметр мәні қажет", @@ -12098,4 +12101,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index fa72f49452..016bec0edc 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -6958,6 +6958,10 @@ // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", + // "process.new.parameter.integer.required": "Parameter value is required", + // TODO New key - Add a translation + "process.new.parameter.integer.required": "Parameter value is required", + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", @@ -13161,4 +13165,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5 index aef66f9802..5bd188953f 100644 --- a/src/assets/i18n/nl.json5 +++ b/src/assets/i18n/nl.json5 @@ -7200,6 +7200,10 @@ // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", + // "process.new.parameter.integer.required": "Parameter value is required", + // TODO New key - Add a translation + "process.new.parameter.integer.required": "Parameter value is required", + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", @@ -13500,4 +13504,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/pl.json5 b/src/assets/i18n/pl.json5 index 65d9c6421e..2ed993c52b 100644 --- a/src/assets/i18n/pl.json5 +++ b/src/assets/i18n/pl.json5 @@ -5786,6 +5786,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Proszę wybrać plik", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Wartość parametru jest wymagana", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Wartość parametru jest wymagana", diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index d31ca0c542..82468941fc 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -5869,6 +5869,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Por favor, selecione um arquivo", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Valor do parâmetro é obrigatório", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Valor do parâmetro é obrigatório", @@ -10812,4 +10815,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index d88efc40bc..e3cf349b19 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -5904,6 +5904,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Selecione um ficheiro", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "É necessário um valor no parâmetro", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "É necessário um valor no parâmetro", @@ -11130,4 +11133,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/sr-cyr.json5 b/src/assets/i18n/sr-cyr.json5 index 6f5c53ed3a..05e279b977 100644 --- a/src/assets/i18n/sr-cyr.json5 +++ b/src/assets/i18n/sr-cyr.json5 @@ -6255,6 +6255,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Молимо изаберите фајл", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Вредност параметра је обавезна", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Вредност параметра је обавезна", @@ -11719,4 +11722,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/sr-lat.json5 b/src/assets/i18n/sr-lat.json5 index 021053e524..fe661b5174 100644 --- a/src/assets/i18n/sr-lat.json5 +++ b/src/assets/i18n/sr-lat.json5 @@ -6253,6 +6253,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Molimo izaberite fajl", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Vrednost parametra je obavezna", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Vrednost parametra je obavezna", @@ -11716,4 +11719,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index 666071fd53..d5d8e23b23 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -6596,6 +6596,10 @@ // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", + // "process.new.parameter.integer.required": "Parameter value is required", + // TODO New key - Add a translation + "process.new.parameter.integer.required": "Parameter value is required", + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", @@ -12527,4 +12531,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5 index e2ecc9b3c1..4ef4440a8e 100644 --- a/src/assets/i18n/sw.json5 +++ b/src/assets/i18n/sw.json5 @@ -7714,6 +7714,10 @@ // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", + // "process.new.parameter.integer.required": "Parameter value is required", + // TODO New key - Add a translation + "process.new.parameter.integer.required": "Parameter value is required", + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", @@ -14194,4 +14198,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index bdbc40cf9a..1fd622ea89 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -6740,6 +6740,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Lütfen dosya seçiniz.", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Parametre değeri gerekli", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Parametre değeri gerekli", @@ -12721,4 +12724,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5 index 1a1eddf2c9..ae976e2bda 100644 --- a/src/assets/i18n/uk.json5 +++ b/src/assets/i18n/uk.json5 @@ -6768,6 +6768,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Виберіть файл", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Цей параметр є обв'язковим", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Цей параметр є обв'язковим", diff --git a/src/assets/i18n/vi.json5 b/src/assets/i18n/vi.json5 index 3813297da8..83f7f2ad18 100644 --- a/src/assets/i18n/vi.json5 +++ b/src/assets/i18n/vi.json5 @@ -6344,6 +6344,9 @@ // "process.new.parameter.file.required": "Please select a file", "process.new.parameter.file.required": "Vui lòng chọn một tệp tin", + // "process.new.parameter.integer.required": "Parameter value is required", + "process.new.parameter.integer.required": "Giá trị tham số là bắt buộc", + // "process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Giá trị tham số là bắt buộc", @@ -11942,4 +11945,4 @@ "embargo.listelement.badge": "Embargo until {{ date }}", -} \ No newline at end of file +} From 4c26359e293c995c4ec2cce78fd1a7e126dbcab0 Mon Sep 17 00:00:00 2001 From: guillermo2519 Date: Tue, 29 Apr 2025 21:24:59 -0600 Subject: [PATCH 40/54] Fixed Missing Tags in Import Popup from External Sources - 4220 --- src/assets/i18n/en.json5 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 03c960ac1f..2709a1e99c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1,5 +1,4 @@ { - "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", "401.link.home-page": "Take me to the home page", @@ -640,7 +639,6 @@ "admin.reports.items.title": "Title", - "admin.reports.commons.filters": "Filters", "admin.reports.commons.additional-data": "Additional data to return", @@ -661,7 +659,6 @@ "admin.reports.commons.filters.matches_all": "Matches all specified filters", - "admin.reports.commons.filters.property": "Item Property Filters", "admin.reports.commons.filters.property.is_item": "Is Item - always true", @@ -2910,6 +2907,14 @@ "item.preview.oaire.citation.endPage": "Citation end page", + "item.preview.dc.relation.hasversion": "Has version", + + "item.preview.dc.relation.ispartofseries": "Is part of series", + + "item.preview.dc.rights": "Rights", + + "item.preview.dc.identifier.other": "Other Identifier", + "item.preview.dc.relation.issn": "ISSN", "item.preview.dc.identifier.isbn": "ISBN", @@ -5685,6 +5690,8 @@ "subscriptions.tooltip": "Subscribe", + "subscriptions.unsubscribe": "Unsubscribe", + "subscriptions.modal.title": "Subscriptions", "subscriptions.modal.type-frequency": "Type and frequency", @@ -5751,7 +5758,7 @@ "thumbnail.person.placeholder": "No Profile Picture Available", - "title": "DSpace", + title: "DSpace", "vocabulary-treeview.header": "Hierarchical tree view", From 0aea4a7666a3f70d95d4bc7fd56798c401c57494 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Wed, 30 Apr 2025 10:22:01 +0200 Subject: [PATCH 41/54] 127047: Remove unnecessary CSS variable from spec setup --- src/app/submission/edit/submission-edit.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/submission/edit/submission-edit.component.spec.ts b/src/app/submission/edit/submission-edit.component.spec.ts index 3ef6c7045d..29d872f542 100644 --- a/src/app/submission/edit/submission-edit.component.spec.ts +++ b/src/app/submission/edit/submission-edit.component.spec.ts @@ -57,8 +57,6 @@ describe('SubmissionEditComponent Component', () => { const submissionObject: any = mockSubmissionObject; beforeEach(waitForAsync(() => { - // Fix for missing CSS custom property - document.documentElement.style.setProperty('--bs-xl', '1200'); itemDataService = jasmine.createSpyObj('itemDataService', { findByHref: createSuccessfulRemoteDataObject$(submissionObject.item), }); From d3b48f4ea4290d5206ee881834e78cc06c3df144 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 30 Apr 2025 13:42:51 +0200 Subject: [PATCH 42/54] 130484: Only add bundles when they are missing from the subject --- .../item-bitstreams/item-bitstreams.component.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts index ea56a4274a..6fb2ae8c63 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts @@ -244,10 +244,24 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme ), map((bundlePage: PaginatedList) => bundlePage.page), ).subscribe((bundles: Bundle[]) => { - this.bundlesSubject.next([...this.bundlesSubject.getValue(), ...bundles]); + this.updateBundlesSubject(bundles); }); } + updateBundlesSubject(newBundles: Bundle[]) { + const currentBundles = this.bundlesSubject.getValue(); + const bundlesToAdd: Bundle[] = []; + + // Only add bundles to the bundle subject if they are not present yet + newBundles.forEach(newBundle => { + if (!currentBundles.some(currentBundle => currentBundle.id === newBundle.id)) { + bundlesToAdd.push(newBundle); + } + }); + + this.bundlesSubject.next([...currentBundles, ...bundlesToAdd]); + } + /** * Submit the current changes From 41afcf9cf653156c648815ee9bf77a986340b3d7 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 30 Apr 2025 14:08:37 +0200 Subject: [PATCH 43/54] 130484: Correctly update the 'showLoadMoreLink$' observable --- .../item-bitstreams.component.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts index 6fb2ae8c63..40a75a59a5 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts @@ -30,7 +30,6 @@ import { map, switchMap, take, - tap, } from 'rxjs/operators'; import { AlertComponent } from 'src/app/shared/alert/alert.component'; import { AlertType } from 'src/app/shared/alert/alert-type'; @@ -239,27 +238,30 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: this.bundlesOptions })).pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), - tap((bundlesPL: PaginatedList) => - this.showLoadMoreLink$.next(bundlesPL.pageInfo.currentPage < bundlesPL.pageInfo.totalPages), - ), - map((bundlePage: PaginatedList) => bundlePage.page), - ).subscribe((bundles: Bundle[]) => { - this.updateBundlesSubject(bundles); + ).subscribe((bundles: PaginatedList) => { + this.updateBundles(bundles); }); } - updateBundlesSubject(newBundles: Bundle[]) { + /** + * Update the subject containing the bundles with the provided bundles. + * Also updates the showLoadMoreLink observable so it does not show up when it is no longer necessary. + */ + updateBundles(newBundlesPL: PaginatedList) { const currentBundles = this.bundlesSubject.getValue(); const bundlesToAdd: Bundle[] = []; // Only add bundles to the bundle subject if they are not present yet - newBundles.forEach(newBundle => { + newBundlesPL.page.forEach(newBundle => { if (!currentBundles.some(currentBundle => currentBundle.id === newBundle.id)) { bundlesToAdd.push(newBundle); } }); - this.bundlesSubject.next([...currentBundles, ...bundlesToAdd]); + const updatedBundles = [...currentBundles, ...bundlesToAdd]; + + this.showLoadMoreLink$.next(updatedBundles.length < newBundlesPL.totalElements); + this.bundlesSubject.next(updatedBundles); } From 7b9cd73ee0811cd3188f21b4bbf5e1e64280849e Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 30 Apr 2025 14:51:42 +0200 Subject: [PATCH 44/54] [DURACOM-326] fix possible issue on missing value for eperson patch --- .../core/eperson/eperson-data.service.spec.ts | 32 +++++++++++++++ src/app/core/eperson/eperson-data.service.ts | 3 +- src/app/shared/testing/eperson.mock.ts | 40 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index ac735d8e92..f3209cd34c 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -31,6 +31,7 @@ import { import { EPersonMock, EPersonMock2, + EPersonMockWithNoName, } from '../../shared/testing/eperson.mock'; import { GroupMock } from '../../shared/testing/group-mock'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; @@ -281,6 +282,37 @@ describe('EPersonDataService', () => { }); }); + describe('updateEPerson with non existing metadata', () => { + beforeEach(() => { + spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMockWithNoName)); + }); + describe('add name that was not previously set', () => { + beforeEach(() => { + const changedEPerson = Object.assign(new EPerson(), { + id: EPersonMock.id, + metadata: Object.assign(EPersonMock.metadata, { + 'eperson.firstname': [ + { + language: null, + value: 'User', + }, + ], + }), + email: EPersonMock.email, + canLogIn: EPersonMock.canLogIn, + requireCertificate: EPersonMock.requireCertificate, + _links: EPersonMock._links, + }); + service.updateEPerson(changedEPerson).subscribe(); + }); + it('should send PatchRequest with add email operation', () => { + const operations = [{ op: 'add', path: '/eperson.firstname', value: [{ language: null, value: 'User' }] }]; + const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations); + expect(requestService.send).toHaveBeenCalledWith(expected); + }); + }); + }); + describe('clearEPersonRequests', () => { beforeEach(() => { spyOn(halService, 'getEndpoint').and.callFake((linkPath: string) => { diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 33a02de4d7..a493f55405 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -269,7 +269,8 @@ export class EPersonDataService extends IdentifiableDataService impleme * @param newEPerson */ private generateOperations(oldEPerson: EPerson, newEPerson: EPerson): Operation[] { - let operations = this.comparator.diff(oldEPerson, newEPerson).filter((operation: Operation) => operation.op === 'replace'); + let operations = this.comparator.diff(oldEPerson, newEPerson) + .filter((operation: Operation) => ['replace', 'add'].includes(operation.op)); if (hasValue(oldEPerson.email) && oldEPerson.email !== newEPerson.email) { operations = [...operations, { op: 'replace', path: '/email', value: newEPerson.email, diff --git a/src/app/shared/testing/eperson.mock.ts b/src/app/shared/testing/eperson.mock.ts index 002ee9326f..36daad1e57 100644 --- a/src/app/shared/testing/eperson.mock.ts +++ b/src/app/shared/testing/eperson.mock.ts @@ -91,3 +91,43 @@ export const EPersonMock2: EPerson = Object.assign(new EPerson(), { ], }, }); + +export const EPersonMockWithNoName: EPerson = Object.assign(new EPerson(), { + handle: null, + groups: [], + netid: 'test@test.com', + lastActive: '2018-05-14T12:25:42.411+0000', + canLogIn: true, + email: 'test@test.com', + requireCertificate: false, + selfRegistered: false, + _links: { + self: { + href: 'https://rest.api/dspace-spring-rest/api/eperson/epersons/testid', + }, + groups: { href: 'https://rest.api/dspace-spring-rest/api/eperson/epersons/testid/groups' }, + }, + id: 'testid', + uuid: 'testid', + type: 'eperson', + metadata: { + 'dc.title': [ + { + language: null, + value: 'User Test', + }, + ], + 'eperson.lastname': [ + { + language: null, + value: 'Test', + }, + ], + 'eperson.language': [ + { + language: null, + value: 'en', + }, + ], + }, +}); From dc8b10593c1769e7476e54808317c3ea10945a87 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 29 Jan 2025 20:53:54 +0100 Subject: [PATCH 45/54] 117287: Removed remaining observable function calls from the HTML templates --- .../metadata-field-form.component.html | 2 +- .../metadata-field-form.component.ts | 127 +++++++++--------- .../item-authorizations.component.html | 10 +- .../item-authorizations.component.spec.ts | 10 +- .../item-authorizations.component.ts | 46 ++----- .../virtual-metadata.component.html | 10 +- .../virtual-metadata.component.spec.ts | 6 +- .../virtual-metadata.component.ts | 40 ++++-- .../orcid-auth/orcid-auth.component.html | 22 +-- .../orcid-auth/orcid-auth.component.ts | 61 +++------ .../orcid-queue/orcid-queue.component.html | 8 +- .../orcid-queue/orcid-queue.component.ts | 12 +- src/app/navbar/navbar.component.html | 2 +- .../overview/process-overview.component.html | 8 +- .../overview/process-overview.component.ts | 4 +- ...rofile-page-researcher-form.component.html | 10 +- .../profile-page-researcher-form.component.ts | 20 +-- .../profile-page/profile-page.component.html | 2 +- .../profile-page.component.spec.ts | 55 +------- .../profile-page/profile-page.component.ts | 7 - .../register-email-form.component.html | 6 +- .../register-email-form.component.ts | 14 +- .../eperson-group-list.component.html | 61 ++++----- .../eperson-group-list.component.spec.ts | 64 +++------ .../eperson-group-list.component.ts | 79 +++-------- src/app/shared/host-window.service.ts | 4 +- .../pagination/pagination.component.html | 2 +- .../shared/pagination/pagination.component.ts | 8 +- ...submission-form-section-add.component.html | 6 +- .../submission-form-section-add.component.ts | 6 + .../form/submission-form.component.html | 6 +- .../form/submission-form.component.spec.ts | 2 +- .../form/submission-form.component.ts | 11 +- ...mission-section-cc-licenses.component.html | 38 +++--- ...ubmission-section-cc-licenses.component.ts | 23 +++- .../section-identifiers.component.html | 25 ++-- .../section-identifiers.component.ts | 28 +--- .../dspace/app/navbar/navbar.component.html | 2 +- 38 files changed, 332 insertions(+), 515 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html index 4e29fbb6e8..e03c8adf91 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html @@ -1,4 +1,4 @@ -
      +

      {{messagePrefix + '.create' | translate}}

      diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 773e0600fb..77c5d56378 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -10,7 +10,7 @@ import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest } from 'rxjs'; +import { Observable } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { MetadataField } from '../../../../core/metadata/metadata-field.model'; @@ -89,6 +89,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { */ @Output() submitForm: EventEmitter = new EventEmitter(); + activeMetadataField$: Observable; + constructor(public registryService: RegistryService, private formBuilderService: FormBuilderService, private translateService: TranslateService) { @@ -97,70 +99,65 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { /** * Initialize the component, setting up the necessary Models for the dynamic form */ - ngOnInit() { - combineLatest([ - this.translateService.get(`${this.messagePrefix}.element`), - this.translateService.get(`${this.messagePrefix}.qualifier`), - this.translateService.get(`${this.messagePrefix}.scopenote`) - ]).subscribe(([element, qualifier, scopenote]) => { - this.element = new DynamicInputModel({ - id: 'element', - label: element, - name: 'element', - validators: { - required: null, - pattern: '^[^. ,]*$', - maxLength: 64, - }, - required: true, - errorMessages: { - pattern: 'error.validation.metadata.element.invalid-pattern', - maxLength: 'error.validation.metadata.element.max-length', - }, - }); - this.qualifier = new DynamicInputModel({ - id: 'qualifier', - label: qualifier, - name: 'qualifier', - validators: { - pattern: '^[^. ,]*$', - maxLength: 64, - }, - required: false, - errorMessages: { - pattern: 'error.validation.metadata.qualifier.invalid-pattern', - maxLength: 'error.validation.metadata.qualifier.max-length', - }, - }); - this.scopeNote = new DynamicInputModel({ - id: 'scopeNote', - label: scopenote, - name: 'scopeNote', - required: false, - }); - this.formModel = [ - new DynamicFormGroupModel( - { - id: 'metadatadatafieldgroup', - group:[this.element, this.qualifier, this.scopeNote] - }) - ]; - this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => { - if (field == null) { - this.clearFields(); - } else { - this.formGroup.patchValue({ - metadatadatafieldgroup: { - element: field.element, - qualifier: field.qualifier, - scopeNote: field.scopeNote, - }, - }); - this.element.disabled = true; - this.qualifier.disabled = true; - } - }); + ngOnInit(): void { + this.activeMetadataField$ = this.registryService.getActiveMetadataField(); + this.element = new DynamicInputModel({ + id: 'element', + label: this.translateService.instant(`${this.messagePrefix}.element`), + name: 'element', + validators: { + required: null, + pattern: '^[^. ,]*$', + maxLength: 64, + }, + required: true, + errorMessages: { + pattern: 'error.validation.metadata.element.invalid-pattern', + maxLength: 'error.validation.metadata.element.max-length', + }, + }); + this.qualifier = new DynamicInputModel({ + id: 'qualifier', + label: this.translateService.instant(`${this.messagePrefix}.qualifier`), + name: 'qualifier', + validators: { + pattern: '^[^. ,]*$', + maxLength: 64, + }, + required: false, + errorMessages: { + pattern: 'error.validation.metadata.qualifier.invalid-pattern', + maxLength: 'error.validation.metadata.qualifier.max-length', + }, + }); + this.scopeNote = new DynamicInputModel({ + id: 'scopeNote', + label: this.translateService.instant(`${this.messagePrefix}.scopenote`), + name: 'scopeNote', + required: false, + }); + this.formModel = [ + new DynamicFormGroupModel( + { + id: 'metadatadatafieldgroup', + group:[this.element, this.qualifier, this.scopeNote] + }) + ]; + this.formGroup = this.formBuilderService.createFormGroup(this.formModel); + this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => { + if (field == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadatafieldgroup: { + element: field.element, + qualifier: field.qualifier, + scopeNote: field.scopeNote, + }, + }); + this.element.disabled = true; + this.qualifier.disabled = true; + } }); } diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html index 7f121081f2..7569ebd6e3 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html @@ -1,9 +1,9 @@
      - - + + - + @@ -16,7 +16,7 @@
      + *ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id).bitstreams | async)"> diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts index 5d2afbaf4c..3269a91118 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts @@ -147,17 +147,9 @@ describe('ItemAuthorizationsComponent test suite', () => { })); }); - it('should get the item UUID', () => { - - expect(comp.getItemUUID()).toBeObservable(cold('(a|)', { - a: item.id - })); - - }); - it('should get the item\'s bundle', () => { - expect(comp.getItemBundles()).toBeObservable(cold('a', { + expect(comp.bundles$).toBeObservable(cold('a', { a: bundles })); diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index 635cf455b5..09b3fe728e 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -4,7 +4,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; -import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators'; +import { catchError, filter, map, mergeMap, take } from 'rxjs/operators'; import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { @@ -17,6 +17,7 @@ import { LinkService } from '../../../core/cache/builders/link.service'; import { Bundle } from '../../../core/shared/bundle.model'; import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { Bitstream } from '../../../core/shared/bitstream.model'; +import { AlertType } from '../../../shared/alert/aletr-type'; /** * Interface for a bundle's bitstream map entry @@ -52,7 +53,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The target editing item * @type {Observable} */ - private item$: Observable; + item$: Observable; /** * Array to track all subscriptions and unsubscribe them onDestroy @@ -91,16 +92,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { */ private bitstreamPageSize = 4; - /** - * Initialize instance variables - * - * @param {LinkService} linkService - * @param {ActivatedRoute} route - * @param nameService - */ + itemName$: Observable; + + readonly AlertType = AlertType; + constructor( - private linkService: LinkService, - private route: ActivatedRoute, + protected linkService: LinkService, + protected route: ActivatedRoute, public nameService: DSONameService ) { } @@ -109,37 +107,19 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * Initialize the component, setting up the bundle and bitstream within the item */ ngOnInit(): void { - this.getBundlesPerItem(); + this.getBundlesPerItem(); + this.itemName$ = this.getItemName(); } /** - * Return the item's UUID + * Return the item's name */ - getItemUUID(): Observable { - return this.item$.pipe( - map((item: Item) => item.id), - first((UUID: string) => isNotEmpty(UUID)) - ); - } - - /** - * Return the item's name - */ - getItemName(): Observable { + private getItemName(): Observable { return this.item$.pipe( map((item: Item) => this.nameService.getName(item)) ); } - /** - * Return all item's bundles - * - * @return an observable that emits all item's bundles - */ - getItemBundles(): Observable { - return this.bundles$.asObservable(); - } - /** * Get all bundles per item * and all the bitstreams per bundle diff --git a/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html b/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html index b83b93d8f1..02cd26f78e 100644 --- a/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html +++ b/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html @@ -5,9 +5,9 @@
    @if (coarLdnEnabled$ | async) { -
    + - + \ No newline at end of file diff --git a/src/app/footer/footer.component.scss b/src/app/footer/footer.component.scss index 7b6c6ae239..9381d7378f 100644 --- a/src/app/footer/footer.component.scss +++ b/src/app/footer/footer.component.scss @@ -23,9 +23,8 @@ .bottom-footer { .notify-enabled { - position: absolute; - bottom: 4px; - right: 0; + position: relative; + margin-top: 4px; .coar-notify-support-route { padding: 0 calc(var(--bs-spacer) / 2); @@ -37,7 +36,11 @@ margin-bottom: 8.5px; } - margin-top: 20px; + @media screen and (min-width: map-get($grid-breakpoints, md)) { + position: absolute; + bottom: 4px; + right: 0; + } } ul { li { diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.html b/src/app/item-page/full/field-components/file-section/full-file-section.component.html index b04d636a13..0d7a0ef6da 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.html @@ -1,84 +1,108 @@
    @if (hasValuesInBundle(originals)) { -
    -

    {{"item.page.filesection.original.bundle" | translate}}

    - @if (originals?.page?.length > 0) { - - @for (file of originals?.page; track file) { -
    -
    - -
    -
    -
    -
    {{"item.page.filesection.name" | translate}}
    -
    {{ dsoNameService.getName(file) }}
    -
    {{"item.page.filesection.size" | translate}}
    -
    {{(file.sizeBytes) | dsFileSize }}
    -
    {{"item.page.filesection.format" | translate}}
    -
    {{(file.format | async)?.payload?.description}}
    - @if (file.hasMetadata('dc.description')) { -
    {{"item.page.filesection.description" | translate}}
    -
    {{file.firstMetadataValue("dc.description")}}
    - } -
    -
    -
    - - {{"item.page.filesection.download" | translate}} - -
    -
    - } -
    +
    +

    + {{ "item.page.filesection.original.bundle" | translate }} +

    + @if (originals?.page?.length > 0) { + + @for (file of originals?.page; track file) { +
    +
    + +
    +
    +
    +
    + {{ "item.page.filesection.name" | translate }} +
    +
    {{ dsoNameService.getName(file) }}
    +
    + {{ "item.page.filesection.size" | translate }} +
    +
    {{ file.sizeBytes | dsFileSize }}
    +
    + {{ "item.page.filesection.format" | translate }} +
    +
    + {{ (file.format | async)?.payload?.description }} +
    + @if (file.hasMetadata('dc.description')) { +
    + {{ "item.page.filesection.description" | translate }} +
    +
    + {{ file.firstMetadataValue("dc.description") }} +
    + } +
    +
    +
    + + + {{ "item.page.filesection.download" | translate }} + + +
    +
    } -
    + + } +
    }
    @if (hasValuesInBundle(licenses)) { -
    -

    {{"item.page.filesection.license.bundle" | translate}}

    - @if (licenses?.page?.length > 0) { - - @for (file of licenses?.page; track file) { -
    -
    - -
    -
    -
    -
    {{"item.page.filesection.name" | translate}}
    -
    {{ dsoNameService.getName(file) }}
    -
    {{"item.page.filesection.size" | translate}}
    -
    {{(file.sizeBytes) | dsFileSize }}
    -
    {{"item.page.filesection.format" | translate}}
    -
    {{(file.format | async)?.payload?.description}}
    -
    {{"item.page.filesection.description" | translate}}
    -
    {{file.firstMetadataValue("dc.description")}}
    -
    -
    -
    - - {{"item.page.filesection.download" | translate}} - -
    -
    - } -
    +
    +

    + {{ "item.page.filesection.license.bundle" | translate }} +

    + @if (licenses?.page?.length > 0) { + + @for (file of licenses?.page; track file) { +
    +
    + +
    +
    +
    +
    + {{ "item.page.filesection.name" | translate }} +
    +
    {{ dsoNameService.getName(file) }}
    +
    + {{ "item.page.filesection.size" | translate }} +
    +
    {{ file.sizeBytes | dsFileSize }}
    +
    + {{ "item.page.filesection.format" | translate }} +
    +
    + {{ (file.format | async)?.payload?.description }} +
    +
    + {{ "item.page.filesection.description" | translate }} +
    +
    + {{ file.firstMetadataValue("dc.description") }} +
    +
    +
    +
    + + + {{ "item.page.filesection.download" | translate }} + + +
    +
    } -
    + + } +
    }
    diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html index bb9144ce99..422bb2ce3d 100644 --- a/src/app/shared/file-download-link/file-download-link.component.html +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -5,17 +5,29 @@ [queryParams]="(bitstreamPath$| async)?.queryParams" [target]="isBlank ? '_blank': '_self'" [ngClass]="cssClasses" - [attr.aria-label]="('file-download-link.download' | translate) + dsoNameService.getName(bitstream)" + [attr.aria-label]="getDownloadLinkTitle(canDownload$ | async, canDownloadWithToken$ | async, dsoNameService.getName(bitstream))" + [title]="getDownloadLinkTitle(canDownload$ | async, canDownloadWithToken$ | async, dsoNameService.getName(bitstream))" role="link" tabindex="0"> @if ((canDownload$ | async) === false && (canDownloadWithToken$ | async) === false) { - + + + } @else if ((canDownloadWithToken$ | async) && (canDownload$ | async) === false) { - + + + + } @else if (showIcon) { + } -
    diff --git a/src/app/shared/file-download-link/file-download-link.component.scss b/src/app/shared/file-download-link/file-download-link.component.scss index 4b18747a95..06b448f96c 100644 --- a/src/app/shared/file-download-link/file-download-link.component.scss +++ b/src/app/shared/file-download-link/file-download-link.component.scss @@ -1,3 +1,7 @@ .request-a-copy-access-icon { color: var(--bs-success); } + +.btn-download{ + width: fit-content; +} diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index 7eb2392b07..09e03f8dac 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -12,7 +12,10 @@ import { ActivatedRoute, RouterLink, } from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { combineLatest as observableCombineLatest, Observable, @@ -75,6 +78,11 @@ export class FileDownloadLinkComponent implements OnInit { */ @Input() showAccessStatusBadge = true; + /** + * A boolean indicating whether the download icon should be displayed. + */ + @Input() showIcon = false; + itemRequest: ItemRequest; bitstreamPath$: Observable<{ @@ -90,6 +98,7 @@ export class FileDownloadLinkComponent implements OnInit { private authorizationService: AuthorizationDataService, public dsoNameService: DSONameService, private route: ActivatedRoute, + private translateService: TranslateService, ) { } @@ -153,4 +162,9 @@ export class FileDownloadLinkComponent implements OnInit { queryParams: {}, }; } + + getDownloadLinkTitle(canDownload: boolean,canDownloadWithToken: boolean, bitstreamName: string): string { + return (canDownload || canDownloadWithToken ? this.translateService.instant('file-download-link.download') : + this.translateService.instant('file-download-link.request-copy')) + bitstreamName; + } } diff --git a/src/app/shared/file-download-link/themed-file-download-link.component.ts b/src/app/shared/file-download-link/themed-file-download-link.component.ts index 1b5def4012..52add33069 100644 --- a/src/app/shared/file-download-link/themed-file-download-link.component.ts +++ b/src/app/shared/file-download-link/themed-file-download-link.component.ts @@ -29,6 +29,8 @@ export class ThemedFileDownloadLinkComponent extends ThemedComponent{{ accessStatus.status | translate: {date: accessStatus.date} }} + {{ accessStatus.status | translate: {date: accessStatus.date} }} } } diff --git a/src/app/shared/object-collection/shared/badges/access-status-badge/access-status-badge.component.scss b/src/app/shared/object-collection/shared/badges/access-status-badge/access-status-badge.component.scss index 8b13789179..bcf09e5ecc 100644 --- a/src/app/shared/object-collection/shared/badges/access-status-badge/access-status-badge.component.scss +++ b/src/app/shared/object-collection/shared/badges/access-status-badge/access-status-badge.component.scss @@ -1 +1,3 @@ - +span{ + white-space: normal; +} diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index de569270e0..c3e9087f27 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,25 +1,30 @@
    @if (isLoading) { -
    -
    -
    - -
    +
    +
    +
    +
    +
    } @if (src !== null) { - - } - @if (src === null && !isLoading) { -
    -
    -
    - {{ placeholder | translate }} -
    + + } @if (src === null && !isLoading) { +
    +
    +
    + {{ placeholder | translate }}
    +
    }
    diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 16d56f9708..adbb540dc0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7061,4 +7061,6 @@ "embargo.listelement.badge": "Embargo until {{ date }}", "metadata-export-search.submit.error.limit-exceeded": "Only the first {{limit}} items will be exported", + + "file-download-link.request-copy": "Request a copy of ", }